/** * 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.viewers; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.layout.TreeColumnLayout; import org.eclipse.jface.util.LocalSelectionTransfer; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.window.ToolTip; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.Transfer; 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.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Tree; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.lpe.common.config.ConfigParameterDescription; import org.spotter.eclipse.ui.Activator; import org.spotter.eclipse.ui.ServiceClientWrapper; import org.spotter.eclipse.ui.actions.CopyExtensionAction; import org.spotter.eclipse.ui.actions.CutExtensionAction; import org.spotter.eclipse.ui.actions.DeleteExtensionAction; import org.spotter.eclipse.ui.actions.PasteExtensionAction; import org.spotter.eclipse.ui.dialogs.AddExtensionDialog; import org.spotter.eclipse.ui.dnd.ExtensionDragListener; import org.spotter.eclipse.ui.dnd.ExtensionDropListener; import org.spotter.eclipse.ui.editors.AbstractExtensionsEditor; import org.spotter.eclipse.ui.listeners.IItemChangedListener; import org.spotter.eclipse.ui.model.BasicEditorExtensionItemFactory; import org.spotter.eclipse.ui.model.ExtensionMetaobject; import org.spotter.eclipse.ui.model.IExtensionItem; import org.spotter.eclipse.ui.model.IExtensionItemFactory; import org.spotter.eclipse.ui.model.xml.IModelWrapper; import org.spotter.eclipse.ui.providers.SpotterExtensionsContentProvider; import org.spotter.eclipse.ui.providers.SpotterExtensionsLabelProvider; import org.spotter.eclipse.ui.util.DialogUtils; import org.spotter.eclipse.ui.util.WidgetUtils; import org.spotter.shared.environment.model.XMConfiguration; /** * This viewer is ready-to-use and views a extensions group containing a table * or tree (depending on the <code>hierarchical</code> flag) and some controls * to edit the configured components. * <p> * The viewer looks best if placed within a composite with a * <code>FillLayout</code> or similar in order to use all the available space. * The viewer's content provider expects input of type {@link IExtensionItem} * and interprets it as root element. Depending on the input's * <code>isConnectionIgnored()</code> return value there will be a refresh * button to update the connection status of all elements. When a properties * group viewer is set, it will be updated when the selection of the extension * changes. * </p> * * @author Denis Knoepfle * */ public class ExtensionsGroupViewer implements IItemChangedListener { private static final int VIEWER_CONTROL_STYLE = SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL; private static final String NO_SERVICE_CONNECTION = "No connection to Spotter Service"; private static final String NO_EXTENSIONS = "No extension in the category was found. Ensure that " + "you have placed the extension jar in the correct directory PATH/TO/PLUGINS."; private final AbstractExtensionsEditor editor; private final boolean isHierarchical; private final boolean ignoreConnection; private final IExtensionItem extensionsInput; private final IExtensionItemFactory extensionItemFactory; private IExtensionItem currentSelectedExtension; private PropertiesGroupViewer propertiesGroupViewer; private Control viewerControl; private TableViewer extensionsTblViewer; private TreeViewer extensionsTreeViewer; private StructuredViewer extensionsViewer; private Clipboard clipboard; private DeleteExtensionAction deleteExtensionAction; private Button btnAddExtension, btnAppendExtension, btnRemoveExtension, btnRefreshExtensions; /** * Creates an extensions group viewer under the given parent which is * associated with the provided editor. * * @param parent * the parent of this viewer * @param editor * the associated editor, must not be <code>null</code> * @param hierarchical * determines whether extension items can have children * @param editSupport * determines whether edit support like copy, cut, paste, remove * and drag 'n drop is enabled */ public ExtensionsGroupViewer(Composite parent, AbstractExtensionsEditor editor, boolean hierarchical, boolean editSupport) { if (editor == null) { throw new IllegalArgumentException("editor must not be null"); } this.editor = editor; this.isHierarchical = hierarchical; this.extensionsInput = editor.getInitialExtensionsInput(); this.extensionItemFactory = new BasicEditorExtensionItemFactory(editor.getEditorId()); this.ignoreConnection = extensionsInput == null ? true : extensionsInput.isConnectionIgnored(); createExtensionsGroup(parent, editSupport); addButtonListeners(); addSelectionListeners(); if (extensionsInput != null) { extensionsInput.addItemChangedListener(this); extensionsInput.updateChildrenConnections(); } } /** * Sets the <code>PropertiesGroupViewer</code>. It will be updated via * {@link PropertiesGroupViewer#updateProperties(IExtensionItem) * updateProperties(ExtensionItem)} whenever a extension item is selected. * * @param viewer * the viewer */ public void setPropertiesGroupViewer(PropertiesGroupViewer viewer) { this.propertiesGroupViewer = viewer; } /** * Asks this viewer to take focus. */ public void setFocus() { viewerControl.setFocus(); } /** * Disposes of this viewer. */ public void dispose() { if (clipboard != null) { clipboard.dispose(); } if (extensionsInput != null) { extensionsInput.removeItemChangedListener(this); } } /** * @return the underlying viewer */ public StructuredViewer getViewer() { return extensionsViewer; } /** * Create a table viewer under the given parent. Initializes the viewer with * the given input. Uses SpotterExtensionsContentProvider as content * provider and SpotterExtensionsLabelProvider as label provider. * * @param parent * The parent composite. Must not be <code>null</code>. * @param input * The input of the viewer. Must not be <code>null</code>. * @param editor * The underlying editor if any or <code>null</code>. * @param dragAndDropSupport * Determines whether drag 'n drop is supported. * * @return the created table viewer * * @see SpotterExtensionsContentProvider * @see SpotterExtensionsLabelProvider */ public static TableViewer createTableViewer(Composite parent, IExtensionItem input, AbstractExtensionsEditor editor, boolean dragAndDropSupport) { if (parent == null) { throw new IllegalArgumentException("parent must not be null"); } if (input == null) { throw new IllegalArgumentException("input must not be null"); } // configure table layout Composite tblExtensionsComp = new Composite(parent, SWT.NONE); tblExtensionsComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); TableColumnLayout tblExtensionsColLayout = new TableColumnLayout(); tblExtensionsComp.setLayout(tblExtensionsColLayout); // create table Table table = new Table(tblExtensionsComp, VIEWER_CONTROL_STYLE); table.setHeaderVisible(false); table.setLinesVisible(false); // create viewer for table TableViewer tableViewer = new TableViewer(table); ColumnViewerToolTipSupport.enableFor(tableViewer, ToolTip.NO_RECREATE); TableViewerColumn extensionsColumn = new TableViewerColumn(tableViewer, SWT.NONE); tblExtensionsColLayout.setColumnData(extensionsColumn.getColumn(), new ColumnWeightData(1)); if (dragAndDropSupport) { addDragAndDropSupport(tableViewer, editor, false); } tableViewer.setContentProvider(new SpotterExtensionsContentProvider()); tableViewer.setLabelProvider(new SpotterExtensionsLabelProvider()); tableViewer.setInput(input); return tableViewer; } /** * Create a tree viewer under the given parent. Initializes the viewer with * the given input. Uses SpotterExtensionsContentProvider as content * provider and SpotterExtensionsLabelProvider as label provider. * * @param parent * The parent composite. Must not be <code>null</code>. It is * recommended to use a {@link GridLayout} on the parent or at * least a layout that has set the <i>fill flag</i>. * @param input * The input of the viewer. Must not be <code>null</code>. * @param editor * The underlying editor if any or <code>null</code>. * @param dragAndDropSupport * Determines whether drag 'n drop is supported. * * @return the created table viewer * * @see SpotterExtensionsContentProvider * @see SpotterExtensionsLabelProvider */ public static TreeViewer createTreeViewer(Composite parent, IExtensionItem input, AbstractExtensionsEditor editor, boolean dragAndDropSupport) { if (parent == null) { throw new IllegalArgumentException("parent must not be null"); } if (input == null) { throw new IllegalArgumentException("input must not be null"); } // configure tree layout Composite treeExtensionsComp = new Composite(parent, SWT.NONE); if (parent.getLayout() instanceof GridLayout) { treeExtensionsComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); } TreeColumnLayout treeExtensionsColLayout = new TreeColumnLayout(); treeExtensionsComp.setLayout(treeExtensionsColLayout); // create tree Tree tree = new Tree(treeExtensionsComp, VIEWER_CONTROL_STYLE); tree.setHeaderVisible(false); tree.setLinesVisible(false); // create viewer for tree TreeViewer treeViewer = new TreeViewer(tree); ColumnViewerToolTipSupport.enableFor(treeViewer, ToolTip.NO_RECREATE); TreeViewerColumn extensionsColumn = new TreeViewerColumn(treeViewer, SWT.NONE); treeExtensionsColLayout.setColumnData(extensionsColumn.getColumn(), new ColumnWeightData(1)); if (dragAndDropSupport) { addDragAndDropSupport(treeViewer, editor, true); } treeViewer.setContentProvider(new SpotterExtensionsContentProvider()); treeViewer.setLabelProvider(new SpotterExtensionsLabelProvider()); treeViewer.setInput(input); return treeViewer; } private static void addDragAndDropSupport(StructuredViewer viewer, AbstractExtensionsEditor editor, boolean hierarchical) { final int operations = DND.DROP_MOVE | DND.DROP_COPY; Transfer[] transferTypes = new Transfer[] { LocalSelectionTransfer.getTransfer() }; viewer.addDragSupport(operations, transferTypes, new ExtensionDragListener(viewer, editor)); viewer.addDropSupport(operations, transferTypes, new ExtensionDropListener(viewer, editor, hierarchical)); } private void createExtensionsGroup(Composite container, boolean editSupport) { Group grpConfiguredComponents = new Group(container, SWT.NONE); grpConfiguredComponents.setText("configured components"); grpConfiguredComponents.setLayout(WidgetUtils.createGridLayout(2)); extensionsViewer = createViewerControl(grpConfiguredComponents, editSupport); viewerControl = extensionsViewer.getControl(); Composite buttonComp = new Composite(grpConfiguredComponents, SWT.NONE); buttonComp.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); RowLayout buttonCompLayout = new RowLayout(SWT.VERTICAL); buttonCompLayout.fill = true; buttonCompLayout.center = true; buttonCompLayout.pack = false; buttonCompLayout.spacing = WidgetUtils.DEFAULT_VERTICAL_SPACING; buttonComp.setLayout(buttonCompLayout); createButtons(buttonComp, editSupport); } private ColumnViewer createViewerControl(Composite parent, boolean editSupport) { ColumnViewer columnViewer; if (isHierarchical) { extensionsTreeViewer = createTreeViewer(parent, extensionsInput, editor, editSupport); extensionsTreeViewer.expandAll(); columnViewer = extensionsTreeViewer; } else { extensionsTblViewer = createTableViewer(parent, extensionsInput, editor, editSupport); columnViewer = extensionsTblViewer; } if (editSupport) { IEditorSite editorSite = editor.getEditorSite(); if (editorSite == null) { throw new IllegalStateException("cannot initialize actions when editor site is null"); } // enable copy/cut/paste functionality clipboard = new Clipboard(editorSite.getShell().getDisplay()); IActionBars bars = editorSite.getActionBars(); String editorId = editor.getEditorId(); deleteExtensionAction = new DeleteExtensionAction(columnViewer, editor); CopyExtensionAction copyExtensionAction = new CopyExtensionAction(columnViewer, clipboard, editorId); bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), deleteExtensionAction); bars.setGlobalActionHandler(ActionFactory.CUT.getId(), new CutExtensionAction(columnViewer, copyExtensionAction, deleteExtensionAction)); bars.setGlobalActionHandler(ActionFactory.COPY.getId(), copyExtensionAction); bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new PasteExtensionAction(columnViewer, editorId)); bars.updateActionBars(); } return columnViewer; } private void createButtons(Composite parent, boolean editSupport) { btnAddExtension = new Button(parent, SWT.PUSH); btnAddExtension.setText("Add..."); btnAddExtension.setToolTipText("Opens a dialog to add more extensions"); if (isHierarchical) { btnAppendExtension = new Button(parent, SWT.PUSH); btnAppendExtension.setText("Append..."); btnAppendExtension.setToolTipText("Opens a dialog to append extensions to the selected item"); btnAppendExtension.setEnabled(false); } if (editSupport) { btnRemoveExtension = new Button(parent, SWT.PUSH); btnRemoveExtension.setText("Remove"); btnRemoveExtension.setToolTipText("Removes the selected extension"); btnRemoveExtension.setEnabled(false); } if (!ignoreConnection) { btnRefreshExtensions = new Button(parent, SWT.PUSH); btnRefreshExtensions.setText("Refresh"); btnRefreshExtensions.setToolTipText("Refreshes the connection status of the configured components"); btnRefreshExtensions.setEnabled(extensionsInput != null && extensionsInput.hasItems()); } } /** * Opens and handles a dialog showing available extensions that can be * added. * * @param parentItem * the extension item under which the newly created components * will be added */ private void showAndHandleAddDialog(IExtensionItem parentItem) { Shell shell = PlatformUI.getWorkbench().getDisplay().getActiveShell(); ExtensionMetaobject[] extensions = editor.getAvailableExtensions(); if (extensions == null) { return; } else if (extensions.length == 0) { DialogUtils.openInformation(NO_EXTENSIONS); return; } AddExtensionDialog dialog = new AddExtensionDialog(shell, extensions); Control previousFocusControl = Display.getCurrent().getFocusControl(); if (dialog.open() == Window.OK) { Object[] result = dialog.getResult(); IModelWrapper parentWrapper = parentItem.getModelWrapper(); Object xmlParent = parentWrapper == null ? null : parentWrapper.getXMLModel(); IExtensionItem lastAdded = null; for (Object component : result) { ExtensionMetaobject metaobject = (ExtensionMetaobject) component; IExtensionItem item = processAddedComponent(xmlParent, metaobject); parentItem.addItem(item); item.updateConnectionStatus(); lastAdded = item; } extensionsViewer.setSelection(new StructuredSelection(lastAdded)); editor.markDirty(); } if (previousFocusControl != null && !previousFocusControl.isFocusControl()) { previousFocusControl.forceFocus(); } } /** * Processes the given extension component. * * @param xmlParent * the XML parent model that will receive the created model * @param extension * the extension * @return an <code>ExtensionItem</code> object that contains a suitable * model wrapper */ private IExtensionItem processAddedComponent(Object xmlParent, ExtensionMetaobject extension) { IModelWrapper modelWrapper = editor.createModelWrapper(xmlParent, extension); List<XMConfiguration> xmConfigList = new ArrayList<XMConfiguration>(); for (ConfigParameterDescription confDescr : modelWrapper.getExtensionConfigParams()) { if (confDescr.isMandatory()) { XMConfiguration xmConfig = new XMConfiguration(); String key = confDescr.getName(); xmConfig.setKey(key); String value = confDescr.getDefaultValue(); xmConfig.setValue(value); xmConfigList.add(xmConfig); } } if (!xmConfigList.isEmpty()) { modelWrapper.setConfig(xmConfigList); } return extensionItemFactory.createExtensionItem(modelWrapper); } private void addButtonListeners() { btnAddExtension.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // append items to the root showAndHandleAddDialog(extensionsInput); } }); if (isHierarchical) { btnAppendExtension.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // append items to the selected item showAndHandleAddDialog(currentSelectedExtension); } }); } if (btnRemoveExtension != null) { btnRemoveExtension.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { deleteExtensionAction.run(); } }); } if (!ignoreConnection) { btnRefreshExtensions.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ServiceClientWrapper client = Activator.getDefault().getClient(editor.getProject().getName()); if (client.testConnection(true)) { extensionsInput.updateChildrenConnections(); } else { extensionsInput.setChildrenError(NO_SERVICE_CONNECTION); } } }); } } private void addSelectionListeners() { extensionsViewer.addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection sel = (IStructuredSelection) event.getSelection(); if (!sel.isEmpty()) { btnRemoveExtension.setEnabled(true); if (isHierarchical) { btnAppendExtension.setEnabled(true); } currentSelectedExtension = (IExtensionItem) sel.getFirstElement(); } else { btnRemoveExtension.setEnabled(false); if (isHierarchical) { btnAppendExtension.setEnabled(false); } currentSelectedExtension = null; } updateProperties(); } }); if (extensionsTreeViewer != null) { extensionsTreeViewer.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); Object selectedNode = selection.getFirstElement(); extensionsTreeViewer.setExpandedState(selectedNode, !extensionsTreeViewer.getExpandedState(selectedNode)); } }); } } /** * Updates the properties in the <code>PropertiesGroupViewer</code> if one * is set and not <code>null</code>. */ private void updateProperties() { if (propertiesGroupViewer != null) { propertiesGroupViewer.updateProperties(currentSelectedExtension); } } @Override public void childAdded(IExtensionItem parent, IExtensionItem item) { updateRefreshButton(); } @Override public void childRemoved(IExtensionItem parent, IExtensionItem item) { updateRefreshButton(); } @Override public void appearanceChanged(IExtensionItem item) { } private void updateRefreshButton() { if (!ignoreConnection) { btnRefreshExtensions.setEnabled(extensionsInput != null && extensionsInput.hasItems()); } } }