package org.openlca.app.viewers.table.modify; import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckboxCellEditor; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Item; import org.openlca.app.components.DialogCellEditor; import org.openlca.app.viewers.table.modify.ICellModifier.CellEditingType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides an easy and type safe way to add cell editors to a table viewer. It * is important that the viewer is configured with column properties that are * used for the binding of cell modifiers. Thus, you have to call * <code>viewer.setColumnProperties(aStringArray)</code> <b>before</b> you * create the modify support. */ public class ModifySupport<T> { private Logger log = LoggerFactory.getLogger(getClass()); private Map<String, ICellModifier<T>> cellModifiers; private CellEditor[] editors; private String[] columnProperties; private TableViewer viewer; public ModifySupport(TableViewer viewer) { this.viewer = viewer; initCellEditors(); } private void initCellEditors() { columnProperties = (String[]) viewer.getColumnProperties(); viewer.setCellModifier(new CellModifier()); editors = new CellEditor[columnProperties.length]; this.cellModifiers = new HashMap<>(); viewer.setCellEditors(editors); } /** * Binds a dialog cell editor to the given property. It is assumed that the * editor directly operates on the values in the respective table and that * the values are set in the respective editor. */ public void bind(String property, DialogCellEditor dialog) { int idx = findIndex(property); if (idx == -1) return; editors[idx] = dialog; } /** * Binds the given getter and setter to the given table property. Null * values for the getter are allowed. The setter is only called if text was * changed. */ public void bind(String property, Getter<T> getter, Setter<T> setter) { TextCellModifier<T> modifier = new TextCellModifier<T>() { @Override protected String getText(T element) { if (getter == null) return ""; String val = getter.getText(element); return val == null ? "" : val; } @Override protected void setText(T element, String text) { if (getter == null || setter == null) return; String oldVal = getter.getText(element); if (Objects.equals(oldVal, text)) return; setter.setText(element, text); } }; bind(property, modifier); } /** * Binds the given modifier to the given property of the viewer. */ public void bind(String property, ICellModifier<T> modifier) { int index = findIndex(property); if (index == -1) return; cellModifiers.put(columnProperties[index], modifier); setEditor(modifier, index); } private int findIndex(String property) { int index = -1; for (int i = 0; i < columnProperties.length; i++) { if (Objects.equals(columnProperties[i], property)) { index = i; break; } } if (index == -1) log.warn("Property {} is not a column property", property); return index; } private void setEditor(ICellModifier<T> modifier, int index) { switch (modifier.getCellEditingType()) { case TEXTBOX: if (modifier.getStyle() != SWT.NONE) editors[index] = new TextCellEditor(viewer.getTable(), modifier.getStyle()); else editors[index] = new TextCellEditor(viewer.getTable()); break; case COMBOBOX: editors[index] = new ComboEditor(viewer.getTable(), new String[0]); break; case CHECKBOX: if (modifier.getStyle() != SWT.NONE) editors[index] = new CheckboxCellEditor(viewer.getTable(), modifier.getStyle()); else editors[index] = new CheckboxCellEditor(viewer.getTable()); break; default: break; } } private CellEditor getCellEditor(String property) { int idx = findIndex(property); if (idx == -1) return null; return editors[idx]; } private void refresh(T value) { for (String property : cellModifiers.keySet()) { ICellModifier<T> modifier = cellModifiers.get(property); if (modifier.getCellEditingType() == CellEditingType.COMBOBOX) { ((ComboBoxCellEditor) getCellEditor(property)) .setItems(modifier.getStringValues(value)); } } } private class CellModifier implements org.eclipse.jface.viewers.ICellModifier { @Override @SuppressWarnings("unchecked") public boolean canModify(Object element, String property) { if (element == null || property == null) return false; if (cellModifiers.containsKey(property)) { ICellModifier<T> modifier = cellModifiers.get(property); return modifier != null && modifier.canModify((T) element); } CellEditor editor = getCellEditor(property); return editor instanceof DialogCellEditor; } @Override public Object getValue(Object element, String property) { ICellModifier<T> modifier = cellModifiers.get(property); if (modifier != null) return getModifierValue(element, modifier); CellEditor editor = getCellEditor(property); if (editor != null) return element; return null; } @SuppressWarnings("unchecked") private Object getModifierValue(Object element, ICellModifier<T> modifier) { T elem = (T) element; Object value = modifier.getValue(elem); switch (modifier.getCellEditingType()) { case TEXTBOX: return value != null ? value.toString() : ""; case COMBOBOX: return getComboIndex(modifier, elem, value); case CHECKBOX: if (value instanceof Boolean) return value; else return false; default: return element; } } private Object getComboIndex(ICellModifier<T> modifier, T elem, Object value) { refresh(elem); Object[] values = modifier.getValues(elem); if (values == null) return -1; for (int i = 0; i < values.length; i++) { if (Objects.equals(values[i], value)) return i; } return -1; } @Override public void modify(Object element, String property, Object value) { if (element instanceof Item) element = ((Item) element).getData(); ICellModifier<T> modifier = cellModifiers.get(property); if (modifier != null) { T elem = setModifierValue(element, value, modifier); refresh(elem); } if (modifier != null && modifier.affectsOtherElements()) viewer.refresh(true); else viewer.refresh(element, true); } @SuppressWarnings("unchecked") private T setModifierValue(Object element, Object value, ICellModifier<T> modifier) { T elem = (T) element; switch (modifier.getCellEditingType()) { case TEXTBOX: modifier.modify(elem, value.toString()); break; case COMBOBOX: setComboValue(modifier, elem, value); break; case CHECKBOX: modifier.modify(elem, value); break; default: break; } return elem; } private void setComboValue(ICellModifier<T> modifier, T elem, Object value) { if (value instanceof Integer) { int index = (int) value; if (index == -1) return; Object[] values = modifier.getValues(elem); if (values == null || index >= values.length) return; modifier.modify(elem, values[index]); } } } /** * Overwrites the getValue method from the JFace combo editor so that also * entered strings that are elements of the respective combo-items are * accepted as user input. */ private class ComboEditor extends ComboBoxCellEditor { public ComboEditor(Composite parent, String[] items) { super(parent, items); } @Override protected Object doGetValue() { Object val = super.doGetValue(); if (!(val instanceof Integer)) return val; int idx = (Integer) val; if (idx > -1) return new Integer(idx); String cellText = getCellText(); return getIndexForText(cellText); } private String getCellText() { Control control = getControl(); if (!(control instanceof CCombo)) return null; CCombo combo = (CCombo) getControl(); return combo.getText(); } private Integer getIndexForText(String cellText) { if (cellText == null) return new Integer(-1); String term = cellText.trim(); String[] items = getItems(); for (int i = 0; i < items.length; i++) { if (term.equals(items[i])) return new Integer(i); } return new Integer(-1); } } }