// 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.chromium.debug.core.model.IPredefinedSourceWrapProvider;
import org.chromium.debug.core.model.LaunchParams;
import org.chromium.debug.core.model.LaunchParams.LookupMode;
import org.chromium.debug.ui.PluginUtil;
import org.chromium.debug.ui.TableUtils;
import org.chromium.debug.ui.TableUtils.ColumnBasedLabelProvider;
import org.chromium.debug.ui.TableUtils.ColumnData;
import org.chromium.debug.ui.launcher.LaunchTabGroup.Params;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
/**
* A launch configuration tab that holds source look-up and mapping parameters.
*/
public class ScriptMappingTab extends TabBase<ScriptMappingTab.Elements, Params> {
interface Elements {
RadioButtonsLogic<LookupMode> getLookupMode();
PredefinedWrapChooser getPredefinedWrapChooser();
}
ScriptMappingTab(Params params) {
super(params);
}
@Override
public Elements createElements(Composite parent, final Runnable modifyListener) {
Composite composite = createDefaultComposite(parent);
setControl(composite);
LookupModeControl lookupModeControl =
new LookupModeControl(composite, getParams().getScriptNameDescription());
RadioButtonsLogic.Listener radioButtonsListener =
new RadioButtonsLogic.Listener() {
public void selectionChanged() {
modifyListener.run();
}
};
final RadioButtonsLogic<LookupMode> lookupModeLogic =
lookupModeControl.createLogic(radioButtonsListener);
Group group = new Group(composite, SWT.NONE);
group.setText(Messages.ScriptMappingTab_RECOGNIZED_WRAPPING);
{
GridData data = new GridData();
data.horizontalAlignment = GridData.FILL;
group.setLayoutData(data);
GridLayout layout = new GridLayout();
layout.numColumns = 1;
group.setLayout(layout);
}
final PredefinedWrapChooser wrapChooser = new PredefinedWrapChooser(group);
{
GridData data = new GridData();
data.horizontalAlignment = GridData.FILL;
wrapChooser.getControl().setLayoutData(data);
}
wrapChooser.addListener(new PredefinedWrapChooser.Listener() {
@Override public void checkStateChanged() {
modifyListener.run();
}
});
return new Elements() {
@Override public RadioButtonsLogic<LookupMode> getLookupMode() {
return lookupModeLogic;
}
@Override public PredefinedWrapChooser getPredefinedWrapChooser() {
return wrapChooser;
}
};
}
/**
* UI control that allows to select supported wrappers from a predefined set.
* It deals in terms {@link IPredefinedSourceWrapProvider.Entry} ids.
* The control gives special treatment to ids that weren't resolved allowing user
* to deselect them.
*/
private static class PredefinedWrapChooser {
private final Control root;
private final CheckboxTableViewer tableViewer;
private final List<Listener> listeners = new ArrayList<Listener>(1);
PredefinedWrapChooser(Composite parent) {
final Table table = new Table(parent, SWT.CHECK | SWT.BORDER | SWT.MULTI |
SWT.FULL_SELECTION | SWT.V_SCROLL);
root = table;
table.setFont(parent.getFont());
tableViewer = new CheckboxTableViewer(table);
table.setHeaderVisible(false);
tableViewer.setContentProvider(TableUtils.OBJECT_ARRAY_CONTENT_PROVIDER);
TableUtils.ColumnLabelProvider<ItemData> columnOneLabelProvider =
new TableUtils.ColumnLabelProvider<ItemData>() {
@Override public Image getColumnImage(ItemData element) {
return null;
}
@Override
public String getColumnText(ItemData element) {
return element.accept(columnTextVisitor);
}
private final ItemData.Visitor<String> columnTextVisitor = new ItemData.Visitor<String>() {
@Override public String visitNormal(IPredefinedSourceWrapProvider.Entry entry) {
return entry.getWrapper().getName();
}
@Override public String visitUnresolved(String id) {
return NLS.bind(Messages.ScriptMappingTab_UNRESOVLED, id);
}
};
@Override
public TableColumn createColumn(Table table) {
TableColumn statusCol = new TableColumn(table, SWT.NONE);
int width = PluginUtil.getFontMetrics(table, table.getFont()).getAverageCharWidth() * 40;
statusCol.setWidth(width);
return statusCol;
}
};
List<ColumnData<ItemData, ?>> columnList = new ArrayList<ColumnData<ItemData, ?>>(1);
columnList.add(ColumnData.create(
new TableUtils.TrivialAdapter<ItemData>(), columnOneLabelProvider));
ColumnBasedLabelProvider<ItemData> labelProvider = new ColumnBasedLabelProvider<ItemData>(
TableUtils.createCastAdapter(ItemData.class), columnList);
labelProvider.setUpColumns(table);
tableViewer.setLabelProvider(labelProvider);
tableViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(final DoubleClickEvent event) {
ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection == false) {
return;
}
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
if (structuredSelection.size() != 1) {
return;
}
ItemData data = (ItemData) structuredSelection.getFirstElement();
data.accept(new ItemData.Visitor<Void>() {
@Override
public Void visitNormal(IPredefinedSourceWrapProvider.Entry entry) {
MessageBox messageBox =
new MessageBox(event.getViewer().getControl().getShell(), SWT.RESIZE);
String description = entry.getHumanDescription();
messageBox.setText(Messages.ScriptMappingTab_DESCRIPTION);
messageBox.setMessage(description);
messageBox.open();
return null;
}
@Override
public Void visitUnresolved(String id) {
// Keep silent.
return null;
}
});
}
});
tableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
for (Listener listener : listeners) {
listener.checkStateChanged();
}
}
});
}
Control getControl() {
return root;
}
void setData(Collection<String> wrapperIds,
Map<String, IPredefinedSourceWrapProvider.Entry> entriesMap) {
List<ItemData> itemList = new ArrayList<ItemData>(entriesMap.size());
Map<String, Integer> idToPosition = new HashMap<String, Integer>(entriesMap.size());
for (Map.Entry<String, IPredefinedSourceWrapProvider.Entry> en : entriesMap.entrySet()) {
idToPosition.put(en.getKey(), itemList.size());
itemList.add(ItemData.createNormal(en.getValue()));
}
List<ItemData> selected = new ArrayList<ItemData>(wrapperIds.size());
for (String id : wrapperIds) {
ItemData item;
Integer pos = idToPosition.get(id);
if (pos == null) {
item = ItemData.createUnresolved(id);
itemList.add(item);
} else {
item = itemList.get(pos);
}
selected.add(item);
}
tableViewer.setInput(itemList.toArray());
tableViewer.setCheckedElements(selected.toArray());
}
Collection<String> getValue() {
Object[] selection = tableViewer.getCheckedElements();
List<String> result = new ArrayList<String>(selection.length);
ItemData.Visitor<String> visitor = new ItemData.Visitor<String>() {
@Override public String visitNormal(IPredefinedSourceWrapProvider.Entry entry) {
return entry.getId();
}
@Override public String visitUnresolved(String id) {
return id;
}
};
for (Object selectionObject : selection) {
ItemData item = (ItemData) selectionObject;
result.add(item.accept(visitor));
}
return result;
}
void addListener(Listener listener) {
listeners.add(listener);
}
static abstract class ItemData {
interface Visitor<R> {
R visitNormal(IPredefinedSourceWrapProvider.Entry entry);
R visitUnresolved(String id);
}
abstract <R> R accept(Visitor<R> visitor);
static ItemData createNormal(final IPredefinedSourceWrapProvider.Entry entry) {
return new ItemData() {
@Override <R> R accept(Visitor<R> visitor) {
return visitor.visitNormal(entry);
}
};
}
static ItemData createUnresolved(final String id) {
return new ItemData() {
@Override <R> R accept(Visitor<R> visitor) {
return visitor.visitUnresolved(id);
}
};
}
}
interface Listener {
void checkStateChanged();
}
}
/**
* Dialog UI group of 2 radio buttons for lookup mode.
*/
private static class LookupModeControl {
private final Map<LookupMode, Button> buttons;
LookupModeControl(Composite container, String scriptNameFormatDescription) {
buttons = new LinkedHashMap<LookupMode, Button>();
Group group = new Group(container, 0);
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
group.setText(Messages.ChromiumRemoteTab_LOOKUP_GROUP_TITLE);
group.setLayout(new GridLayout(1, false));
buttons.put(LookupMode.EXACT_MATCH, createButtonBlock(group,
Messages.ChromiumRemoteTab_EXACT_MATCH, Messages.ChromiumRemoteTab_EXACT_MATCH_LINE1,
Messages.ChromiumRemoteTab_EXACT_MATCH_LINE2));
buttons.put(LookupMode.AUTO_DETECT, createButtonBlock(group,
Messages.ChromiumRemoteTab_AUTODETECT, Messages.ChromiumRemoteTab_AUTODETECT_LINE1,
Messages.ChromiumRemoteTab_AUTODETECT_LINE2 + scriptNameFormatDescription));
addRadioButtonSwitcher(buttons.values());
}
RadioButtonsLogic<LookupMode> createLogic(RadioButtonsLogic.Listener listener) {
return new RadioButtonsLogic<LookupMode>(buttons, listener);
}
private static Button createButtonBlock(Composite parent, String buttonLabel,
String descriptionLine1, String descriptionLine2) {
Composite buttonComposite = new Composite(parent, SWT.NONE);
GridLayout gridLayout = createHtmlStyleGridLayout(3);
buttonComposite.setLayout(gridLayout);
Button button = new Button(buttonComposite, SWT.RADIO | SWT.NO_RADIO_GROUP);
button.setText(buttonLabel);
Label padding = new Label(buttonComposite, SWT.NONE);
padding.setText(" "); //$NON-NLS-1$
Label descriptionLine1Label = new Label(buttonComposite, SWT.NONE);
descriptionLine1Label.setText(descriptionLine1);
// Extra label to fill a grid in layout.
new Label(buttonComposite, SWT.NONE);
new Label(buttonComposite, SWT.NONE);
Label descriptionLine2Label = new Label(buttonComposite, SWT.NONE);
descriptionLine2Label.setText(descriptionLine2);
return button;
}
}
@Override
public String getName() {
return Messages.ScriptMappingTab_TAB_NAME;
}
@Override
protected MessageData isValidImpl(ILaunchConfiguration config) {
try {
LaunchParams.PredefinedSourceWrapperIds.resolveEntries(config);
} catch (CoreException e) {
return new MessageData(false,
NLS.bind(Messages.ScriptMappingTab_UNRESOLVED_ERROR_MESSAGE, e.getMessage()));
}
return null;
}
protected TabFieldList<Elements, Params> getTabFields() {
return TAB_FIELDS;
}
static final TabFieldList<Elements, Params> TAB_FIELDS;
static {
List<TabField<?, ?, Elements, Params>> list =
new ArrayList<ChromiumRemoteTab.TabField<?, ?, Elements, Params>>(4);
list.add(new TabField<String, LookupMode, Elements, Params>(
LaunchParams.SOURCE_LOOKUP_MODE, TypedMethods.STRING,
new FieldAccess<LookupMode, Elements>() {
@Override
void setValue(LookupMode value, Elements tabElements) {
tabElements.getLookupMode().select(value);
}
@Override
LookupMode getValue(Elements tabElements) {
return tabElements.getLookupMode().getSelected();
}
},
new DefaultsProvider<LookupMode, Params>() {
@Override LookupMode getFallbackValue() {
// TODO: support default value from eclipse variables.
return LookupMode.DEFAULT_VALUE;
}
@Override LookupMode getInitialConfigValue(Params context) {
return LookupMode.AUTO_DETECT;
}
},
LookupMode.STRING_CONVERTER));
list.add(new TabField<String, List<String>, Elements, Params>(
LaunchParams.PredefinedSourceWrapperIds.CONFIG_PROPERTY, TypedMethods.STRING,
new FieldAccess<List<String>, Elements>() {
@Override
void setValue(List<String> value, Elements tabElements) {
tabElements.getPredefinedWrapChooser().setData(value,
IPredefinedSourceWrapProvider.Access.getEntries());
}
@Override
List<String> getValue(Elements tabElements) {
List<String> result =
new ArrayList<String>(tabElements.getPredefinedWrapChooser().getValue());
return result;
}
},
new DefaultsProvider<List<String>, Params>() {
@Override List<String> getFallbackValue() {
return Collections.emptyList();
}
@Override
List<String> getInitialConfigValue(Params context) {
List<String> result;
if (context.preEnableSourceWrapper()) {
result = new ArrayList<String>(
IPredefinedSourceWrapProvider.Access.getEntries().keySet());
} else {
result = Collections.emptyList();
}
return result;
}
},
LaunchParams.PredefinedSourceWrapperIds.CONVERTER));
TAB_FIELDS = createFieldListImpl(list);
}
}