/** * Copyright 2014 SAP AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.spotter.eclipse.ui.editors; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.swt.SWT; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; import org.lpe.common.config.ConfigParameterDescription; import org.lpe.common.util.LpeSupportedTypes; import org.spotter.eclipse.ui.listeners.TextEditorErrorListener; import org.spotter.eclipse.ui.model.AbstractPropertyItem; import org.spotter.eclipse.ui.model.IExtensionItem; import org.spotter.eclipse.ui.viewers.PropertiesGroupViewer; /** * A class implementing editing support for properties. The cell editor returned * by this editing support depends on the type of the property in the cell. * * @author Denis Knoepfle * */ public final class PropertiesEditingSupport extends EditingSupport { /** * Class that handles the cycling activation of cell editors within a table * viewer during traversal when cell editing is active. This listener should * be added to the control of the corresponding cell editor. */ private class ActivationTraverser implements TraverseListener { /** * The cell editor that should be activated. */ protected final CellEditor cellEditor; /** * Creates an activation traverser for the given cell editor. * * @param cellEditor * the cell editor that should be activated */ public ActivationTraverser(CellEditor cellEditor) { this.cellEditor = cellEditor; } /** * Performs the apply and deactivation mechanism of the cell editor. */ protected void performApplyAndDeactivate() { try { Method m = cellEditor.getClass().getSuperclass() .getDeclaredMethod("fireApplyEditorValue", (Class<?>[]) new Class[0]); m.setAccessible(true); m.invoke(cellEditor, (Object[]) null); cellEditor.deactivate(); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException("Could not apply editor value", e); } } @Override public void keyTraversed(TraverseEvent e) { if (e.detail != SWT.TRAVERSE_TAB_NEXT && e.detail != SWT.TRAVERSE_TAB_PREVIOUS) { return; } performApplyAndDeactivate(); e.doit = false; TableViewer tableViewer = PropertiesEditingSupport.this.propertiesViewer.getTableViewer(); Table table = tableViewer.getTable(); if (table.getItemCount() <= 1) { return; } if (!tableViewer.getSelection().isEmpty()) { int index = table.getSelectionIndex(); if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { // backwards index = (index - 1 + table.getItemCount()) % table.getItemCount(); Object element = tableViewer.getElementAt(index); table.setSelection(index); tableViewer.setSelection(new StructuredSelection(element)); tableViewer.editElement(element, table.getColumnCount() - 1); } else { // forwards index = (index + 1) % table.getItemCount(); Object element = tableViewer.getElementAt(index); table.setSelection(index); tableViewer.setSelection(new StructuredSelection(element)); tableViewer.editElement(element, table.getColumnCount() - 1); } } } } private class ComboActivationTraverser extends ActivationTraverser { public ComboActivationTraverser(CellEditor cellEditor) { super(cellEditor); } @Override protected void performApplyAndDeactivate() { try { Method m = cellEditor.getClass().getDeclaredMethod("applyAndDeactivate", (Class<?>[]) new Class[0]); m.setAccessible(true); m.invoke(cellEditor, (Object[]) null); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) { throw new RuntimeException("Could not apply editor value", e1); } } } private static final String[] BOOLEAN_VALUES = { Boolean.TRUE.toString(), Boolean.FALSE.toString() }; private static final int COMBO_ACTIVATION_STYLE = ComboBoxCellEditor.DROP_DOWN_ON_MOUSE_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_KEY_ACTIVATION | ComboBoxCellEditor.DROP_DOWN_ON_PROGRAMMATIC_ACTIVATION; private AbstractSpotterEditor editor; private PropertiesGroupViewer propertiesViewer; // default text editor private TextCellEditor cellDefaultTextEditor; // editor for numbers private TextCellEditor cellNumberEditor; // editor for booleans private CustomComboBoxCellEditor cellBooleanEditor; // editor for parameters that are no sets but have options available private CustomComboBoxCellEditor cellComboBoxEditor; // custom editor for special purposes like directories, files and sets private CustomDialogCellEditor cellCustomDialogEditor; /** * Creates an editing support for the given operating viewer. * * @param operatingViewer * the viewer this editing support works for * @param editor * the editor which is operated in * @param propertiesViewer * the properties group viewer that contains the operating viewer */ public PropertiesEditingSupport(ColumnViewer operatingViewer, AbstractSpotterEditor editor, PropertiesGroupViewer propertiesViewer) { super(operatingViewer); this.editor = editor; this.propertiesViewer = propertiesViewer; Composite parent = (Composite) getViewer().getControl(); cellDefaultTextEditor = new TextCellEditor(parent); cellDefaultTextEditor.getControl().addTraverseListener(new ActivationTraverser(cellDefaultTextEditor)); cellNumberEditor = new TextCellEditor(parent); ControlDecoration decor = new ControlDecoration(cellNumberEditor.getControl(), SWT.LEFT | SWT.TOP); cellNumberEditor.addListener(new TextEditorErrorListener(cellNumberEditor, decor)); cellNumberEditor.getControl().addTraverseListener(new ActivationTraverser(cellNumberEditor)); cellBooleanEditor = new CustomComboBoxCellEditor(parent, BOOLEAN_VALUES, SWT.DROP_DOWN | SWT.READ_ONLY); cellBooleanEditor.setActivationStyle(COMBO_ACTIVATION_STYLE); cellBooleanEditor.getControl().addTraverseListener(new ComboActivationTraverser(cellBooleanEditor)); cellComboBoxEditor = new CustomComboBoxCellEditor(parent, new String[0], SWT.DROP_DOWN | SWT.READ_ONLY); cellComboBoxEditor.setActivationStyle(COMBO_ACTIVATION_STYLE); cellComboBoxEditor.getControl().addTraverseListener(new ComboActivationTraverser(cellComboBoxEditor)); cellCustomDialogEditor = new CustomDialogCellEditor(parent); cellCustomDialogEditor.getControl().addTraverseListener(new ActivationTraverser(cellCustomDialogEditor)); } @Override protected CellEditor getCellEditor(Object element) { CellEditor cellEditor = null; if (element instanceof AbstractPropertyItem) { AbstractPropertyItem tableItem = (AbstractPropertyItem) element; ConfigParameterDescription desc = tableItem.getConfigParameterDescription(); LpeSupportedTypes type = desc.getType(); String lowerBoundary = desc.getLowerBoundary(); String upperBoundary = desc.getUpperBoundary(); switch (type) { case Integer: case Long: case Float: case Double: cellNumberEditor.setValidator(new NumberValidator(type, lowerBoundary, upperBoundary)); cellEditor = cellNumberEditor; break; case Boolean: cellEditor = cellBooleanEditor; break; case String: if (desc.isASet() || desc.isADirectory() || desc.isAFile()) { cellCustomDialogEditor.setConfigParameterDescription(desc); cellEditor = cellCustomDialogEditor; } else if (desc.optionsAvailable()) { Set<String> availableOptions = desc.getOptions(); cellComboBoxEditor.setItems(availableOptions.toArray(new String[availableOptions.size()])); cellEditor = cellComboBoxEditor; } else { cellEditor = cellDefaultTextEditor; } break; default: cellEditor = cellDefaultTextEditor; break; } } return cellEditor; } @Override protected boolean canEdit(Object element) { return true; } @Override protected Object getValue(Object element) { if (element instanceof AbstractPropertyItem) { AbstractPropertyItem tableItem = (AbstractPropertyItem) element; String value = tableItem.getValue(); ConfigParameterDescription desc = tableItem.getConfigParameterDescription(); if (desc.getType() == LpeSupportedTypes.Boolean) { return BOOLEAN_VALUES[0].equals(value) ? 0 : 1; } else if (desc.getType() == LpeSupportedTypes.String && !desc.isASet() && desc.optionsAvailable()) { return getOptionIndex(value, desc); } else { return value; } } return null; } @Override protected void setValue(Object element, Object value) { if (value != null && element instanceof AbstractPropertyItem) { AbstractPropertyItem tableItem = (AbstractPropertyItem) element; String newValue; ConfigParameterDescription desc = tableItem.getConfigParameterDescription(); if (desc.getType() == LpeSupportedTypes.Boolean) { newValue = (String) BOOLEAN_VALUES[(Integer) value]; } else if (desc.getType() == LpeSupportedTypes.String && !desc.isASet() && desc.optionsAvailable()) { newValue = (String) desc.getOptions().toArray()[(Integer) value]; } else { newValue = (String) value; } if (tableItem.getValue() == null || !tableItem.getValue().equals(newValue)) { tableItem.updateValue(newValue); IExtensionItem inputModel = propertiesViewer.getInputModel(); inputModel.propertyDirty(tableItem); // we can assume that at this point inputModel is never null if (desc.isMandatory()) { inputModel.updateConnectionStatus(); } else { inputModel.fireItemAppearanceChanged(); } editor.markDirty(); } } } /** * Retrieves the index of the value string in the descriptions options set. * * @param value * the value as string * @param desc * the description to look in for the index * @return the index of the option in the options set or 0 if no match found */ private Integer getOptionIndex(String value, ConfigParameterDescription desc) { int index = 0; for (String option : desc.getOptions()) { if (option.equals(value)) { return Integer.valueOf(index); } index++; } return Integer.valueOf(0); } }