blob: 09c4b4023723e15c13718e51cf9d4f066cee851f [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2016 itemis and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* itemis - Initial API and implementation
* itemis - [506671] Add support for specifying and injecting user-defined arguments for workflows through workflow launch configurations
*
* </copyright>
*/
package org.eclipse.sphinx.platform.ui.groups;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.sphinx.platform.ui.groups.messages.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
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.Table;
import org.eclipse.swt.widgets.TableItem;
public class NameValueTableGroup extends AbstractGroup {
protected static final String DEFAULT_NAME = Messages.cell_name_default;
/**
* Content provider for the name/value table
*/
protected static class NameValueViewerContentProvider implements IStructuredContentProvider {
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, String> entries = (Map<String, String>) inputElement;
return entries.entrySet().toArray(new Object[entries.size()]);
}
return new Object[0];
}
@Override
public void dispose() {
// Do nothing
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (newInput == null) {
return;
}
if (viewer instanceof TableViewer) {
TableViewer tableViewer = (TableViewer) viewer;
if (tableViewer.getTable().isDisposed()) {
return;
}
tableViewer.setComparator(new ViewerComparator() {
@Override
@SuppressWarnings("unchecked")
public int compare(Viewer viewer, Object e1, Object e2) {
if (e1 == null) {
return -1;
} else if (e2 == null) {
return 1;
} else {
Map.Entry<String, String> entry1 = (Map.Entry<String, String>) e1;
Map.Entry<String, String> entry2 = (Map.Entry<String, String>) e2;
if (entry1.getKey() == null) {
return -1;
} else if (entry2.getKey() == null) {
return 1;
} else {
return entry1.getKey().compareToIgnoreCase(entry2.getKey());
}
}
}
});
}
}
}
protected static class NameColumnLabelProvider extends ColumnLabelProvider {
@Override
public String getText(Object element) {
@SuppressWarnings("unchecked")
Map.Entry<String, String> entry = (Map.Entry<String, String>) element;
return entry.getKey();
}
}
protected static class ValueColumnLabelProvider extends ColumnLabelProvider {
@Override
public String getText(Object element) {
@SuppressWarnings("unchecked")
Map.Entry<String, String> entry = (Map.Entry<String, String>) element;
return entry.getValue();
}
}
protected abstract class AbstractTextEditingSupport extends EditingSupport {
protected TableViewer viewer;
protected CellEditor editor;
public AbstractTextEditingSupport(TableViewer viewer) {
super(viewer);
this.viewer = viewer;
editor = new TextCellEditor(viewer.getTable());
}
@Override
protected CellEditor getCellEditor(Object element) {
return editor;
}
@Override
protected boolean canEdit(Object element) {
return true;
}
@Override
protected final void setValue(Object element, Object value) {
// Perform value change
doSetValue(element, value);
// Notify listeners to enclosing group
notifyGroupChanged(null);
}
protected abstract void doSetValue(Object element, Object value);
}
protected class NameEditingSupport extends AbstractTextEditingSupport {
public NameEditingSupport(TableViewer viewer) {
super(viewer);
}
@Override
@SuppressWarnings("unchecked")
protected Object getValue(Object element) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) element;
return entry.getKey();
}
@Override
@SuppressWarnings("unchecked")
protected void doSetValue(Object element, Object value) {
Map.Entry<String, String> oldEntry = (Map.Entry<String, String>) element;
final String newName = String.valueOf(value);
Map<String, String> entries = (Map<String, String>) viewer.getInput();
entries.remove(oldEntry.getKey());
entries.put(newName, oldEntry.getValue());
viewer.refresh();
/*
* Usability tweak: Start editing of an entry's value when done with the editing of its name; this avoids
* unnecessary mouse clicks in between and facilitates the localization of the entry when it "jumps" at
* another table position due to automatic sorting of the entries based on their names
*/
/*
* !! Important Note !! Make sure that editing of the entry's value is started after a little delay only so
* that the editing of the entry's name can be fully completed before. Otherwise, the editing of the entry's
* value would be started anyway but the resulting new value wouldn't be saved and the old value would
* remain in place and reappear in the table cell when done with the editing.
*/
viewer.getControl().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
Entry<String, String> newEntry = findEntry(newName);
nameValueViewer.editElement(newEntry, 1);
}
}
});
}
}
protected class ValueEditingSupport extends AbstractTextEditingSupport {
public ValueEditingSupport(TableViewer viewer) {
super(viewer);
}
@Override
@SuppressWarnings("unchecked")
protected Object getValue(Object element) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) element;
return entry.getValue();
}
@Override
@SuppressWarnings("unchecked")
protected void doSetValue(Object element, Object value) {
String newName = String.valueOf(value);
Map.Entry<String, String> entry = (Map.Entry<String, String>) element;
entry.setValue(newName);
viewer.update(element, null);
}
}
protected TableViewer nameValueViewer;
protected Button addButton;
protected Button removeButton;
public NameValueTableGroup(String groupName) {
super(groupName);
}
public NameValueTableGroup(String groupName, IDialogSettings dialogSettings) {
super(groupName, dialogSettings);
}
@Override
protected void doCreateContent(Composite parent, int numColumns) {
// Create and parent layout with 2 column
// TODO Consider to remove numColumns parameters from createContents()/doCreateContents and make it a
// responsibility of the group implementations to determine how many columns they want to provide in their
// layout (as it is done right here already)
parent.setLayout(new GridLayout(2, false));
// Create name/value table area
createTableArea(parent);
// Create add/remove button area
createButtonArea(parent);
// Trigger update of add/remove button enablement
handleTableSelectionChanged(StructuredSelection.EMPTY);
}
protected void createTableArea(Composite parent) {
// Create table composite
Composite tableComposite = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
tableComposite.setFont(parent.getFont());
// Create table and table viewer
nameValueViewer = new TableViewer(tableComposite, getTableStyle());
Table nameValueTable = nameValueViewer.getTable();
GridDataFactory.fillDefaults().applyTo(nameValueTable);
nameValueTable.setFont(parent.getFont());
nameValueTable.setHeaderVisible(true);
nameValueTable.setLinesVisible(true);
nameValueViewer.setContentProvider(new NameValueViewerContentProvider());
nameValueViewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
handleTableSelectionChanged(event.getSelection());
}
});
// Create table columns
TableViewerColumn nameColumn = new TableViewerColumn(nameValueViewer, SWT.NONE);
nameColumn.getColumn().setText(Messages.column_name_label);
nameColumn.setLabelProvider(new NameColumnLabelProvider());
nameColumn.setEditingSupport(new NameEditingSupport(nameValueViewer));
TableViewerColumn valueColumn = new TableViewerColumn(nameValueViewer, SWT.NONE);
valueColumn.getColumn().setText(Messages.column_value_label);
valueColumn.setLabelProvider(new ValueColumnLabelProvider());
valueColumn.setEditingSupport(new ValueEditingSupport(nameValueViewer));
// Create table column layout
TableColumnLayout tableColumnLayout = new TableColumnLayout(true);
PixelConverter pixelConverter = new PixelConverter(parent.getFont());
tableColumnLayout.setColumnData(nameColumn.getColumn(), new ColumnWeightData(1, pixelConverter.convertWidthInCharsToPixels(20)));
tableColumnLayout.setColumnData(valueColumn.getColumn(), new ColumnWeightData(2, pixelConverter.convertWidthInCharsToPixels(20)));
tableComposite.setLayout(tableColumnLayout);
}
protected int getTableStyle() {
return SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION;
}
protected void createButtonArea(Composite parent) {
// Create button composite
Composite buttonComposite = new Composite(parent, SWT.NONE);
GridDataFactory.swtDefaults().align(GridData.END, GridData.BEGINNING).applyTo(buttonComposite);
buttonComposite.setFont(parent.getFont());
buttonComposite.setLayout(new GridLayout(1, false));
// Create buttons
addButton = new Button(buttonComposite, SWT.PUSH);
GridDataFactory.swtDefaults().align(GridData.FILL, GridData.BEGINNING).hint(computeButtonHint(addButton)).applyTo(addButton);
addButton.setFont(parent.getFont());
addButton.setText(Messages.button_add_label);
addButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
handleAddButtonSelected();
}
});
removeButton = new Button(buttonComposite, SWT.PUSH);
GridDataFactory.swtDefaults().align(GridData.FILL, GridData.BEGINNING).hint(computeButtonHint(removeButton)).applyTo(removeButton);
removeButton.setFont(parent.getFont());
removeButton.setText(Messages.button_remove_label);
removeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
handleRemoveButtonSelected();
}
});
}
protected Point computeButtonHint(Button button) {
PixelConverter converter = new PixelConverter(button);
int dialogWidthHint = converter.convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
Point preferredSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
return new Point(Math.max(dialogWidthHint, preferredSize.x), preferredSize.y);
}
/**
* Responds to a selection changed event in the name/value table
*
* @param selection
* the selection
*/
protected void handleTableSelectionChanged(ISelection selection) {
/*
* Usability tweak: Make sure that Remove button gets only enabled when one or multiple name/value entries have
* been selected
*/
int size = ((IStructuredSelection) selection).size();
removeButton.setEnabled(size > 0);
/*
* Usability tweak: Make sure that Add button remains disabled when a new name/value entry has been added but
* its name has not yet been changed away from its default
*/
@SuppressWarnings("unchecked")
Map<String, String> entries = (Map<String, String>) nameValueViewer.getInput();
addButton.setEnabled(entries != null && !entries.containsKey(DEFAULT_NAME));
}
/**
* Adds a new name/value entry to the table.
*/
protected void handleAddButtonSelected() {
// Add new name/value entry using default values
addEntry(DEFAULT_NAME, Messages.cell_value_default);
/*
* Usability tweak: Select newly added name/value entry
*/
Entry<String, String> entry = findEntry(DEFAULT_NAME);
nameValueViewer.setSelection(new StructuredSelection(entry));
}
/**
* Removes the selected name/value entry from the table.
*/
@SuppressWarnings("unchecked")
protected void handleRemoveButtonSelected() {
Assert.isNotNull(nameValueViewer);
int oldSelectionIdx = nameValueViewer.getTable().getSelectionIndex();
// Remove selected name/value entries
Map<String, String> entries = (Map<String, String>) nameValueViewer.getInput();
for (Object selected : ((IStructuredSelection) nameValueViewer.getSelection()).toList()) {
Map.Entry<String, String> entry = (Map.Entry<String, String>) selected;
entries.remove(entry.getKey());
}
nameValueViewer.refresh();
/*
* Usability tweak: Select name/value entry next to remove name/value entries
*/
int newSelectionIdx = oldSelectionIdx < entries.size() ? oldSelectionIdx : entries.size() - 1;
if (newSelectionIdx != -1) {
TableItem newSelected = nameValueViewer.getTable().getItem(newSelectionIdx);
nameValueViewer.setSelection(new StructuredSelection(newSelected.getData()));
}
}
public void setInput(Map<String, String> entries) {
Assert.isNotNull(nameValueViewer);
// Set new name/value entries as viewer input
nameValueViewer.setInput(entries);
// Trigger update of add/remove button enablement
handleTableSelectionChanged(StructuredSelection.EMPTY);
}
@SuppressWarnings("unchecked")
public Map<String, String> getInput() {
Assert.isNotNull(nameValueViewer);
// Return current name/value entries by retrieving viewer input
return (Map<String, String>) nameValueViewer.getInput();
}
public void addEntry(String name, String value) {
Assert.isNotNull(nameValueViewer);
@SuppressWarnings("unchecked")
Map<String, String> entries = (Map<String, String>) nameValueViewer.getInput();
entries.put(name, value);
Entry<String, String> entry = findEntry(name);
nameValueViewer.add(entry);
}
protected Map.Entry<String, String> findEntry(String name) {
Assert.isNotNull(nameValueViewer);
@SuppressWarnings("unchecked")
Map<String, String> entries = (Map<String, String>) nameValueViewer.getInput();
for (Map.Entry<String, String> entry : entries.entrySet()) {
if (name.equals(entry.getKey())) {
return entry;
}
}
return null;
}
}