package au.gov.ga.earthsci.core.preferences; import java.util.Arrays; import java.util.List; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.preference.FieldEditor; 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.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.TableColumn; import org.eclipse.swt.widgets.TableItem; /** * A {@link FieldEditor} that provides a tabular display of items from a list. * These items can be selected using checkboxes, and the list of selected items * will be bound to the named preference. * <p/> * List items can be rendered as table items using the {@link ITableItemCreator} * interface. The default implementation uses the list item's * {@link Object#toString()} method. * <p/> * List items are stored in the preferences store as a comma separated list * * @param E * The type of object that will be selected using this field editor * * @author James Navin (james.navin@ga.gov.au) */ public class MultiSelectTableListFieldEditor<E> extends FieldEditor { private static final String SOURCE_OBJECT_KEY = "au.gov.ga.earthsci.application.preferences.fieldeditor.MultiSelectTableListFieldEditor.sourceItem"; //$NON-NLS-1$ private static final String NONE_SELECTED = "none"; //$NON-NLS-1$ private final ITableItemCreator<E> DEFAULT_TABLE_ITEM_CREATOR = new ITableItemCreator<E>() { @Override public TableItem createTableItem(Table table, E object) { if (object == null) { return null; } TableItem ti = new TableItem(table, SWT.NONE); ti.setText(new String[] { object.toString() }); return ti; } }; /** The table control */ private Table table; /** Table column names. May be <code>null</code>. */ private String[] columnNames; /** The list backing this editor */ private List<E> backingList; /** The item creator to use for converting list items to table rows */ private ITableItemCreator<E> tableItemCreator = DEFAULT_TABLE_ITEM_CREATOR; /** The item serialiser to use for converting selected items to strings */ private IItemSerializer<E> itemSerialiser; /** * Whether to default to all checked, in the absence of overriding * preferences */ private boolean defaultAllChecked = true; /** Composite containing select all/none buttons */ private Composite buttonBox; /** Select all elements in the list */ private Button selectAllButton; /** Deselect all elements in the list */ private Button selectNoneButton; /** * Creates a table backed by the provided list. The list item toString() * method will be used to render values. */ public MultiSelectTableListFieldEditor(String keyName, List<E> backingList, IItemSerializer<E> itemSerializer, Composite parent) { this(keyName, backingList, null, null, itemSerializer, parent); } /** * Creates a table backed by the provided list. The list item toString() * method will be used to render values. */ public MultiSelectTableListFieldEditor(String keyName, List<E> backingList, String[] columnNames, ITableItemCreator<E> tableItemCreator, IItemSerializer<E> itemSerializer, Composite parent) { setPreferenceName(keyName); this.backingList = backingList; this.columnNames = columnNames == null ? null : Arrays.copyOf(columnNames, columnNames.length); this.tableItemCreator = tableItemCreator == null ? DEFAULT_TABLE_ITEM_CREATOR : tableItemCreator; this.itemSerialiser = itemSerializer; doFillIntoGrid(parent, 1); } @Override protected void adjustForNumColumns(int numColumns) { GridData gd = (GridData) table.getLayoutData(); gd.horizontalSpan = numColumns; gd.grabExcessHorizontalSpace = gd.horizontalSpan == 1; } @Override protected void doFillIntoGrid(Composite parent, int numColumns) { getTableControl(parent); GridData gd = new GridData(); gd.horizontalSpan = numColumns + 1; gd.horizontalAlignment = GridData.FILL; gd.grabExcessHorizontalSpace = true; table.setLayoutData(gd); buttonBox = getButtonBoxControl(parent); gd = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_CENTER); gd.horizontalAlignment = SWT.CENTER; buttonBox.setLayoutData(gd); } @Override protected void doLoad() { if (table != null) { if (!getPreferenceStore().contains(getPreferenceName()) && defaultAllChecked) { selectAll(); } else { loadFromString(getPreferenceStore().getString(getPreferenceName())); } } } @Override protected void doLoadDefault() { if (table != null) { String defaultString = getPreferenceStore().getDefaultString(getPreferenceName()); if (!defaultString.isEmpty()) { loadFromString(defaultString); } else if (defaultAllChecked) { selectAll(); } } } private void selectAll() { for (TableItem i : table.getItems()) { i.setChecked(true); } } private void selectNone() { for (TableItem i : table.getItems()) { i.setChecked(false); } } private void loadFromString(String csv) { // Special case if (NONE_SELECTED.equals(csv)) { selectNone(); return; } String[] selected = csv.split(","); //$NON-NLS-1$ for (String s : selected) { E object = itemSerialiser.fromString(s); if (object == null) { continue; } for (TableItem i : table.getItems()) { if (object.equals(i.getData(SOURCE_OBJECT_KEY))) { i.setChecked(true); } } } } @SuppressWarnings("unchecked") @Override protected void doStore() { StringBuffer result = new StringBuffer(); TableItem[] items = table.getItems(); int checkedItemCount = 0; for (int i = 0; i < items.length; i++) { if (!items[i].getChecked()) { continue; } if (checkedItemCount != 0) { result.append(','); } result.append(itemSerialiser.asString((E) items[i].getData(SOURCE_OBJECT_KEY))); checkedItemCount++; } try { // If the preference is stored as an empty string it is removed from the store. // In this case, "None selected" is different from "not set". Hence the special keyword. if (checkedItemCount == 0) { result.append(NONE_SELECTED); } getPreferenceStore().setValue(getPreferenceName(), result.toString()); } catch (Exception e) { e.printStackTrace(); } } @Override public int getNumberOfControls() { return 1; } @Override public void setEnabled(boolean enabled, Composite parent) { super.setEnabled(enabled, parent); table.setEnabled(enabled); } /** * @return The table control for this editor, creating it if needed. */ private Table getTableControl(Composite parent) { if (table != null) { return table; } table = new Table(parent, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.CHECK | SWT.MULTI); table.setLinesVisible(true); table.setHeaderVisible(columnNames != null); for (String columnName : columnNames) { TableColumn column = new TableColumn(table, SWT.NONE); column.setText(columnName); } for (E object : backingList) { TableItem tableItem = tableItemCreator.createTableItem(table, object); tableItem.setData(SOURCE_OBJECT_KEY, object); } for (TableColumn column : table.getColumns()) { column.pack(); } return table; } /** * Returns this field editor's button box containing the select all / none * buttons */ private Composite getButtonBoxControl(Composite parent) { if (buttonBox == null) { buttonBox = new Composite(parent, SWT.NULL); GridLayout layout = new GridLayout(); layout.numColumns = 2; layout.marginWidth = 0; buttonBox.setLayout(layout); createButtons(buttonBox); buttonBox.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent event) { selectAllButton = null; selectNoneButton = null; buttonBox = null; } }); } else { checkParent(buttonBox, parent); } return buttonBox; } /** * 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) { selectAllButton = createPushButton(box, "All"); selectAllButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { selectAll(); } }); selectNoneButton = createPushButton(box, "None"); selectNoneButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { selectNone(); } }); } /** * 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 label) { Button button = new Button(parent, SWT.PUSH); button.setText(label); 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; } /** * A strategy interface used to create a table item from the provided * object. * <p/> * The created table items should have column text that matches any column * headers. */ public static interface ITableItemCreator<E> { TableItem createTableItem(Table parent, E object); } /** * A strategy interface used to serialize list items into a comma-separated * list stored in the preferences store. */ public static interface IItemSerializer<E> { String asString(E object); E fromString(String string); } }