/* * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY. * * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS. * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION, * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM */ package org.csstudio.sds.ui.internal.properties; import java.util.HashMap; import java.util.Map; import org.csstudio.dal.ui.dnd.rfc.IProcessVariableAdressReceiver; import org.csstudio.dal.ui.dnd.rfc.ProcessVariableExchangeUtil; import org.csstudio.platform.model.pvs.IProcessVariableAddress; import org.csstudio.sds.ui.SdsUiPlugin; import org.csstudio.sds.util.TextDnDUtil; import org.csstudio.ui.util.CustomMediaFactory; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.ListViewer; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; 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.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; /** * A table cell editor for values of type Map(String, String). * * @author Kai Meyer */ public final class StringMapCellEditor extends AbstractDialogCellEditor { /** * The minimum count of entries in the list. */ public static final int MINIMUM_ENTRY_COUNT = 0; /** * A default key for the Map. */ private static final String DEFAULT_KEY = "channel"; /** * The current map. */ private Map<String, String> _map; /** * A copy of the original map. */ private Map<String, String> _originalMap; /** * Creates a new string cell editor parented under the given control. The * cell editor value is a Map of Strings. * * @param parent * The parent table. * @param title * The title for this CellEditor */ public StringMapCellEditor(final Composite parent, final String title) { super(parent, title); } /** * {@inheritDoc} */ @Override protected void openDialog(final Shell parentShell, final String dialogTitle) { final MapInputDialog dialog = new MapInputDialog(parentShell,dialogTitle,"Add, edit or remove the values"); if (dialog.open()==Window.CANCEL) { _map = _originalMap; } } /** * {@inheritDoc} */ @Override protected boolean shouldFireChanges() { return _map != null; } /** * {@inheritDoc} */ @Override protected Object doGetValue() { return _map; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected void doSetValue(final Object value) { Assert.isTrue(value instanceof Map); _map = new HashMap<String, String>(); _originalMap = (Map<String, String>) value; for (final String key : _originalMap.keySet()) { _map.put(key, _originalMap.get(key)); } } /** * This class represents a Dialog to add, edit and remove the entries of a Map. * * @author Kai Meyer */ private final class MapInputDialog extends Dialog { /** * The title of the dialog. */ private final String _title; /** * The message to display, or <code>null</code> if none. */ private final String _message; /** * The List-Widget. */ private ListViewer _viewer; /** * Adds new entries to the List. */ private Action _addAction; /** * Edits the selected entry. */ private Action _editAction; /** * Removes the selected entries from the List. */ private Action _removeAction; /** * The entry dialog of this MapInputDialog. */ private MapEntryDialog _dialog = null; /** * Creates an input dialog with OK and Cancel buttons. Note that the dialog * will have no visual representation (no widgets) until it is told to open. * <p> * Note that the <code>open</code> method blocks for input dialogs. * </p> * * @param parentShell * the parent shell, or <code>null</code> to create a top-level * shell * @param dialogTitle * the dialog title, or <code>null</code> if none * @param dialogMessage * the dialog message, or <code>null</code> if none */ public MapInputDialog(final Shell parentShell, final String dialogTitle, final String dialogMessage) { super(parentShell); this.setShellStyle(SWT.MODELESS | SWT.CLOSE | SWT.MAX | SWT.TITLE | SWT.BORDER | SWT.RESIZE); _title = dialogTitle; _message = dialogMessage; } /** * {@inheritDoc} */ @Override protected void configureShell(final Shell shell) { super.configureShell(shell); if (_title != null) { shell.setText(_title); } } /** * {@inheritDoc} */ @Override protected Control createDialogArea(final Composite parent) { final Composite composite = (Composite) super.createDialogArea(parent); composite.setLayout(new GridLayout(2, false)); if (_message != null) { final Label label = new Label(composite, SWT.WRAP); label.setText(_message); final GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); data.horizontalSpan = 2; data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); label.setLayoutData(data); //label.setFont(parent.getFont()); } final Composite toolBarComposite = new Composite(composite,SWT.BORDER); final GridLayout gridLayout = new GridLayout(1,false); gridLayout.marginLeft = 0; gridLayout.marginRight = 0; gridLayout.marginBottom = 0; gridLayout.marginTop = 0; gridLayout.marginHeight = 0; gridLayout.marginWidth = 0; toolBarComposite.setLayout(gridLayout); final GridData grid = new GridData(SWT.FILL,SWT.FILL,true,true); toolBarComposite.setLayoutData(grid); final ToolBarManager toolbarManager = new ToolBarManager(SWT.FLAT); final ToolBar toolBar = toolbarManager.createControl(toolBarComposite); final GridData gid = new GridData(); gid.horizontalAlignment = GridData.FILL; gid.verticalAlignment = GridData.BEGINNING; toolBar.setLayoutData(gid); this.createActions(toolbarManager); _viewer = this.createListViewer(toolBarComposite); this.hookPopupMenu(_viewer); this.hookDoubleClick(_viewer); //DialogFontUtil.setDialogFont(composite); //applyDialogFont(composite); return composite; } /** * Creates Actions and adds them to the given {@link ToolBarManager}. * @param manager * The ToolBarManager, which should contain the actions */ private void createActions(final ToolBarManager manager) { _addAction = new Action() { @Override public void run() { openMapDialog(null, null, true); refreshAction(); } }; _addAction.setText("Add "+_title); _addAction.setToolTipText("Adds a new "+_title+" to the list"); _addAction.setImageDescriptor(CustomMediaFactory.getInstance() .getImageDescriptorFromPlugin(SdsUiPlugin.PLUGIN_ID, "icons/add.gif")); manager.add(_addAction); _editAction = new Action() { @Override public void run() { final String key = (String) ((IStructuredSelection)_viewer.getSelection()).getFirstElement(); openMapDialog(key, _map.get(key), false); refreshAction(); setFocus(); } }; _editAction.setText("Edit "+_title); _editAction.setToolTipText("Edits the selected "+_title); _editAction.setImageDescriptor(CustomMediaFactory.getInstance() .getImageDescriptorFromPlugin(SdsUiPlugin.PLUGIN_ID, "icons/edit.gif")); _editAction.setEnabled(false); manager.add(_editAction); _removeAction = new Action() { @Override public void run() { removeMapEntry(); refreshAction(); } }; _removeAction.setText("Remove "+_title); _removeAction.setToolTipText("Removes the selected "+_title+" from the list"); _removeAction.setImageDescriptor(CustomMediaFactory.getInstance() .getImageDescriptorFromPlugin(SdsUiPlugin.PLUGIN_ID, "icons/delete.gif")); _removeAction.setEnabled(false); manager.add(_removeAction); manager.update(true); } /** * Creates the viewer for the List. * @param parent * The parent composite for the viewer * @return ListViewer * The ListViewer */ private ListViewer createListViewer(final Composite parent) { final ListViewer viewer = new ListViewer(parent); viewer.setContentProvider(new ArrayContentProvider()); viewer.setLabelProvider(new LabelProvider() { @Override public String getText(final Object element) { return element.toString()+": "+_map.get(element); } }); viewer.setInput(_map.keySet().toArray(new String[_map.keySet().size()])); final GridData gridData = new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL); gridData.verticalSpan = 6; gridData.heightHint = 150; viewer.getList().setLayoutData(gridData); viewer.getList().addSelectionListener(new SelectionAdapter() { /** * {@inheritDoc} */ @Override public void widgetSelected(final SelectionEvent e) { _editAction.setEnabled(viewer.getList().getSelectionIndices().length==1); refreshAction(); } }); viewer.getList().setFocus(); // DnD ProcessVariableExchangeUtil.addProcessVariableAddressDropSupport(viewer.getControl(), DND.DROP_COPY | DND.DROP_MOVE, new IProcessVariableAdressReceiver(){ @Override public void receive(final IProcessVariableAddress[] pvs, final DropTargetEvent event) { if(pvs.length>0) { openMapDialog(DEFAULT_KEY, pvs[0].getFullName(), true); } } }); return viewer; } /** * Adds a Popup menu to the given ListViewer. * @param viewer * The ListViewer */ private void hookPopupMenu(final ListViewer viewer) { final MenuManager popupMenu = new MenuManager(); popupMenu.add(_addAction); popupMenu.add(_editAction); popupMenu.add(new Separator()); popupMenu.add(_removeAction); final Menu menu = popupMenu.createContextMenu(viewer.getList()); viewer.getList().setMenu(menu); } /** * Adds doubleclick support to the given ListViewer. * @param viewer * The Listviewer */ private void hookDoubleClick(final ListViewer viewer) { viewer.getControl().addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(final MouseEvent e) { if (_viewer.getList().getSelectionCount()==1) { _editAction.run(); } else { _addAction.run(); } } }); } /** * Opens a Dialog for adding a new Point. * @param initKey * The initial key for the Dialog * @param initValue * The initial value for the Dialog * @param isNew * True, if the entry is new, false otherwise */ private void openMapDialog(final String initKey, final String initValue, final boolean isNew) { int index = _viewer.getList().getItemCount(); final int[] selectedIndices = _viewer.getList().getSelectionIndices(); if (selectedIndices.length>0) { index = selectedIndices[0]; } try { if (_dialog!=null) { _dialog.close(); } _dialog = new MapEntryDialog(this.getParentShell(),"Alias", null, initKey, initValue, isNew); this.getButton(IDialogConstants.CANCEL_ID).setEnabled(false); this.getButton(IDialogConstants.OK_ID).setEnabled(false); if (_dialog.open()==Window.OK) { if (!_viewer.getList().isDisposed()) { _viewer.setInput(_map.keySet()); _viewer.refresh(); } } _dialog = null; if (!_viewer.getList().isDisposed()) { this.getButton(IDialogConstants.CANCEL_ID).setEnabled(true); this.getButton(IDialogConstants.OK_ID).setEnabled(true); _viewer.getList().setSelection(index); } getShell().setFocus(); } catch (final Exception e) { e.printStackTrace(); } } /** * Removes the current selected map entry from the List. */ private void removeMapEntry() { if (_viewer.getList().getSelectionIndices().length>0) { for (final Object o : ((IStructuredSelection)_viewer.getSelection()).toArray()) { final String key = (String) o; _map.remove(key); } } _viewer.setInput(_map.keySet()); _viewer.refresh(); } /** * {@inheritDoc} */ @Override public boolean close() { if (_dialog!=null) { _dialog.close(); } return super.close(); } /** * Enables or disables the RemoveButton. */ private void refreshAction() { if (_viewer.getList().getItemCount()>MINIMUM_ENTRY_COUNT) { _removeAction.setEnabled(_viewer.getList().getSelectionIndices().length>0); } else { _removeAction.setEnabled(false); } _editAction.setEnabled(_viewer.getList().getSelectionCount()==1); } } /** * This class represents a Dialog for editing a map entry. * @author Kai Meyer */ private final class MapEntryDialog extends Dialog { /** * The title of the dialog. */ private final String _title; /** * The message to display, or <code>null</code> if none. */ private final String _message; /** * The key for the map; name of the entry. */ private final String _key; /** * The value for the map; value of the entry. */ private final String _value; /** * The Text for the name of the entry. */ private Text _nameText; /** * The Text for the value of the entry. */ private Text _valueText; /** * A boolean, which indicates if the entry is new. */ private final boolean _isNew; /** * Creates an input dialog with OK and Cancel buttons. Note that the dialog * will have no visual representation (no widgets) until it is told to open. * <p> * Note that the <code>open</code> method blocks for input dialogs. * </p> * * @param parentShell * the parent shell, or <code>null</code> to create a top-level * shell * @param dialogTitle * the dialog title, or <code>null</code> if none * @param dialogMessage * the dialog message, or <code>null</code> if none * @param initialKey * the initial input key, or <code>null</code> if none * @param initialValue * the initial input value, or <code>null</code> if none * @param isNew * true, if the entry is new, false otherwise */ public MapEntryDialog(final Shell parentShell, final String dialogTitle, final String dialogMessage, final String initialKey, final String initialValue, final boolean isNew) { super(parentShell); this.setShellStyle(SWT.MODELESS | SWT.CLOSE | SWT.MAX | SWT.TITLE | SWT.BORDER | SWT.RESIZE); _title = dialogTitle; _message = dialogMessage; _key = initialKey; _value = initialValue; _isNew = isNew; } /** * {@inheritDoc} */ @Override protected void configureShell(final Shell shell) { super.configureShell(shell); if (_title != null) { shell.setText(_title); } } /** * {@inheritDoc} */ @Override protected Control createDialogArea(final Composite parent) { final Composite composite = (Composite) super.createDialogArea(parent); composite.setLayout(new GridLayout(2, false)); if (_message != null) { final Label label = new Label(composite, SWT.WRAP); label.setText(_message); final GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); data.horizontalSpan = 2; data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); label.setLayoutData(data); //label.setFont(parent.getFont()); } _nameText = this.createTextEntry(composite, "Name:", _key); _valueText = this.createTextEntry(composite, "Value:", _value); //DialogFontUtil.setDialogFont(composite); //applyDialogFont(composite); return composite; } /** * Creates a Label and a Text. * @param parent * The parent composite for the Widgets * @param labelTitle * The title for the Label * @param textValue * The initial value for the Text * @return Text * The Text-Widget */ private Text createTextEntry(final Composite parent, final String labelTitle, final String textValue) { final Label label = new Label(parent, SWT.NONE); label.setText(labelTitle); final Text text = new Text(parent, SWT.MULTI | SWT.BORDER); final GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); gd.widthHint = 280; text.setLayoutData(gd); if (textValue==null) { text.setText(""); } else { text.setText(textValue); } ProcessVariableExchangeUtil.addProcessVariableAddressDropSupport(text, DND.DROP_COPY | DND.DROP_MOVE, new IProcessVariableAdressReceiver(){ @Override public void receive(final IProcessVariableAddress[] pvs, final DropTargetEvent event) { text.setText(pvs[0].getRawName()); } }); TextDnDUtil.addDragSupport(text); text.addKeyListener(new KeyListener() { @Override public void keyPressed(final KeyEvent e) { if (e.keyCode==13) { getButton(IDialogConstants.OK_ID).setFocus(); okPressed(); } } @Override public void keyReleased(final KeyEvent e) { } }); return text; } /** * {@inheritDoc} */ @Override protected void okPressed() { this.getButton(IDialogConstants.OK_ID).setFocus(); if (_nameText.getText()!=null && _nameText.getText().trim().length()>0) { if (_key!=null && _map.containsKey(_key) && !_isNew) { _map.remove(_key); } _map.put(_nameText.getText(), _valueText.getText()); } super.okPressed(); } } }