// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.debug.ui.launcher;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.model.LaunchParams.ValueConverter;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
/**
* Basic implementation of launch configuration tab. It holds input parameters and created
* UI elements. The class is abstract and requires a set of fields to be described in
* a ancestor. This way it can automatically handle main tab lifecycle by loading, storing
* and initializing them.
* @param <ELEMENTS> type interface holding UI elements
* @param <PARAMS> type of interface holding input parameters
*/
abstract class TabBase<ELEMENTS, PARAMS> extends AbstractLaunchConfigurationTab {
private final PARAMS params;
private ELEMENTS tabElements = null;
protected TabBase(PARAMS params) {
this.params = params;
}
protected PARAMS getParams() {
return params;
}
@Override
public void createControl(Composite parent) {
Runnable modifyListener = new Runnable() {
@Override public void run() {
updateLaunchConfigurationDialog();
}
};
tabElements = createElements(parent, modifyListener);
}
protected abstract ELEMENTS createElements(Composite parent, Runnable modifyListener);
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
getTabFields().setDefaults(configuration, getParams());
}
@Override
public void initializeFrom(ILaunchConfiguration configuration) {
getTabFields().initializeFrom(tabElements, configuration);
}
@Override
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
getTabFields().saveToConfig(tabElements, configuration);
}
@Override
public boolean isValid(ILaunchConfiguration config) {
MessageData messageData;
try {
messageData = isValidImpl(config);
} catch (CoreException e) {
ChromiumDebugPlugin.log(new Exception("Unexpected storage problem", e)); //$NON-NLS-1$
messageData = new MessageData(true, "Internal error " + e.getMessage()); //$NON-NLS-1$
}
if (messageData == null) {
messageData = MessageData.EMPTY_OK;
}
if (messageData.isValid()) {
setMessage(messageData.getMessage());
setErrorMessage(null);
} else {
setMessage(null);
setErrorMessage(messageData.getMessage());
}
return messageData.isValid();
}
/**
* Tries to check whether config is valid and return message or fails with exception.
*/
protected abstract MessageData isValidImpl(ILaunchConfiguration config) throws CoreException;
/**
* Describes a tab error/warning message.
*/
protected static class MessageData {
public static final MessageData EMPTY_OK = new MessageData(true, null);
private final boolean valid;
private final String message;
public MessageData(boolean isValid, String message) {
this.valid = isValid;
this.message = message;
}
public boolean isValid() {
return valid;
}
public String getMessage() {
return message;
}
}
protected abstract TabFieldList<? super ELEMENTS, ? super PARAMS> getTabFields();
/**
* A dialog window tab field description. It is a static description -- it has no reference to
* a particular element instance.
* @param <P> physical type of field as stored in config; used internally
* @param <L> logical type of field used in runtime operations
* @param <E> interface to dialog elements
* @param <C> context that holds immutable input parameter of the tab dialog
*/
static class TabField<P, L, E, C> {
private final String configAttributeName;
private final TypedMethods<P> typedMethods;
private final FieldAccess<L, E> fieldAccess;
private final DefaultsProvider<L, C> defaultsProvider;
private final ValueConverter<P, L> valueConverter;
TabField(String configAttributeName, TypedMethods<P> typedMethods,
FieldAccess<L, E> fieldAccess, DefaultsProvider<L, C> defaultsProvider,
ValueConverter<P, L> valueConverter) {
this.typedMethods = typedMethods;
this.defaultsProvider = defaultsProvider;
this.configAttributeName = configAttributeName;
this.fieldAccess = fieldAccess;
this.valueConverter = valueConverter;
}
void saveToConfig(E tabElements, ILaunchConfigurationWorkingCopy config) {
L logicalValue = fieldAccess.getValue(tabElements);
P persistentValue = valueConverter.encode(logicalValue);
typedMethods.setConfigAttribute(config, configAttributeName, persistentValue);
}
void initializeFrom(E tabElements, ILaunchConfiguration config) {
L fallbackLogicalValue = defaultsProvider.getFallbackValue();
P fallbackPersistenValue = valueConverter.encode(fallbackLogicalValue);
L value;
try {
P persistentValue = typedMethods.getConfigAttribute(config, configAttributeName,
fallbackPersistenValue);
value = valueConverter.decode(persistentValue);
} catch (CoreException e) {
ChromiumDebugPlugin.log(new Exception("Unexpected storage problem", e)); //$NON-NLS-1$
value = fallbackLogicalValue;
}
fieldAccess.setValue(value, tabElements);
}
public void setDefault(ILaunchConfigurationWorkingCopy config, C context) {
L value = defaultsProvider.getInitialConfigValue(context);
if (value != null) {
P persistentValue = valueConverter.encode(value);
typedMethods.setConfigAttribute(config, configAttributeName, persistentValue);
}
}
}
static abstract class FieldAccess<T, E> {
abstract void setValue(T value, E tabElements);
abstract T getValue(E tabElements);
}
static abstract class FieldEditorAccess<T, E> extends FieldAccess<T, E> {
private final TypedMethods<T> fieldType;
FieldEditorAccess(TypedMethods<T> fieldType) {
this.fieldType = fieldType;
}
@Override
void setValue(T value, E tabElements) {
FieldEditor fieldEditor = getFieldEditor(tabElements);
fieldType.setStoreDefaultValue(fieldEditor.getPreferenceStore(),
fieldEditor.getPreferenceName(), value);
fieldEditor.loadDefault();
}
@Override
T getValue(E tabElements) {
FieldEditor fieldEditor = getFieldEditor(tabElements);
storeEditor(fieldEditor, getEditorErrorValue());
return fieldType.getStoreValue(fieldEditor.getPreferenceStore(),
fieldEditor.getPreferenceName());
}
abstract FieldEditor getFieldEditor(E tabElements);
abstract String getEditorErrorValue();
}
static abstract class DefaultsProvider<T, C> {
abstract T getFallbackValue();
abstract T getInitialConfigValue(C context);
}
/**
* Provides uniform access to various signatures of config and store methods.
*/
static abstract class TypedMethods<T> {
abstract T getConfigAttribute(ILaunchConfiguration config, String attributeName,
T defaultValue) throws CoreException;
abstract void setConfigAttribute(ILaunchConfigurationWorkingCopy config, String attributeName,
T value);
abstract T getStoreValue(IPreferenceStore store, String preferenceName);
abstract void setStoreDefaultValue(IPreferenceStore store, String propertyName, T value);
static final TypedMethods<String> STRING = new TypedMethods<String>() {
String getConfigAttribute(ILaunchConfiguration config, String attributeName,
String defaultValue) throws CoreException {
return config.getAttribute(attributeName, defaultValue);
}
public void setConfigAttribute(ILaunchConfigurationWorkingCopy config, String attributeName,
String value) {
config.setAttribute(attributeName, value);
}
void setStoreDefaultValue(IPreferenceStore store, String propertyName, String value) {
store.setDefault(propertyName, value);
}
String getStoreValue(IPreferenceStore store, String preferenceName) {
return store.getString(preferenceName);
}
};
static final TypedMethods<Integer> INT = new TypedMethods<Integer>() {
public void setConfigAttribute(ILaunchConfigurationWorkingCopy config, String attributeName,
Integer value) {
config.setAttribute(attributeName, value);
}
Integer getConfigAttribute(ILaunchConfiguration config, String attributeName,
Integer defaultValue) throws CoreException {
return config.getAttribute(attributeName, defaultValue);
}
void setStoreDefaultValue(IPreferenceStore store, String propertyName, Integer value) {
store.setDefault(propertyName, value);
}
Integer getStoreValue(IPreferenceStore store, String preferenceName) {
return store.getInt(preferenceName);
}
};
static final TypedMethods<Boolean> BOOL = new TypedMethods<Boolean>() {
public void setConfigAttribute(ILaunchConfigurationWorkingCopy config, String attributeName,
Boolean value) {
config.setAttribute(attributeName, value);
}
Boolean getConfigAttribute(ILaunchConfiguration config, String attributeName,
Boolean defaultValue) throws CoreException {
return config.getAttribute(attributeName, defaultValue);
}
void setStoreDefaultValue(IPreferenceStore store, String propertyName, Boolean value) {
store.setDefault(propertyName, value);
}
Boolean getStoreValue(IPreferenceStore store, String preferenceName) {
return store.getBoolean(preferenceName);
}
};
}
protected static Composite createDefaultComposite(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
layout.numColumns = 1;
composite.setLayout(layout);
GridData data = new GridData();
data.verticalAlignment = GridData.FILL;
data.horizontalAlignment = GridData.FILL;
composite.setLayoutData(data);
return composite;
}
protected static Composite createInnerComposite(Composite parent, int numColumns) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setLayout(new GridLayout(numColumns, false));
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
composite.setLayoutData(gd);
return composite;
}
protected static GridLayout createHtmlStyleGridLayout(int numberOfColumns) {
GridLayout layout = new GridLayout(numberOfColumns, false);
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
return layout;
}
private static void storeEditor(FieldEditor editor, String errorValue) {
if (editor.isValid()) {
editor.store();
} else {
editor.getPreferenceStore().setValue(editor.getPreferenceName(), errorValue);
}
}
static class RadioButtonsLogic<K> {
private final Map<K, Button> buttons;
RadioButtonsLogic(Map<K, Button> buttons, final Listener listener) {
this.buttons = buttons;
SelectionListener selectionListener = new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent e) {
}
public void widgetSelected(SelectionEvent e) {
if (listener != null && e.widget instanceof Button) {
Button button = (Button) e.widget;
if (button.getSelection()) {
listener.selectionChanged();
}
}
}
};
for (Button button : buttons.values()) {
button.addSelectionListener(selectionListener);
}
}
void select(K key) {
for (Map.Entry<K, Button> en : buttons.entrySet()) {
en.getValue().setSelection(en.getKey().equals(key));
}
}
K getSelected() {
for (Map.Entry<K, Button> en : buttons.entrySet()) {
if (en.getValue().getSelection()) {
return en.getKey();
}
}
return null;
}
interface Listener {
void selectionChanged();
}
}
static void addRadioButtonSwitcher(final Collection<Button> buttons) {
SelectionListener selectionListener = new SelectionListener() {
public void widgetDefaultSelected(SelectionEvent e) {
}
public void widgetSelected(SelectionEvent e) {
if (e.widget instanceof Button) {
Button button = (Button) e.widget;
if (!button.getSelection()) {
return;
}
for (Button other : buttons) {
if (other == button) {
continue;
}
other.setSelection(false);
}
}
}
};
for (Button button : buttons) {
if ((button.getStyle() & SWT.NO_RADIO_GROUP) == 0) {
throw new IllegalArgumentException();
}
button.addSelectionListener(selectionListener);
}
}
/**
* Encapsulate tab field list together with a way of accessing them. This includes adapting
* type of dialog elements structure (<E>) to a type accepted by fields.
* @param <E> type of elements structure that provides getters to dialog elements
* @param <P> type of dialog window parameters
*/
interface TabFieldList<E, P> {
void setDefaults(ILaunchConfigurationWorkingCopy configuration, P params);
void initializeFrom(E elements, ILaunchConfiguration configuration);
void saveToConfig(E elements, ILaunchConfigurationWorkingCopy configuration);
}
/**
* Create a plain implementation of {@link TabFieldList} over a list of tab fields.
*/
static <E, P> TabFieldList<E, P> createFieldListImpl(
final List<? extends TabField<?, ?, ? super E, P>> tabFields) {
return new TabFieldList<E, P>() {
public void setDefaults(ILaunchConfigurationWorkingCopy configuration, P params) {
for (TabField<?, ?, ?, P> field : tabFields) {
field.setDefault(configuration, params);
}
}
@Override
public void initializeFrom(E elements, ILaunchConfiguration configuration) {
for (TabField<?, ?, ? super E, ?> field : tabFields) {
field.initializeFrom(elements, configuration);
}
}
@Override
public void saveToConfig(E elements, ILaunchConfigurationWorkingCopy configuration) {
for (TabField<?, ?, ? super E, ?> field : tabFields) {
field.saveToConfig(elements, configuration);
}
}
};
}
interface Adapter<F, T> {
T get(F from);
}
/**
* Creates {@link TabFieldList} implementation that adapts dialog elements type using the adapter
* to inner type of dialog elements that provided list of {@link TabFieldList} accept.
* @param list of tab fields that accepts alternative type of dialog elements structure
* @param elementsAdapter converts external dialog elements structure type to the inner type
*/
static <E, INNER, P> TabFieldList<E, P> createFieldListAdapting(
final TabFieldList<? super INNER, ? super P> list, final Adapter<E, INNER> elementsAdapter) {
return new TabFieldList<E, P>() {
@Override public void setDefaults(ILaunchConfigurationWorkingCopy configuration, P params) {
list.setDefaults(configuration, params);
}
@Override public void initializeFrom(E elements, ILaunchConfiguration configuration) {
list.initializeFrom(elementsAdapter.get(elements), configuration);
}
@Override
public void saveToConfig(E elements, ILaunchConfigurationWorkingCopy configuration) {
list.saveToConfig(elementsAdapter.get(elements), configuration);
}
};
}
static <E, P> TabFieldList<E, P> createCompositeFieldList(
final List<? extends TabFieldList<? super E, ? super P>> listList) {
return new TabFieldList<E, P>() {
@Override
public void setDefaults(ILaunchConfigurationWorkingCopy configuration,
P params) {
for (TabFieldList<?, ? super P> list : listList) {
list.setDefaults(configuration, params);
}
}
@Override
public void initializeFrom(E elements, ILaunchConfiguration configuration) {
for (TabFieldList<? super E, ?> list : listList) {
list.initializeFrom(elements, configuration);
}
}
@Override
public void saveToConfig(E elements,
ILaunchConfigurationWorkingCopy configuration) {
for (TabFieldList<? super E, ?> list : listList) {
list.saveToConfig(elements, configuration);
}
}
};
}
}