/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.modeling.ui.diagramEditor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Set; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ICheckStateProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.TreeEditor; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.part.Page; import org.simantics.diagram.layer.ILayersViewPage; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.participant.Selection; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.ElementLayers; import org.simantics.g2d.layers.IEditableLayer; import org.simantics.g2d.layers.ILayer; import org.simantics.g2d.layers.ILayers; import org.simantics.g2d.layers.ILayersEditor; import org.simantics.g2d.layers.SimpleLayer; import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener; import org.simantics.utils.datastructures.Arrays; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.ui.ISelectionUtils; public class DiagramLayersPage extends Page implements ILayersViewPage { private static final String TEXT_APPLY_FOCUS_SETTINGS = "Focus Active"; private static final String TOOLTIP_APPLY_FOCUS_SETTINGS = "Only Focus Diagram Elements For Active Roles"; private static final String TEXT_IGNORE_FOCUS_SETTINGS = "Focus All"; private static final String TOOLTIP_IGNORE_FOCUS_SETTINGS = "Focus All Diagram Elements Regardless Of Active Roles"; private static final String TEXT_APPLY_VISIBILITY_SETTINGS = "Show Active"; private static final String TOOLTIP_APPLY_VISIBILITY_SETTINGS = "Only Show Diagram Elements For Active Roles"; private static final String TEXT_IGNORE_VISIBILITY_SETTINGS = "Show All"; private static final String TOOLTIP_IGNORE_VISIBILITY_SETTINGS = "Show All Diagram Elements Regardless Of Active Roles"; final private ICanvasContext context; final private IDiagram diagram; private CheckboxTreeViewer viewer; private Composite composite; private TreeEditor editor; private Collection elements = Collections.emptySet(); enum Attribute { Visible, Focusable } enum Tristate { True, False, Both; static Tristate to(Boolean b) { return b == null ? null : b ? True : False; } boolean toBoolean() { switch (this) { case Both: throw new IllegalStateException("cannot convert Tristate Both to boolean"); case False: return false; case True: return true; default: return false; } } Tristate toggle() { switch (this) { case Both: case False: return True; case True: return False; default: return False; } } Tristate merge(Tristate state) { if (state == null) return this; switch (this) { case Both: return Both; case False: switch (state) { case False: return False; case Both: case True: return Both; } case True: switch (state) { case True: return True; case False: case Both: return Both; } } return this; } } Boolean getAttribute(IElement e, ILayer layer, Attribute attribute) { ElementClass ec = e.getElementClass(); for (ElementLayers el : ec.getItemsByClass(ElementLayers.class)) { switch (attribute) { case Visible: return Boolean.valueOf(el.isVisible(e, layer)); case Focusable: return Boolean.valueOf(el.isFocusable(e, layer)); } } return null; } boolean setAttribute(IElement e, ILayer layer, Attribute attribute, boolean value) { ElementClass ec = e.getElementClass(); for (ElementLayers el : ec.getItemsByClass(ElementLayers.class)) { switch (attribute) { case Visible: return el.setVisibility(e, layer, value); case Focusable: return el.setFocusability(e, layer, value); } } return false; } Tristate getJointAttribute(Collection elements, ILayer layer, Attribute attribute) { Tristate state = null; for (IElement e : elements) { Boolean attr = getAttribute(e, layer, attribute); if (attr == null) continue; if (state == null) { state = Tristate.to(attr); } else { state = state.merge(Tristate.to(attr)); } } return state; } int setAttribute(Collection elements, ILayer layer, Attribute attribute, boolean value) { int result = 0; for (IElement e : elements) { if (setAttribute(e, layer, attribute, value)) ++result; } return result; } final private IHintListener selectionListener = new HintListenerAdapter() { @Override public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { Collection es = Collections.emptySet(); if (newValue instanceof Collection) { Collection coll = (Collection)newValue; es = new ArrayList(coll.size()); for (Object o : coll) { if (!(o instanceof IElement)) return; es.add((IElement) o); } if (es.isEmpty()) es = Collections.emptySet(); } elements = es; redraw(); } private void redraw() { viewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (viewer.getControl().isDisposed()) return; viewer.getControl().redraw(); } }); } }; public DiagramLayersPage(IDiagram diagram, ICanvasContext context) { assert(diagram != null); this.diagram = diagram; this.context = context; context.getDefaultHintContext().addKeyHintListener(Selection.SELECTION0, selectionListener); } @Override public void dispose() { context.getDefaultHintContext().removeKeyHintListener(Selection.SELECTION0, selectionListener); super.dispose(); } @Override public void createControl(Composite parent) { final ILayersEditor layers = diagram.getHint(DiagramHints.KEY_LAYERS_EDITOR); layers.addListener(new ILayersEditorListener() { @Override public void layerRemoved(ILayer layer) { scheduleRefresh(); } @Override public void layerAdded(ILayer layer) { scheduleRefresh(); } @Override public void layerActivated(ILayer layer) { scheduleRefresh(); } @Override public void layerDeactivated(ILayer layer) { scheduleRefresh(); } @Override public void ignoreFocusChanged(boolean value) { } @Override public void ignoreVisibilityChanged(boolean value) { } void scheduleRefresh() { viewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { viewer.refresh(); } }); } }); composite = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(4).applyTo(composite); Button addButton = new Button(composite, SWT.NONE); addButton.setText("New"); addButton.setToolTipText("Create New Diagram Role"); addButton.addSelectionListener(new SelectionListener() { String findFreshName(ILayers layers, String proposal) { Set all = layers.getLayers(); String name = proposal; int i = 1; while (true) { boolean match = false; for (ILayer layer : all) { if (name.equals(layer.getName())) { match = true; break; } } if (!match) return name; ++i; name = proposal + " " + i; } } @Override public void widgetSelected(SelectionEvent e) { String name = findFreshName(layers, "New Role"); SimpleLayer layer = new SimpleLayer(name); layers.addLayer(layer); layers.activate(layer); } @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); final Button removeButton = new Button(composite, SWT.NONE); removeButton.setText("Remove"); removeButton.setToolTipText("Remove Selected Diagram Role"); removeButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { TreeItem[] items = viewer.getTree().getSelection(); if (items.length == 0) return; TreeItem[] all = viewer.getTree().getItems(); int firstIndex = Arrays.indexOf(all, items[0]); for (TreeItem item : items) { int index = Arrays.indexOf(all, item); all[index] = null; ILayer layer = (ILayer)item.getData(); layers.removeLayer(layer); } int selectIndex = firstIndex - 1; if (firstIndex == 0) { for (int i = firstIndex; i < all.length; ++i) if (all[i] != null) { selectIndex = i; break; } } if (selectIndex >= 0) { viewer.getTree().select(all[selectIndex]); } } @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); String ignoreVisibilityText = TEXT_IGNORE_VISIBILITY_SETTINGS; String ignoreVisibilityTooltip = TOOLTIP_IGNORE_VISIBILITY_SETTINGS; boolean ignoreVisibility = layers.getIgnoreVisibilitySettings(); if (ignoreVisibility) { ignoreVisibilityText = TEXT_APPLY_VISIBILITY_SETTINGS; ignoreVisibilityTooltip = TOOLTIP_APPLY_VISIBILITY_SETTINGS; } final Button ignoreVisibilityButton = new Button(composite, SWT.NONE); ignoreVisibilityButton.setText(ignoreVisibilityText); ignoreVisibilityButton.setToolTipText(ignoreVisibilityTooltip); ignoreVisibilityButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { String ignoreText = TEXT_IGNORE_VISIBILITY_SETTINGS; String ignoreTooltip= TOOLTIP_IGNORE_VISIBILITY_SETTINGS; boolean ignore = layers.getIgnoreVisibilitySettings(); if(!ignore) { ignoreText = TEXT_APPLY_VISIBILITY_SETTINGS; ignoreTooltip = TOOLTIP_APPLY_VISIBILITY_SETTINGS; layers.setIgnoreVisibilitySettings(true); } else { layers.setIgnoreVisibilitySettings(false); } ignoreVisibilityButton.setText(ignoreText); ignoreVisibilityButton.setToolTipText(ignoreTooltip); composite.layout(); context.getThreadAccess().asyncExec(new Runnable() { @Override public void run() { if(context.isDisposed()) return; context.getContentContext().setDirty(); } }); } @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); String ignoreFocusText = TEXT_IGNORE_FOCUS_SETTINGS; String ignoreFocusTooltip = TOOLTIP_IGNORE_FOCUS_SETTINGS; boolean ignoreFocus = layers.getIgnoreFocusSettings(); if(ignoreFocus) { ignoreFocusText = TEXT_APPLY_FOCUS_SETTINGS; ignoreFocusTooltip = TOOLTIP_APPLY_FOCUS_SETTINGS; } final Button ignoreFocusButton = new Button(composite, SWT.NONE); ignoreFocusButton.setText(ignoreFocusText); ignoreFocusButton.setToolTipText(ignoreFocusTooltip); ignoreFocusButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { String ignoreText = TEXT_IGNORE_FOCUS_SETTINGS; String ignoreTooltip = TOOLTIP_IGNORE_FOCUS_SETTINGS; boolean ignore = layers.getIgnoreFocusSettings(); if(!ignore) { ignoreText = TEXT_APPLY_FOCUS_SETTINGS; ignoreTooltip = TOOLTIP_APPLY_FOCUS_SETTINGS; layers.setIgnoreFocusSettings(true); } else { layers.setIgnoreFocusSettings(false); } ignoreFocusButton.setText(ignoreText); ignoreFocusButton.setToolTipText(ignoreTooltip); composite.layout(); context.getThreadAccess().asyncExec(new Runnable() { @Override public void run() { if(context.isDisposed()) return; context.getContentContext().setDirty(); } }); } @Override public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } }); viewer = new CheckboxTreeViewer(composite, SWT.BORDER | SWT.FULL_SELECTION ); GridDataFactory.fillDefaults().grab(true, true).span(4, 1).applyTo(viewer.getControl()); viewer.getControl().setToolTipText("Selects the diagram to include in the exported document."); viewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS); viewer.getTree().setHeaderVisible(true); editor = new TreeEditor(viewer.getTree()); final TreeColumn column1 = new TreeColumn(viewer.getTree(), SWT.LEFT); column1.setText("Role"); column1.setWidth(100); final TreeColumn column2 = new TreeColumn(viewer.getTree(), SWT.LEFT); column2.setText("Show"); column2.setWidth(50); final TreeColumn column3 = new TreeColumn(viewer.getTree(), SWT.LEFT); column3.setText("Focus"); column3.setWidth(50); viewer.getTree().addListener(SWT.Resize, new Listener() { @Override public void handleEvent(Event event) { Tree tree = viewer.getTree(); Point size = tree.getSize(); int w = Math.max(size.x - 100 - tree.getBorderWidth() * 2, 30); column1.setWidth(w); } }); viewer.getTree().addListener(SWT.PaintItem, new Listener() { public void handleEvent(Event event) { if ((event.index == 1 || event.index == 2) && !elements.isEmpty()) { ILayer[] lz = layers.getLayers().toArray(new ILayer[0]); TreeItem item = (TreeItem)event.item; int index = viewer.getTree().indexOf(item); int width = 0; if (event.index == 1) width = (column2.getWidth() - 1); if (event.index == 2) width = (column3.getWidth() - 1); Attribute attribute = Attribute.Visible; if (event.index == 2) attribute = Attribute.Focusable; Tristate state = getJointAttribute(elements, lz[index], attribute); Color color = null; switch (state) { case False: color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_RED); break; case True: color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_GREEN); break; case Both: color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_GRAY); break; } GC gc = event.gc; Color foreground = gc.getForeground(); Color background = gc.getBackground(); gc.setBackground(color); gc.setForeground(viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_BLACK)); gc.fillRectangle(event.x, event.y, width-1, event.height-1); Rectangle rect2 = new Rectangle(event.x, event.y, width-1, event.height-1); gc.drawRectangle(rect2); gc.setForeground(background); gc.setBackground(foreground); } } }); viewer.getTree().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent event) { if (event.keyCode == SWT.F2) { // FIXME: Eclipse currently eats F2 presses. This should be // implemented as a command handler or find some way to // force these listeners to have priority... System.out.println("startediting"); TreeItem[] items = viewer.getTree().getSelection(); if(items.length != 1) return; TreeItem item = items[0]; ILayer layer = ISelectionUtils.filterSingleSelection(viewer.getSelection(), ILayer.class); if (layer == null) return; startEditing(layer, item); } } }); viewer.getTree().addListener(SWT.MouseDown, new Listener() { public void handleEvent(Event event) { if (viewer.getControl().isDisposed()) return; Point pt = new Point(event.x, event.y); TreeItem item = viewer.getTree().getItem(pt); if (item == null) return; int index = viewer.getTree().indexOf(item); ILayer[] lz = layers.getLayers().toArray(new ILayer[0]); ILayer layer = lz[index]; Rectangle rect = item.getBounds(0); if (event.count == 2 && rect.contains(pt)) { startEditing(layer, item); return; } // Cannot adjust visibility/focusability if no elements are selected. if (elements.isEmpty()) return; rect = item.getBounds(1); if (rect.contains(pt)) { Tristate state = getJointAttribute(elements, layer, Attribute.Visible); if (setAttribute(elements, layer, Attribute.Visible, state.toggle().toBoolean()) > 0) { refresh(); } return; } Rectangle rect2 = item.getBounds(2); if (rect2.contains(pt)) { Tristate state = getJointAttribute(elements, layer, Attribute.Focusable); if (setAttribute(elements, layer, Attribute.Focusable, state.toggle().toBoolean()) > 0) { refresh(); } return; } } private void refresh() { viewer.getControl().redraw(); context.getThreadAccess().asyncExec(new Runnable() { @Override public void run() { if (context.isDisposed()) return; context.getContentContext().setDirty(); } }); } }); viewer.addCheckStateListener(new ICheckStateListener(){ @Override public void checkStateChanged(CheckStateChangedEvent event) { ILayer layer = (ILayer)event.getElement(); if(event.getChecked()) layers.activate(layer); else layers.deactivate(layer); viewer.setSubtreeChecked(event.getElement(), event.getChecked()); context.getThreadAccess().asyncExec(new Runnable() { @Override public void run() { if(context.isDisposed()) return; context.getContentContext().setDirty(); } }); } }); viewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { ISelection s = event.getSelection(); if (s.isEmpty()) { removeButton.setEnabled(false); } else { removeButton.setEnabled(true); } } }); viewer.setContentProvider(new ITreeContentProvider(){ @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public void dispose() { } @Override public Object[] getElements(Object inputElement) { return layers.getLayers().toArray(); } @Override public boolean hasChildren(Object element) { return false; } @Override public Object getParent(Object element) { return null; } @Override public Object[] getChildren(Object parentElement) { return new Object[0]; } }); viewer.setLabelProvider(new CellLabelProvider() { @Override public void update(ViewerCell cell) { if(cell.getColumnIndex() == 0) { ILayer layer = (ILayer)cell.getElement(); cell.setText(layer.getName()); } else { cell.setText(""); } } }); viewer.setCheckStateProvider(new ICheckStateProvider() { @Override public boolean isChecked(Object element) { ILayer layer = (ILayer)element; final boolean isActive = layers.isActive(layer); return isActive; } @Override public boolean isGrayed(Object element) { return false; } }); viewer.setInput(this); for(ILayer layer : layers.getVisibleLayers()) { viewer.setSubtreeChecked(layer, true); } } @Override public Control getControl() { return composite; } @Override public void setFocus() { } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { } @Override public ISelection getSelection() { return null; } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { } @Override public void setSelection(ISelection selection) { } private boolean startEditing(final ILayer layer, final TreeItem item/*, final int columnIndex*/) { // Column column = columns[columnIndex]; String initialText = layer.getName(); final Composite composite = new Composite(viewer.getTree(), SWT.NONE); final Text text = new Text(composite, SWT.BORDER); final int insetX = 0; final int insetY = 0; composite.addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { Rectangle rect = composite.getClientArea(); text.setBounds(rect.x + insetX, rect.y + insetY, rect.width - insetX * 2, rect.height - insetY * 2); } }); Listener textListener = new Listener() { public void handleEvent(final Event e) { //String error; String newText; switch (e.type) { case SWT.FocusOut: if(layer instanceof IEditableLayer) { IEditableLayer l = (IEditableLayer)layer; l.setName(text.getText()); System.out.println("renamed layer to " + text.getText()); viewer.refresh(); } // // Item may be disposed if the tree gets reset after a previous editing. // if (!item.isDisposed()) { // item.setText(columnIndex, text.getText()); // queueSelectionRefresh(context); // } // } else { // // System.out.println("validation error: " + error); // } composite.dispose(); break; case SWT.Modify: // newText = text.getText(); // error = modifier.isValid(newText); // if (error != null) { // text.setBackground(invalidModificationColor); // // System.out.println("validation error: " + error); // } else { // text.setBackground(null); // } break; case SWT.Verify: newText = text.getText(); String leftText = newText.substring(0, e.start); String rightText = newText.substring(e.end, newText.length()); GC gc = new GC(text); Point size = gc.textExtent(leftText + e.text + rightText); gc.dispose(); size = text.computeSize(size.x, SWT.DEFAULT); editor.horizontalAlignment = SWT.LEFT; Rectangle itemRect = item.getBounds(0), rect = viewer.getTree().getClientArea(); editor.minimumWidth = Math.max(size.x, itemRect.width) + insetX * 2; int left = itemRect.x, right = rect.x + rect.width; editor.minimumWidth = Math.min(editor.minimumWidth, right - left); editor.minimumHeight = size.y + insetY * 2; editor.layout(); break; case SWT.Traverse: switch (e.detail) { case SWT.TRAVERSE_RETURN: if(layer instanceof IEditableLayer) { IEditableLayer l = (IEditableLayer)layer; l.setName(text.getText()); //System.out.println("renamed layer to " + text.getText()); viewer.refresh(); } // error = modifier.isValid(text.getText()); // if (error == null) { // modifier.modify(text.getText()); // if (!item.isDisposed()) { // item.setText(columnIndex, text.getText()); // queueSelectionRefresh(context); // } // } // FALL THROUGH case SWT.TRAVERSE_ESCAPE: composite.dispose(); e.doit = false; break; default: //System.out.println("unhandled traversal: " + e.detail); break; } break; } } }; text.addListener(SWT.FocusOut, textListener); text.addListener(SWT.Traverse, textListener); text.addListener(SWT.Verify, textListener); text.addListener(SWT.Modify, textListener); editor.setEditor(composite, item, 0); text.setText(initialText); text.selectAll(); text.setFocus(); // lastItem[0] = item; return true; } }