package org.marketcetera.photon.ui;
import java.text.Collator;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Widget;
import org.marketcetera.core.MMapEntry;
import org.marketcetera.photon.Messages;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.gui.AdvancedTableFormat;
import ca.odell.glazedlists.gui.WritableTableFormat;
public abstract class MapEditor extends FieldEditor implements Messages
{
/**
* The list widget; <code>null</code> if none (before creation or after
* disposal).
*/
private Table table;
private IndexedTableViewer tableViewer;
/**
* The button box containing the Add, Remove, Up, and Down buttons;
* <code>null</code> if none (before creation or after disposal).
*/
private Composite buttonBox;
/**
* The Add button.
*/
private Button addButton;
/**
* The Remove button.
*/
private Button removeButton;
/**
* The Up button.
*/
private Button upButton;
/**
* The Down button.
*/
private Button downButton;
/**
* The selection listener.
*/
private SelectionListener selectionListener;
EventList<Map.Entry<String, String>> entries;
/**
* Creates a new list field editor
*/
protected MapEditor() {
}
/**
* Creates a list field editor.
*
* @param name
* the name of the preference this field editor works on
* @param labelText
* the label text of the field editor
* @param parent
* the parent of the field edMapFieldEditortrol
*/
protected MapEditor(String name, String labelText, Composite parent) {
init(name, labelText);
createControl(parent);
}
protected boolean isDuplicateKeyAllowed() {
return true;
}
/**
* Notifies that the Add button has been pressed.
*/
private void addPressed() {
setPresentsDefaultValue(false);
Entry<String, String> input = getNewInputObject();
if (input != null) {
if (isDuplicateKeyAllowed() || !hasEntryKey(input.getKey())) {
int index = table.getSelectionIndex();
if (index >= 0) {
entries.add(index + 1, input);
} else {
entries.add(0, input);
}
selectionChanged();
}
}
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
protected void adjustForNumColumns(int numColumns) {
Control control = getLabelControl();
((GridData) control.getLayoutData()).horizontalSpan = numColumns;
((GridData) table.getLayoutData()).horizontalSpan = numColumns - 1;
}
/**
* Creates the Add, Remove, Up, and Down button in the given button box.
*
* @param box
* the box for the buttons
*/
private void createButtons(Composite box) {
addButton = createPushButton(box, "Add");//$NON-NLS-1$
removeButton = createPushButton(box, "Remove");//$NON-NLS-1$
if (includeUpDownButtons()) {
upButton = createPushButton(box, "Up");//$NON-NLS-1$
downButton = createPushButton(box, "Down");//$NON-NLS-1$
}
}
/**
* Combines the given list of items into a single string. This method is the
* converse of <code>parseString</code>.
* <p>
* Subclasses must implement this method.
* </p>
*
* @param entries
* the list of items
* @return the combined string
* @see #parseString
*/
protected abstract String createMap(
EventList<Map.Entry<String, String>> entries);
/**
* Indicates if the up and down buttons should be included or omitted from
* the dialog.
*
* @return a <code>boolean</code> value
*/
protected boolean includeUpDownButtons() {
return true;
}
/**
* Helper method to create a push button.
*
* @param parent
* the parent control
* @param key
* the resource name used to supply the button's label text
* @return Button
*/
private Button createPushButton(Composite parent, String key) {
Button button = new Button(parent, SWT.PUSH);
button.setText(JFaceResources.getString(key));
button.setFont(parent.getFont());
GridData data = new GridData(GridData.FILL_HORIZONTAL);
int widthHint = convertHorizontalDLUsToPixels(button,
IDialogConstants.BUTTON_WIDTH);
data.widthHint = Math.max(widthHint,
button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
button.setLayoutData(data);
button.addSelectionListener(getSelectionListener());
return button;
}
/**
* Creates a selection listener.
*/
public void createSelectionListener() {
selectionListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
Widget widget = event.widget;
if (widget == addButton) {
addPressed();
} else if (widget == removeButton) {
removePressed();
} else if (widget == upButton) {
upPressed();
} else if (widget == downButton) {
downPressed();
} else if (widget == table) {
selectionChanged();
}
}
};
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
protected void doFillIntoGrid(Composite parent, int numColumns) {
Control control = getLabelControl(parent);
GridData gd = new GridData();
gd.horizontalSpan = numColumns;
control.setLayoutData(gd);
table = getTableControl(parent);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.verticalAlignment = GridData.FILL;
gd.horizontalSpan = numColumns - 1;
gd.grabExcessHorizontalSpace = true;
gd.grabExcessVerticalSpace = true;
table.setLayoutData(gd);
createTableViewer();
buttonBox = getButtonBoxControl(parent);
gd = new GridData();
gd.verticalAlignment = GridData.BEGINNING;
buttonBox.setLayoutData(gd);
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
protected void doLoad() {
if (table != null) {
String s = getPreferenceStore().getString(getPreferenceName());
entries = parseString(s);
tableViewer.setInput(entries);
}
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
protected void doLoadDefault() {
if (table != null) {
table.removeAll();
String s = getPreferenceStore().getDefaultString(
getPreferenceName());
entries = parseString(s);
tableViewer.setInput(entries);
}
}
private boolean hasEntryKey(String entryKey) {
if (entryKey == null || entries == null) {
return false;
}
for (Map.Entry<String, String> entry : entries) {
if (entry != null) {
String key = entry.getKey();
if (entryKey.equals(key)) {
return true;
}
}
}
return false;
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
@SuppressWarnings("unchecked")//$NON-NLS-1$
protected void doStore() {
EventList<Map.Entry<String, String>> items = (EventList<Map.Entry<String, String>>) tableViewer
.getInput();
String s = createMap(items);
if (s != null) {
getPreferenceStore().setValue(getPreferenceName(), s);
}
}
/**
* Notifies that the Down button has been pressed.
*/
private void downPressed() {
swap(false);
}
/**
* Returns this field editor's button box containing the Add, Remove, Up,
* and Down button.
*
* @param parent
* the parent control
* @return the button box
*/
public Composite getButtonBoxControl(Composite parent) {
if (buttonBox == null) {
buttonBox = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
buttonBox.setLayout(layout);
createButtons(buttonBox);
buttonBox.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
addButton = null;
removeButton = null;
upButton = null;
downButton = null;
buttonBox = null;
}
});
} else {
checkParent(buttonBox, parent);
}
selectionChanged();
return buttonBox;
}
/**
* Returns this field editor's list control.
*
* @param parent
* the parent control
* @return the list control
*/
public Table getTableControl(Composite parent) {
if (table == null) {
table = new Table(parent, SWT.BORDER | SWT.SINGLE | SWT.V_SCROLL
| SWT.H_SCROLL | SWT.FULL_SELECTION);
table.setHeaderVisible(false);
table.setFont(parent.getFont());
table.addSelectionListener(getSelectionListener());
table.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
table = null;
}
});
table.setHeaderVisible(true);
// 2nd column with task Description
TableColumn column;
column = new TableColumn(table, SWT.LEFT);
column.setText(KEY_LABEL.getText());
column.setWidth(100);
column = new TableColumn(table, SWT.LEFT);
column.setText(VALUE_LABEL.getText());
column.setWidth(100);
} else {
checkParent(table, parent);
}
return table;
}
/**
* Create the TableViewer
*/
private void createTableViewer() {
tableViewer = new IndexedTableViewer(table);
tableViewer.setUseHashlookup(true);
String[] columnNames = new String[] { KEY_LABEL.getText(),
VALUE_LABEL.getText() };
tableViewer.setColumnProperties(columnNames);
// Create the cell editors
CellEditor[] editors = new CellEditor[columnNames.length];
editors[0] = null;
editors[1] = new TextCellEditor(table);
// Assign the cell editors to the viewer
tableViewer.setCellEditors(editors);
// Set the cell modifier for the viewer
tableViewer.setCellModifier(new ICellModifier() {
@SuppressWarnings("unchecked")
@Override
public void modify(Object element, String property, Object value) {
if (element instanceof TableItem) {
if (property == VALUE_LABEL.getText()) {
String origValue = ((MMapEntry<String, String>) ((TableItem) element)
.getData()).getValue();
String newValue = (String) value;
if (newValue != null && !newValue.trim().isEmpty()
&& origValue.compareTo(newValue) != 0) {
((MMapEntry<String, String>) ((TableItem) element)
.getData()).setValue(newValue);
tableViewer.refresh(true, true);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public Object getValue(Object element, String property) {
if (property == VALUE_LABEL.getText()) {
return ((MMapEntry<String, String>) element).getValue();
}
return null;
}
@Override
public boolean canModify(Object element, String property) {
if (property == VALUE_LABEL.getText())
return true;
return false;
}
});
tableViewer
.setContentProvider(new EventListContentProvider<Map.Entry<String, String>>());
tableViewer.setLabelProvider(new MapEntryLabelProvider());
}
// public void elementChanged(MMapEntry<String, String> element) {
// tableViewer.update(element, null);
// }
protected ITableLabelProvider getTableLabelProvider() {
return new ITableLabelProvider() {
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
@SuppressWarnings("unchecked")//$NON-NLS-1$
public String getColumnText(Object element, int columnIndex) {
return (element instanceof Map.Entry) ? columnIndex == 1 ? ((Map.Entry<String, String>) element)
.getKey() : ((Map.Entry<String, String>) element)
.getValue()
: null;
}
public void addListener(ILabelProviderListener listener) {
}
public void dispose() {
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void removeListener(ILabelProviderListener listener) {
}
};
}
/**
* Creates and returns a new item for the list.
* <p>
* Subclasses must implement this method.
* </p>
*
* @return a new item
*/
protected abstract Entry<String, String> getNewInputObject();
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
public int getNumberOfControls() {
return 2;
}
/**
* Returns this field editor's selection listener. The listener is created
* if nessessary.
*
* @return the selection listener
*/
private SelectionListener getSelectionListener() {
if (selectionListener == null) {
createSelectionListener();
}
return selectionListener;
}
/**
* Returns this field editor's shell.
* <p>
* This method is internal to the framework; subclassers should not call
* this method.
* </p>
*
* @return the shell
*/
protected Shell getShell() {
if (addButton == null) {
return null;
}
return addButton.getShell();
}
/**
* Splits the given string into a list of strings. This method is the
* converse of <code>createList</code>.
* <p>
* Subclasses must implement this method.
* </p>
*
* @param stringList
* the string
* @return an array of <code>String</code>
*/
protected abstract EventList<Map.Entry<String, String>> parseString(
String stringList);
/**
* Notifies that the Remove button has been pressed.
*/
protected int removePressed() {
setPresentsDefaultValue(false);
int index = table.getSelectionIndex();
if (index >= 0) {
// table.remove(index);
entries.remove(index);
selectionChanged();
}
return index;
}
/**
* Notifies that the list selection has changed.
*/
private void selectionChanged() {
int index = table.getSelectionIndex();
int size = table.getItemCount();
removeButton.setEnabled(index >= 0);
if (includeUpDownButtons()) {
upButton.setEnabled(size > 1 && index > 0);
downButton.setEnabled(size > 1 && index >= 0 && index < size - 1);
}
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
public void setFocus() {
if (table != null) {
table.setFocus();
}
}
/**
* Moves the currently selected item up or down.
*
* @param up
* <code>true</code> if the item should move up, and
* <code>false</code> if it should move down
*/
@SuppressWarnings("unchecked")//selections are untyped //$NON-NLS-1$
private void swap(boolean up) {
setPresentsDefaultValue(false);
int index = table.getSelectionIndex();
int target = up ? index - 1 : index + 1;
if (index >= 0) {
TableItem[] selection = table.getSelection();
Entry<String, String> toReplace = (Entry<String, String>) selection[0]
.getData();
entries.remove(index);
entries.add(target, toReplace);
table.setSelection(target);
}
selectionChanged();
}
/**
* Notifies that the Up button has been pressed.
*/
private void upPressed() {
swap(true);
}
/*
* @see FieldEditor.setEnabled(boolean,Composite).
*/
public void setEnabled(boolean enabled, Composite parent) {
super.setEnabled(enabled, parent);
getTableControl(parent).setEnabled(enabled);
addButton.setEnabled(enabled);
removeButton.setEnabled(enabled);
if (includeUpDownButtons()) {
upButton.setEnabled(enabled);
downButton.setEnabled(enabled);
}
}
protected class MapTableFormat implements WritableTableFormat<Object>,
AdvancedTableFormat<Object> {
public static final int KEY_COLUMN = 0;
public static final int VALUE_COLUMN = 1;
public boolean isEditable(Object arg0, int arg1) {
return true;
}
@SuppressWarnings("unchecked")//$NON-NLS-1$
public Object setColumnValue(Object baseObject, Object editedValue,
int column) {
switch (column) {
case KEY_COLUMN:
return new MMapEntry<String, String>((String) editedValue,
((Map.Entry<String, String>) baseObject).getValue());
case VALUE_COLUMN:
((MMapEntry<String, String>) baseObject)
.setValue(((String) editedValue));
return baseObject;
default:
return null;
}
}
public int getColumnCount() {
return 2;
}
public String getColumnName(int column) {
switch (column) {
case KEY_COLUMN:
return KEY_LABEL.getText();
case VALUE_COLUMN:
return VALUE_LABEL.getText();
default:
return null;
}
}
@SuppressWarnings("unchecked")//$NON-NLS-1$
public Object getColumnValue(Object obj, int column) {
switch (column) {
case KEY_COLUMN:
return ((Map.Entry<String, String>) obj).getKey();
case VALUE_COLUMN:
return ((Map.Entry<String, String>) obj).getValue();
default:
return null;
}
}
public Class<?> getColumnClass(int arg0) {
return String.class;
}
public Comparator<?> getColumnComparator(int arg0) {
return Collator.getInstance();
}
}
}