1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.modeling.ui.diagramEditor;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
19 import org.eclipse.jface.layout.GridDataFactory;
20 import org.eclipse.jface.layout.GridLayoutFactory;
21 import org.eclipse.jface.viewers.CellLabelProvider;
22 import org.eclipse.jface.viewers.CheckStateChangedEvent;
23 import org.eclipse.jface.viewers.CheckboxTreeViewer;
24 import org.eclipse.jface.viewers.ICheckStateListener;
25 import org.eclipse.jface.viewers.ICheckStateProvider;
26 import org.eclipse.jface.viewers.ISelection;
27 import org.eclipse.jface.viewers.ISelectionChangedListener;
28 import org.eclipse.jface.viewers.ITreeContentProvider;
29 import org.eclipse.jface.viewers.SelectionChangedEvent;
30 import org.eclipse.jface.viewers.TreeViewer;
31 import org.eclipse.jface.viewers.Viewer;
32 import org.eclipse.jface.viewers.ViewerCell;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.custom.TreeEditor;
35 import org.eclipse.swt.events.KeyAdapter;
36 import org.eclipse.swt.events.KeyEvent;
37 import org.eclipse.swt.events.SelectionEvent;
38 import org.eclipse.swt.events.SelectionListener;
39 import org.eclipse.swt.graphics.Color;
40 import org.eclipse.swt.graphics.GC;
41 import org.eclipse.swt.graphics.Point;
42 import org.eclipse.swt.graphics.Rectangle;
43 import org.eclipse.swt.widgets.Button;
44 import org.eclipse.swt.widgets.Composite;
45 import org.eclipse.swt.widgets.Control;
46 import org.eclipse.swt.widgets.Event;
47 import org.eclipse.swt.widgets.Listener;
48 import org.eclipse.swt.widgets.Text;
49 import org.eclipse.swt.widgets.Tree;
50 import org.eclipse.swt.widgets.TreeColumn;
51 import org.eclipse.swt.widgets.TreeItem;
52 import org.eclipse.ui.part.Page;
53 import org.simantics.diagram.layer.ILayersViewPage;
54 import org.simantics.g2d.canvas.ICanvasContext;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.IDiagram;
57 import org.simantics.g2d.diagram.participant.Selection;
58 import org.simantics.g2d.element.ElementClass;
59 import org.simantics.g2d.element.ElementHints;
60 import org.simantics.g2d.element.IElement;
61 import org.simantics.g2d.element.handler.ElementLayerListener;
62 import org.simantics.g2d.element.handler.ElementLayers;
63 import org.simantics.g2d.layers.IEditableLayer;
64 import org.simantics.g2d.layers.ILayer;
65 import org.simantics.g2d.layers.ILayers;
66 import org.simantics.g2d.layers.ILayers.ILayersListener;
67 import org.simantics.g2d.layers.ILayersEditor;
68 import org.simantics.g2d.layers.SimpleLayer;
69 import org.simantics.utils.datastructures.Arrays;
70 import org.simantics.utils.datastructures.disposable.IDisposable;
71 import org.simantics.utils.datastructures.disposable.IDisposeListener;
72 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
73 import org.simantics.utils.datastructures.hints.IHintContext.Key;
74 import org.simantics.utils.datastructures.hints.IHintListener;
75 import org.simantics.utils.datastructures.hints.IHintObservable;
76 import org.simantics.utils.ui.ISelectionUtils;
78 public class DiagramLayersPage extends Page implements ILayersViewPage {
80 private static final String TEXT_IGNORE_FOCUS_SETTINGS = Messages.DiagramLayersPage_FocusAll;
81 private static final String TOOLTIP_IGNORE_FOCUS_SETTINGS = Messages.DiagramLayersPage_FocusAllTT;
83 private static final String TEXT_IGNORE_VISIBILITY_SETTINGS = Messages.DiagramLayersPage_ShowAll;
84 private static final String TOOLTIP_IGNORE_VISIBILITY_SETTINGS = Messages.DiagramLayersPage_ShowAllTT;
86 final private ICanvasContext context;
87 private CheckboxTreeViewer viewer;
88 private Button ignoreVisibilityButton;
89 private Button ignoreFocusButton;
90 private Composite composite;
91 private TreeEditor editor;
93 private Collection<IElement> elements = Collections.emptySet();
94 private ILayersEditor layers;
106 static Tristate to(Boolean b) {
107 return b == null ? null : b ? True : False;
109 boolean toBoolean() {
112 throw new IllegalStateException("cannot convert Tristate Both to boolean"); //$NON-NLS-1$
132 Tristate merge(Tristate state) {
159 Boolean getAttribute(IElement e, ILayer layer, Attribute attribute) {
160 ElementClass ec = e.getElementClass();
161 for (ElementLayers el : ec.getItemsByClass(ElementLayers.class)) {
164 return Boolean.valueOf(el.isVisible(e, layer));
166 return Boolean.valueOf(el.isFocusable(e, layer));
172 Tristate getJointAttribute(Collection<IElement> elements, ILayer layer, Attribute attribute) {
173 Tristate state = null;
174 for (IElement e : elements) {
175 Boolean attr = getAttribute(e, layer, attribute);
180 state = Tristate.to(attr);
182 state = state.merge(Tristate.to(attr));
188 void setAttribute(Collection<IElement> elements, ILayer layer, Attribute attribute, boolean value) {
189 // Short-circuit the hint updates so that the result will be immediately available for viewer.refresh().
190 // There is no listener for the element layer hints so the viewer won't update the tristates automatically!
192 IDiagram diagram = context.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
193 ElementLayerListener elementLayerListener = diagram.getHint(DiagramHints.KEY_ELEMENT_LAYER_LISTENER);
196 for (IElement e : elements) {
197 Set<ILayer> elementLayers = (Set<ILayer>) e.getHint(ElementHints.KEY_VISIBLE_LAYERS);
198 if (elementLayers != null) {
200 elementLayers.add(layer);
202 elementLayers.remove(layer);
204 elementLayerListener.visibilityChanged(e, layer, value);
208 for (IElement e : elements) {
209 Set<ILayer> elementLayers = (Set<ILayer>) e.getHint(ElementHints.KEY_FOCUS_LAYERS);
210 if (elementLayers != null) {
212 elementLayers.add(layer);
214 elementLayers.remove(layer);
216 elementLayerListener.focusabilityChanged(e, layer, value);
220 elementLayerListener.flush();
224 final private IHintListener selectionListener = new HintListenerAdapter() {
227 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
228 Collection<IElement> es = Collections.emptySet();
229 if (newValue instanceof Collection<?>) {
230 Collection<?> coll = (Collection<?>)newValue;
231 es = new ArrayList<IElement>(coll.size());
232 for (Object o : coll) {
233 if (!(o instanceof IElement))
235 es.add((IElement) o);
238 es = Collections.emptySet();
244 private void redraw() {
245 if (viewer != null) {
246 viewer.getControl().getDisplay().asyncExec(new Runnable() {
249 if (viewer.getControl().isDisposed())
251 viewer.getControl().redraw();
258 final private IDisposeListener contextDisposeListener = new IDisposeListener() {
261 public void onDisposed(IDisposable sender) {
262 if (getControl() != null) getControl().getDisplay().asyncExec(new Runnable() {
271 public DiagramLayersPage(ICanvasContext context) {
272 this.context = context;
274 context.getDefaultHintContext().addKeyHintListener(Selection.SELECTION0, selectionListener);
275 context.addDisposeListener(contextDisposeListener);
279 public void dispose() {
281 context.getDefaultHintContext().removeKeyHintListener(Selection.SELECTION0, selectionListener);
282 context.removeDisposeListener(contextDisposeListener);
283 if (layers != null && layersListener != null) {
284 layers.removeLayersListener(layersListener);
285 layersListener = null;
292 public void createControl(Composite parent) {
293 composite = new Composite(parent, SWT.NONE);
294 GridLayoutFactory.fillDefaults().numColumns(4).applyTo(composite);
296 IDiagram diagram = context.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
297 if (diagram != null) onDiagramSet(diagram);
298 context.getDefaultHintContext().addKeyHintListener(DiagramHints.KEY_DIAGRAM, new IHintListener() {
300 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
301 IDiagram diagram = (IDiagram)newValue;
302 onDiagramSet(diagram);
306 public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
312 private void onDiagramSet(IDiagram diagram) {
313 if (diagram != null) {
314 layers = diagram.getHint(DiagramHints.KEY_LAYERS_EDITOR);
315 if (layers != null) initialize(layers, diagram);
316 diagram.addKeyHintListener(DiagramHints.KEY_LAYERS_EDITOR, new IHintListener() {
320 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
321 if (newValue != null) {
322 initialize(layers, diagram);
326 public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
333 private void initialize(ILayersEditor layers, IDiagram diagram) {
334 composite.getDisplay().asyncExec(new Runnable() {
337 initialize2(layers, diagram);
342 private ILayersListener layersListener = new ILayersListener() {
345 public void changed() {
349 void scheduleRefresh() {
350 viewer.getControl().getDisplay().asyncExec(new Runnable() {
354 ignoreVisibilityButton.setSelection(layers.getIgnoreVisibilitySettings());
355 ignoreFocusButton.setSelection(layers.getIgnoreFocusSettings());
356 if (!context.isDisposed())
357 context.getContentContext().setDirty();
363 private static String findFreshName(ILayers layers, String proposal) {
364 Set<ILayer> all = layers.getLayers();
365 String name = proposal;
368 boolean match = false;
369 for (ILayer layer : all) {
370 if (name.equals(layer.getName())) {
378 name = proposal + " " + i; //$NON-NLS-1$
382 private void initialize2(ILayersEditor layers, IDiagram diagram) {
383 layers.addLayersListener(layersListener);
385 Button addButton = new Button(composite, SWT.NONE);
386 addButton.setText(Messages.DiagramLayersPage_New);
387 addButton.setToolTipText(Messages.DiagramLayersPage_NewTT);
388 addButton.addSelectionListener(new SelectionListener() {
391 public void widgetSelected(SelectionEvent e) {
392 String name = findFreshName(layers, Messages.DiagramLayersPage_NewRole);
393 SimpleLayer layer = new SimpleLayer(name);
394 layers.addLayer(layer);
395 layers.activate(layer);
399 public void widgetDefaultSelected(SelectionEvent e) {
405 final Button removeButton = new Button(composite, SWT.NONE);
406 removeButton.setText(Messages.DiagramLayersPage_Remove);
407 removeButton.setToolTipText(Messages.DiagramLayersPage_RemoveTT);
408 removeButton.addSelectionListener(new SelectionListener() {
410 public void widgetSelected(SelectionEvent e) {
411 TreeItem[] items = viewer.getTree().getSelection();
412 if (items.length == 0)
415 TreeItem[] all = viewer.getTree().getItems();
416 int firstIndex = Arrays.indexOf(all, items[0]);
417 for (TreeItem item : items) {
418 int index = Arrays.indexOf(all, item);
420 ILayer layer = (ILayer)item.getData();
421 layers.removeLayer(layer);
423 int selectIndex = firstIndex - 1;
424 if (firstIndex == 0) {
425 for (int i = firstIndex; i < all.length; ++i)
426 if (all[i] != null) {
431 if (selectIndex >= 0) {
432 viewer.getTree().select(all[selectIndex]);
434 context.getThreadAccess().asyncExec(new Runnable() {
438 if(context.isDisposed()) return;
439 context.getContentContext().setDirty();
446 public void widgetDefaultSelected(SelectionEvent e) {
451 ignoreVisibilityButton = new Button(composite, SWT.CHECK);
452 ignoreVisibilityButton.setText(TEXT_IGNORE_VISIBILITY_SETTINGS);
453 ignoreVisibilityButton.setToolTipText(TOOLTIP_IGNORE_VISIBILITY_SETTINGS);
454 ignoreVisibilityButton.setSelection(layers.getIgnoreVisibilitySettings());
455 ignoreVisibilityButton.addSelectionListener(new SelectionListener() {
458 public void widgetSelected(SelectionEvent e) {
459 layers.setIgnoreVisibilitySettings(!layers.getIgnoreVisibilitySettings());
460 context.getThreadAccess().asyncExec(new Runnable() {
464 if(context.isDisposed()) return;
465 context.getContentContext().setDirty();
472 public void widgetDefaultSelected(SelectionEvent e) {
478 ignoreFocusButton = new Button(composite, SWT.CHECK);
479 ignoreFocusButton.setText(TEXT_IGNORE_FOCUS_SETTINGS);
480 ignoreFocusButton.setToolTipText(TOOLTIP_IGNORE_FOCUS_SETTINGS);
481 ignoreFocusButton.setSelection(layers.getIgnoreFocusSettings());
482 ignoreFocusButton.addSelectionListener(new SelectionListener() {
485 public void widgetSelected(SelectionEvent e) {
486 layers.setIgnoreFocusSettings(!layers.getIgnoreFocusSettings());
487 context.getThreadAccess().asyncExec(new Runnable() {
491 if(context.isDisposed()) return;
492 context.getContentContext().setDirty();
499 public void widgetDefaultSelected(SelectionEvent e) {
505 viewer = new CheckboxTreeViewer(composite, SWT.BORDER | SWT.FULL_SELECTION );
507 GridDataFactory.fillDefaults().grab(true, true).span(4, 1).applyTo(viewer.getControl());
508 viewer.getControl().setToolTipText(Messages.DiagramLayersPage_SelectTT);
509 viewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
510 viewer.getTree().setHeaderVisible(true);
511 editor = new TreeEditor(viewer.getTree());
513 final TreeColumn column1 = new TreeColumn(viewer.getTree(), SWT.LEFT);
514 column1.setText(Messages.DiagramLayersPage_Role);
515 column1.setWidth(100);
516 final TreeColumn column2 = new TreeColumn(viewer.getTree(), SWT.LEFT);
517 column2.setText(Messages.DiagramLayersPage_Show);
518 column2.setWidth(50);
519 final TreeColumn column3 = new TreeColumn(viewer.getTree(), SWT.LEFT);
520 column3.setText(Messages.DiagramLayersPage_Focus);
521 column3.setWidth(50);
522 viewer.getTree().addListener(SWT.Resize, new Listener() {
524 public void handleEvent(Event event) {
525 Tree tree = viewer.getTree();
526 Point size = tree.getSize();
527 int w = Math.max(size.x - 100 - tree.getBorderWidth() * 2, 30);
532 viewer.getTree().addListener(SWT.PaintItem, new Listener() {
533 public void handleEvent(Event event) {
534 if ((event.index == 1 || event.index == 2) && !elements.isEmpty()) {
535 ILayer[] lz = layers.getLayers().toArray(new ILayer[0]);
537 TreeItem item = (TreeItem)event.item;
538 int index = viewer.getTree().indexOf(item);
541 if (event.index == 1)
542 width = (column2.getWidth() - 1);
543 if (event.index == 2)
544 width = (column3.getWidth() - 1);
546 Attribute attribute = Attribute.Visible;
547 if (event.index == 2)
548 attribute = Attribute.Focusable;
549 Tristate state = getJointAttribute(elements, lz[index], attribute);
553 color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_GRAY);
557 color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_RED);
560 color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_GREEN);
563 color = viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_GRAY);
569 Color foreground = gc.getForeground();
570 Color background = gc.getBackground();
571 gc.setBackground(color);
572 gc.setForeground(viewer.getTree().getDisplay().getSystemColor(SWT.COLOR_BLACK));
573 gc.fillRectangle(event.x, event.y, width-1, event.height-1);
574 Rectangle rect2 = new Rectangle(event.x, event.y, width-1, event.height-1);
575 gc.drawRectangle(rect2);
576 gc.setForeground(background);
577 gc.setBackground(foreground);
581 viewer.getTree().addKeyListener(new KeyAdapter() {
583 public void keyPressed(KeyEvent event) {
584 if (event.keyCode == SWT.F2) {
585 // FIXME: Eclipse currently eats F2 presses. This should be
586 // implemented as a command handler or find some way to
587 // force these listeners to have priority...
588 TreeItem[] items = viewer.getTree().getSelection();
589 if(items.length != 1)
592 TreeItem item = items[0];
594 ILayer layer = ISelectionUtils.filterSingleSelection(viewer.getSelection(), ILayer.class);
598 startEditing(layer, item);
602 viewer.getTree().addListener(SWT.MouseDown, new Listener() {
603 public void handleEvent(Event event) {
604 if (viewer.getControl().isDisposed())
607 Point pt = new Point(event.x, event.y);
608 TreeItem item = viewer.getTree().getItem(pt);
612 int index = viewer.getTree().indexOf(item);
613 ILayer[] lz = layers.getLayers().toArray(new ILayer[0]);
614 ILayer layer = lz[index];
616 Rectangle rect = item.getBounds(0);
617 if (event.count == 2 && rect.contains(pt)) {
618 startEditing(layer, item);
622 // Cannot adjust visibility/focusability if no elements are selected.
623 if (elements.isEmpty())
626 rect = item.getBounds(1);
627 if (rect.contains(pt)) {
628 Tristate state = getJointAttribute(elements, layer, Attribute.Visible);
629 setAttribute(elements, layer, Attribute.Visible, state.toggle().toBoolean());
634 Rectangle rect2 = item.getBounds(2);
635 if (rect2.contains(pt)) {
636 Tristate state = getJointAttribute(elements, layer, Attribute.Focusable);
637 setAttribute(elements, layer, Attribute.Focusable, state.toggle().toBoolean());
643 private void refresh() {
644 viewer.getControl().redraw();
645 context.getThreadAccess().asyncExec(new Runnable() {
648 if (context.isDisposed())
650 context.getContentContext().setDirty();
656 viewer.addCheckStateListener(new ICheckStateListener(){
658 public void checkStateChanged(CheckStateChangedEvent event) {
659 ILayer layer = (ILayer)event.getElement();
660 if(event.getChecked()) layers.activate(layer);
661 else layers.deactivate(layer);
662 viewer.setSubtreeChecked(event.getElement(), event.getChecked());
663 context.getThreadAccess().asyncExec(new Runnable() {
667 if(context.isDisposed()) return;
668 context.getContentContext().setDirty();
675 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
677 public void selectionChanged(SelectionChangedEvent event) {
678 ISelection s = event.getSelection();
680 removeButton.setEnabled(false);
682 removeButton.setEnabled(true);
687 viewer.setContentProvider(new ITreeContentProvider(){
689 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
692 public void dispose() {
695 public Object[] getElements(Object inputElement) {
696 return layers.getLayers().toArray();
699 public boolean hasChildren(Object element) {
703 public Object getParent(Object element) {
707 public Object[] getChildren(Object parentElement) {
708 return new Object[0];
711 viewer.setLabelProvider(new CellLabelProvider() {
713 public void update(ViewerCell cell) {
714 if(cell.getColumnIndex() == 0) {
715 ILayer layer = (ILayer)cell.getElement();
716 cell.setText(layer.getName());
718 cell.setText(""); //$NON-NLS-1$
722 viewer.setCheckStateProvider(new ICheckStateProvider() {
724 public boolean isChecked(Object element) {
725 ILayer layer = (ILayer)element;
726 final boolean isActive = layers.isActive(layer);
730 public boolean isGrayed(Object element) {
735 viewer.setInput(this);
737 for(ILayer layer : layers.getVisibleLayers()) {
738 viewer.setSubtreeChecked(layer, true);
745 public Control getControl() {
750 public void setFocus() {
754 public void addSelectionChangedListener(ISelectionChangedListener listener) {
758 public ISelection getSelection() {
763 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
767 public void setSelection(ISelection selection) {
770 private boolean startEditing(final ILayer layer, final TreeItem item/*, final int columnIndex*/) {
772 // Column column = columns[columnIndex];
774 String initialText = layer.getName();
776 final Composite composite = new Composite(viewer.getTree(), SWT.NONE);
777 final Text text = new Text(composite, SWT.BORDER);
778 final int insetX = 0;
779 final int insetY = 0;
780 composite.addListener(SWT.Resize, new Listener() {
781 public void handleEvent(Event e) {
782 Rectangle rect = composite.getClientArea();
783 text.setBounds(rect.x + insetX, rect.y + insetY, rect.width - insetX * 2, rect.height
787 Listener textListener = new Listener() {
788 public void handleEvent(final Event e) {
793 if(layer instanceof IEditableLayer) {
794 IEditableLayer l = (IEditableLayer)layer;
795 String name = findFreshName(layers, text.getText());
800 // // Item may be disposed if the tree gets reset after a previous editing.
801 // if (!item.isDisposed()) {
802 // item.setText(columnIndex, text.getText());
803 // queueSelectionRefresh(context);
806 // // System.out.println("validation error: " + error);
811 // newText = text.getText();
812 // error = modifier.isValid(newText);
813 // if (error != null) {
814 // text.setBackground(invalidModificationColor);
815 // // System.out.println("validation error: " + error);
817 // text.setBackground(null);
821 newText = text.getText();
822 String leftText = newText.substring(0, e.start);
823 String rightText = newText.substring(e.end, newText.length());
824 GC gc = new GC(text);
825 Point size = gc.textExtent(leftText + e.text + rightText);
827 size = text.computeSize(size.x, SWT.DEFAULT);
828 editor.horizontalAlignment = SWT.LEFT;
829 Rectangle itemRect = item.getBounds(0),
830 rect = viewer.getTree().getClientArea();
831 editor.minimumWidth = Math.max(size.x, itemRect.width) + insetX * 2;
832 int left = itemRect.x,
833 right = rect.x + rect.width;
834 editor.minimumWidth = Math.min(editor.minimumWidth, right - left);
835 editor.minimumHeight = size.y + insetY * 2;
840 case SWT.TRAVERSE_RETURN:
841 if(layer instanceof IEditableLayer) {
842 IEditableLayer l = (IEditableLayer)layer;
843 String name = findFreshName(layers, text.getText());
846 //System.out.println("renamed layer to " + text.getText());
849 // error = modifier.isValid(text.getText());
850 // if (error == null) {
851 // modifier.modify(text.getText());
852 // if (!item.isDisposed()) {
853 // item.setText(columnIndex, text.getText());
854 // queueSelectionRefresh(context);
858 case SWT.TRAVERSE_ESCAPE:
863 //System.out.println("unhandled traversal: " + e.detail);
870 text.addListener(SWT.FocusOut, textListener);
871 text.addListener(SWT.Traverse, textListener);
872 text.addListener(SWT.Verify, textListener);
873 text.addListener(SWT.Modify, textListener);
874 editor.setEditor(composite, item, 0);
875 text.setText(initialText);
878 // lastItem[0] = item;