1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.symbollibrary.ui;
\r
14 import java.awt.datatransfer.StringSelection;
\r
15 import java.awt.datatransfer.Transferable;
\r
16 import java.awt.dnd.DnDConstants;
\r
17 import java.awt.dnd.DragGestureEvent;
\r
18 import java.awt.dnd.DragSourceDragEvent;
\r
19 import java.awt.dnd.DragSourceDropEvent;
\r
20 import java.awt.dnd.DragSourceEvent;
\r
21 import java.lang.ref.SoftReference;
\r
22 import java.util.ArrayList;
\r
23 import java.util.Collection;
\r
24 import java.util.Collections;
\r
25 import java.util.Comparator;
\r
26 import java.util.EnumSet;
\r
27 import java.util.HashMap;
\r
28 import java.util.Iterator;
\r
29 import java.util.List;
\r
30 import java.util.Map;
\r
31 import java.util.Set;
\r
32 import java.util.TreeSet;
\r
33 import java.util.WeakHashMap;
\r
34 import java.util.concurrent.ExecutorService;
\r
35 import java.util.concurrent.Semaphore;
\r
36 import java.util.concurrent.SynchronousQueue;
\r
37 import java.util.concurrent.ThreadFactory;
\r
38 import java.util.concurrent.ThreadPoolExecutor;
\r
39 import java.util.concurrent.TimeUnit;
\r
40 import java.util.concurrent.atomic.AtomicBoolean;
\r
41 import java.util.concurrent.atomic.AtomicInteger;
\r
42 import java.util.regex.Matcher;
\r
43 import java.util.regex.Pattern;
\r
45 import org.eclipse.core.runtime.IAdaptable;
\r
46 import org.eclipse.core.runtime.IStatus;
\r
47 import org.eclipse.core.runtime.Status;
\r
48 import org.eclipse.jface.layout.GridDataFactory;
\r
49 import org.eclipse.jface.layout.GridLayoutFactory;
\r
50 import org.eclipse.jface.resource.FontDescriptor;
\r
51 import org.eclipse.jface.resource.JFaceResources;
\r
52 import org.eclipse.jface.resource.LocalResourceManager;
\r
53 import org.eclipse.jface.viewers.AcceptAllFilter;
\r
54 import org.eclipse.jface.viewers.BaseLabelProvider;
\r
55 import org.eclipse.jface.viewers.IFilter;
\r
56 import org.eclipse.jface.viewers.ISelection;
\r
57 import org.eclipse.jface.viewers.IStructuredContentProvider;
\r
58 import org.eclipse.jface.viewers.StructuredSelection;
\r
59 import org.eclipse.jface.viewers.Viewer;
\r
60 import org.eclipse.jface.viewers.ViewerFilter;
\r
61 import org.eclipse.nebula.widgets.pgroup.PGroup;
\r
62 import org.eclipse.swt.SWT;
\r
63 import org.eclipse.swt.custom.ScrolledComposite;
\r
64 import org.eclipse.swt.events.ControlAdapter;
\r
65 import org.eclipse.swt.events.ControlEvent;
\r
66 import org.eclipse.swt.events.DisposeEvent;
\r
67 import org.eclipse.swt.events.DisposeListener;
\r
68 import org.eclipse.swt.events.ExpandEvent;
\r
69 import org.eclipse.swt.events.ExpandListener;
\r
70 import org.eclipse.swt.events.ModifyEvent;
\r
71 import org.eclipse.swt.events.ModifyListener;
\r
72 import org.eclipse.swt.graphics.Color;
\r
73 import org.eclipse.swt.graphics.Point;
\r
74 import org.eclipse.swt.graphics.Rectangle;
\r
75 import org.eclipse.swt.layout.GridData;
\r
76 import org.eclipse.swt.widgets.Composite;
\r
77 import org.eclipse.swt.widgets.Control;
\r
78 import org.eclipse.swt.widgets.Event;
\r
79 import org.eclipse.swt.widgets.Listener;
\r
80 import org.eclipse.swt.widgets.Widget;
\r
81 import org.simantics.db.ReadGraph;
\r
82 import org.simantics.db.Resource;
\r
83 import org.simantics.db.common.procedure.adapter.ListenerAdapter;
\r
84 import org.simantics.db.common.request.UnaryRead;
\r
85 import org.simantics.db.exception.DatabaseException;
\r
86 import org.simantics.diagram.internal.Activator;
\r
87 import org.simantics.diagram.symbolcontribution.CompositeSymbolGroup;
\r
88 import org.simantics.diagram.symbolcontribution.IIdentifiedObject;
\r
89 import org.simantics.diagram.symbolcontribution.ISymbolProvider;
\r
90 import org.simantics.diagram.symbolcontribution.IdentifiedObject;
\r
91 import org.simantics.diagram.symbolcontribution.SymbolProviderFactory;
\r
92 import org.simantics.diagram.symbollibrary.IModifiableSymbolGroup;
\r
93 import org.simantics.diagram.symbollibrary.ISymbolGroup;
\r
94 import org.simantics.diagram.symbollibrary.ISymbolGroupListener;
\r
95 import org.simantics.diagram.symbollibrary.ISymbolItem;
\r
96 import org.simantics.diagram.symbollibrary.ui.FilterConfiguration.Mode;
\r
97 import org.simantics.diagram.synchronization.ErrorHandler;
\r
98 import org.simantics.diagram.synchronization.LogErrorHandler;
\r
99 import org.simantics.diagram.synchronization.SynchronizationHints;
\r
100 import org.simantics.g2d.canvas.Hints;
\r
101 import org.simantics.g2d.canvas.ICanvasContext;
\r
102 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
103 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
\r
104 import org.simantics.g2d.chassis.AWTChassis;
\r
105 import org.simantics.g2d.diagram.DiagramUtils;
\r
106 import org.simantics.g2d.diagram.handler.PickContext;
\r
107 import org.simantics.g2d.diagram.handler.PickRequest;
\r
108 import org.simantics.g2d.diagram.handler.layout.FlowLayout;
\r
109 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
\r
110 import org.simantics.g2d.diagram.participant.Selection;
\r
111 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
\r
112 import org.simantics.g2d.dnd.IDragSourceParticipant;
\r
113 import org.simantics.g2d.element.ElementClass;
\r
114 import org.simantics.g2d.element.ElementHints;
\r
115 import org.simantics.g2d.element.IElement;
\r
116 import org.simantics.g2d.element.handler.StaticSymbol;
\r
117 import org.simantics.g2d.event.adapter.SWTMouseEventAdapter;
\r
118 import org.simantics.g2d.gallery.GalleryViewer;
\r
119 import org.simantics.g2d.gallery.ILabelProvider;
\r
120 import org.simantics.g2d.image.DefaultImages;
\r
121 import org.simantics.g2d.image.Image;
\r
122 import org.simantics.g2d.image.Image.Feature;
\r
123 import org.simantics.g2d.image.impl.ImageProxy;
\r
124 import org.simantics.g2d.participant.TransformUtil;
\r
125 import org.simantics.scenegraph.g2d.events.EventTypes;
\r
126 import org.simantics.scenegraph.g2d.events.IEventHandler;
\r
127 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
128 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
\r
129 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
130 import org.simantics.scl.runtime.tuple.Tuple2;
\r
131 import org.simantics.ui.SimanticsUI;
\r
132 import org.simantics.ui.dnd.LocalObjectTransfer;
\r
133 import org.simantics.ui.dnd.LocalObjectTransferable;
\r
134 import org.simantics.ui.dnd.MultiTransferable;
\r
135 import org.simantics.ui.dnd.PlaintextTransfer;
\r
136 import org.simantics.utils.datastructures.cache.ProvisionException;
\r
137 import org.simantics.utils.datastructures.hints.IHintContext;
\r
138 import org.simantics.utils.threads.AWTThread;
\r
139 import org.simantics.utils.threads.IThreadWorkQueue;
\r
140 import org.simantics.utils.threads.SWTThread;
\r
141 import org.simantics.utils.threads.ThreadUtils;
\r
142 import org.simantics.utils.ui.ErrorLogger;
\r
143 import org.simantics.utils.ui.ExceptionUtils;
\r
146 * @author Tuukka Lehtonen
\r
148 public class SymbolLibraryComposite extends Composite {
\r
150 private static final int FILTER_DELAY = 500;
\r
152 private static final String KEY_VIEWER_INITIALIZED = "viewer.initialized";
\r
153 private static final String KEY_USER_EXPANDED = "userExpanded";
\r
154 private static final String KEY_GROUP_FILTERED = "groupFiltered";
\r
156 /** Root composite */
\r
157 ScrolledComposite sc;
\r
159 IThreadWorkQueue swtThread;
\r
160 boolean defaultExpanded = false;
\r
161 ISymbolProvider symbolProvider;
\r
162 AtomicBoolean disposed = new AtomicBoolean(false);
\r
165 * This value is incremented each time a load method is called and symbol
\r
166 * group population is started. It can be used by
\r
167 * {@link #populateGroups(ExecutorService, Control, Iterator, IFilter)} to
\r
168 * tell whether it should stop its population job because a later load
\r
169 * will override its results anyway.
\r
171 AtomicInteger loadCount = new AtomicInteger();
\r
173 Map<ISymbolGroup, PGroup> groups = new HashMap<>();
\r
174 Map<ISymbolGroup, GalleryViewer> groupViewers = new HashMap<>();
\r
175 Map<Object, Boolean> expandedGroups = new HashMap<>();
\r
176 LocalResourceManager resourceManager;
\r
179 ThreadFactory threadFactory = new ThreadFactory() {
\r
181 public Thread newThread(Runnable r) {
\r
182 Thread t = new Thread(r, "Symbol Library Loader");
\r
183 t.setDaemon(false);
\r
184 t.setPriority(Thread.NORM_PRIORITY);
\r
189 Semaphore loaderSemaphore = new Semaphore(1);
\r
190 ExecutorService loaderExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
\r
191 2L, TimeUnit.SECONDS,
\r
192 new SynchronousQueue<Runnable>(),
\r
196 * Used to prevent annoying reloading of symbols when groups are closed and
\r
197 * reopened by not always having to schedule an {@link ImageLoader} in
\r
198 * {@link LabelProvider#getImage(Object)}.
\r
200 Map<ISymbolItem, SoftReference<ImageProxy>> imageCache = new WeakHashMap<ISymbolItem, SoftReference<ImageProxy>>();
\r
202 static final Pattern ANY = Pattern.compile(".*");
\r
203 Pattern currentFilterPattern = ANY;
\r
205 FilterConfiguration config = new FilterConfiguration();
\r
206 IFilter currentGroupFilter = AcceptAllFilter.getInstance();
\r
208 ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
\r
210 static class GroupDescriptor {
\r
211 public final ISymbolGroup lib;
\r
212 public final String label;
\r
213 public final String description;
\r
214 public final PGroup group;
\r
216 public GroupDescriptor(ISymbolGroup lib, String label, String description, PGroup group) {
\r
217 assert(lib != null);
\r
218 assert(label != null);
\r
220 this.label = label;
\r
221 this.description = description;
\r
222 this.group = group;
\r
226 Comparator<GroupDescriptor> groupComparator = new Comparator<GroupDescriptor>() {
\r
228 public int compare(GroupDescriptor o1, GroupDescriptor o2) {
\r
229 return o1.label.compareToIgnoreCase(o2.label);
\r
233 static final EnumSet<Feature> VOLATILE = EnumSet.of(Feature.Volatile);
\r
235 static class PendingImage extends ImageProxy {
\r
236 EnumSet<Feature> features;
\r
237 PendingImage(Image source, EnumSet<Feature> features) {
\r
239 this.features = features;
\r
242 public EnumSet<Feature> getFeatures() {
\r
247 class LabelProvider extends BaseLabelProvider implements ILabelProvider {
\r
249 public Image getImage(final Object element) {
\r
250 ISymbolItem item = (ISymbolItem) element;
\r
251 // Use a volatile ImageProxy to make the image loading asynchronous.
\r
252 ImageProxy proxy = null;
\r
253 SoftReference<ImageProxy> proxyRef = imageCache.get(item);
\r
254 if (proxyRef != null)
\r
255 proxy = proxyRef.get();
\r
256 if (proxy == null) {
\r
257 proxy = new PendingImage(DefaultImages.HOURGLASS.get(), VOLATILE);
\r
258 imageCache.put(item, new SoftReference<ImageProxy>(proxy));
\r
259 ThreadUtils.getNonBlockingWorkExecutor().schedule(new ImageLoader(proxy, item), 100, TimeUnit.MILLISECONDS);
\r
264 public String getText(final Object element) {
\r
265 return ((ISymbolItem) element).getName();
\r
268 public String getToolTipText(Object element) {
\r
269 ISymbolItem item = (ISymbolItem) element;
\r
270 String name = item.getName();
\r
271 String desc = item.getDescription();
\r
272 return name.equals(desc) ? name : name + " - " + desc;
\r
276 public java.awt.Image getToolTipImage(Object object) {
\r
280 public Color getToolTipBackgroundColor(Object object) {
\r
285 public Color getToolTipForegroundColor(Object object) {
\r
290 public SymbolLibraryComposite(final Composite parent, int style, SymbolProviderFactory symbolProvider) {
\r
291 super(parent, style);
\r
292 init(parent, style);
\r
293 SimanticsUI.getSession().asyncRequest(new CreateSymbolProvider(symbolProvider), new SymbolProviderListener());
\r
294 addDisposeListener(new DisposeListener() {
\r
296 public void widgetDisposed(DisposeEvent e) {
\r
297 disposed.set(true);
\r
305 static class CreateSymbolProvider extends UnaryRead<SymbolProviderFactory, ISymbolProvider> {
\r
306 public CreateSymbolProvider(SymbolProviderFactory factory) {
\r
310 public ISymbolProvider perform(ReadGraph graph) throws DatabaseException {
\r
311 //System.out.println("CreateSymbolProvider.perform: " + parameter);
\r
312 ISymbolProvider provider = parameter.create(graph);
\r
318 @SuppressWarnings("unused")
\r
319 private static void print(ISymbolProvider provider) {
\r
320 for (ISymbolGroup grp : provider.getSymbolGroups()) {
\r
321 System.out.println("GROUP: " + grp);
\r
322 if (grp instanceof CompositeSymbolGroup) {
\r
323 CompositeSymbolGroup cgrp = (CompositeSymbolGroup) grp;
\r
324 for (ISymbolGroup grp2 : cgrp.getGroups()) {
\r
325 System.out.println("\tGROUP: " + grp2);
\r
334 class SymbolProviderListener extends ListenerAdapter<ISymbolProvider> {
\r
336 public void exception(Throwable t) {
\r
337 ErrorLogger.defaultLogError(t);
\r
340 public void execute(ISymbolProvider result) {
\r
341 //System.out.println("SymbolProviderListener: " + result);
\r
342 symbolProvider = result;
\r
343 if (result != null) {
\r
344 Collection<ISymbolGroup> groups = result.getSymbolGroups();
\r
349 public boolean isDisposed() {
\r
350 boolean result = SymbolLibraryComposite.this.isDisposed();
\r
355 private void init(final Composite parent, int style) {
\r
356 GridLayoutFactory.fillDefaults().spacing(0,0).applyTo(this);
\r
357 // setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
\r
359 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(getDisplay()), this);
\r
360 swtThread = SWTThread.getThreadAccess(this);
\r
362 filter = new FilterArea(this, SWT.NONE);
\r
363 GridDataFactory.fillDefaults().grab(true, false).applyTo(filter);
\r
364 filter.getText().addModifyListener(new ModifyListener() {
\r
366 //long lastModificationTime = -1000;
\r
368 public void modifyText(ModifyEvent e) {
\r
369 scheduleDelayedFilter(FILTER_DELAY, TimeUnit.MILLISECONDS);
\r
371 private void scheduleDelayedFilter(long filterDelay, TimeUnit delayUnit) {
\r
372 final String text = filter.getText().getText();
\r
374 //long time = System.currentTimeMillis();
\r
375 //long delta = time - lastModificationTime;
\r
376 //lastModificationTime = time;
\r
378 final int count = ++modCount;
\r
379 ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
\r
381 public void run() {
\r
382 int newCount = modCount;
\r
383 if (newCount != count)
\r
386 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
388 public void run() {
\r
389 if (sc.isDisposed())
\r
391 if (!filterGroups(text)) {
\r
392 scheduleDelayedFilter(100, TimeUnit.MILLISECONDS);
\r
397 }, filterDelay, delayUnit);
\r
401 sc = new ScrolledComposite(this, SWT.V_SCROLL);
\r
402 GridDataFactory.fillDefaults().grab(true, true).applyTo(sc);
\r
403 sc.setAlwaysShowScrollBars(false);
\r
404 sc.setExpandHorizontal(false);
\r
405 sc.setExpandVertical(false);
\r
406 sc.getVerticalBar().setIncrement(30);
\r
407 sc.getVerticalBar().setPageIncrement(200);
\r
408 sc.addControlListener( new ControlAdapter() {
\r
410 public void controlResized(ControlEvent e) {
\r
411 //System.out.println("ScrolledComposite resized: " + sc.getSize());
\r
412 refreshScrolledComposite();
\r
415 //sc.setBackground(sc.getDisplay().getSystemColor(SWT.COLOR_RED));
\r
417 c = new Composite(sc, 0);
\r
418 c.setVisible(false);
\r
419 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(c);
\r
420 //c.setBackground(c.getDisplay().getSystemColor(SWT.COLOR_BLUE));
\r
424 // No event context <-> mouse on empty space in symbol library
\r
425 SWTMouseEventAdapter noContextEventAdapter = new SWTMouseEventAdapter(null, externalEventHandler);
\r
426 installMouseEventAdapter(sc, noContextEventAdapter);
\r
427 installMouseEventAdapter(c, noContextEventAdapter);
\r
429 c.addDisposeListener(new DisposeListener() {
\r
431 public void widgetDisposed(DisposeEvent e) {
\r
432 // Remember to shutdown the executor
\r
433 loaderExecutor.shutdown();
\r
434 groupViewers.clear();
\r
439 void refreshScrolledComposite() {
\r
440 // Execute asynchronously to give the UI events triggering this method
\r
441 // call time to run through before actually doing any resizing.
\r
442 // Otherwise the result will lag behind reality when scrollbar
\r
443 // visibility is toggled by the toolkit.
\r
444 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
446 public void run() {
\r
447 if (sc.isDisposed())
\r
449 syncRefreshScrolledComposite();
\r
454 void syncRefreshScrolledComposite() {
\r
455 // Execute asynchronously to give the UI events triggering this method
\r
456 // call time to run through before actually doing any resizing.
\r
457 // Otherwise the result will lag behind reality when scrollbar
\r
458 // visibility is toggled by the toolkit.
\r
459 Rectangle r = sc.getClientArea();
\r
460 Point contentSize = c.computeSize(r.width, SWT.DEFAULT);
\r
461 //System.out.println("[" + Thread.currentThread() + "] computed content size: " + contentSize + ", " + r);
\r
462 c.setSize(contentSize);
\r
466 * (Re-)Load symbol groups, refresh the content
\r
468 void load(Collection<ISymbolGroup> _libraries) {
\r
469 if (_libraries == null)
\r
470 _libraries = Collections.emptyList();
\r
471 final Collection<ISymbolGroup> libraries = _libraries;
\r
472 if (loaderExecutor.isShutdown())
\r
474 loaderExecutor.execute(new Runnable() {
\r
476 public void run() {
\r
477 // Increment loadCount to signal that a new load cycle is on the way.
\r
478 Integer loadId = loadCount.incrementAndGet();
\r
480 loaderSemaphore.acquire();
\r
481 beginPopulate(loadId);
\r
482 } catch (InterruptedException e) {
\r
483 ExceptionUtils.logError(e);
\r
484 } catch (RuntimeException e) {
\r
485 loaderSemaphore.release();
\r
486 ExceptionUtils.logAndShowError(e);
\r
487 } catch (Error e) {
\r
488 loaderSemaphore.release();
\r
489 ExceptionUtils.logAndShowError(e);
\r
493 void beginPopulate(Integer loadId) {
\r
494 synchronized (groups) {
\r
495 // Must use toArray since groups are removed within the loop
\r
496 for (Iterator<Map.Entry<ISymbolGroup, PGroup>> it = groups.entrySet().iterator(); it.hasNext();) {
\r
497 Map.Entry<ISymbolGroup, PGroup> entry = it.next();
\r
498 if (!libraries.contains(entry.getKey())) {
\r
499 PGroup group = entry.getValue();
\r
501 groupViewers.remove(entry.getKey());
\r
502 if (group != null && !group.isDisposed())
\r
503 ThreadUtils.asyncExec(swtThread, disposer(group));
\r
506 Set<GroupDescriptor> groupDescs = new TreeSet<GroupDescriptor>(groupComparator);
\r
507 for (ISymbolGroup lib : libraries) {
\r
508 PGroup group = groups.get(lib);
\r
509 //String label = group != null ? group.getText() : lib.getName();
\r
510 String label = lib.getName();
\r
511 String description = lib.getDescription();
\r
512 groupDescs.add(new GroupDescriptor(lib, label, description, group));
\r
515 // Populate all the missing groups.
\r
516 IFilter groupFilter = currentGroupFilter;
\r
520 groupDescs.iterator(),
\r
525 public void run() {
\r
526 loaderSemaphore.release();
\r
534 void populateGroups(
\r
535 final ExecutorService exec,
\r
536 final Control lastGroup,
\r
537 final Iterator<GroupDescriptor> iter,
\r
538 final IFilter groupFilter,
\r
539 final Integer loadId,
\r
540 final Runnable loadComplete)
\r
542 // Check whether to still continue this population or not.
\r
543 int currentLoadId = loadCount.get();
\r
544 if (currentLoadId != loadId) {
\r
545 loadComplete.run();
\r
549 if (!iter.hasNext()) {
\r
550 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
552 public void run() {
\r
553 if (filter.isDisposed() || c.isDisposed())
\r
556 c.setVisible(true);
\r
559 loadComplete.run();
\r
563 final GroupDescriptor desc = iter.next();
\r
565 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
567 public void run() {
\r
568 // Must make sure that loadComplete is invoked under error
\r
572 } catch (RuntimeException e) {
\r
573 loadComplete.run();
\r
574 ExceptionUtils.logAndShowError(e);
\r
575 } catch (Error e) {
\r
576 loadComplete.run();
\r
577 ExceptionUtils.logAndShowError(e);
\r
581 public void populateGroup() {
\r
582 if (c.isDisposed()) {
\r
583 loadComplete.run();
\r
587 //System.out.println("populating: " + desc.label);
\r
588 PGroup group = desc.group;
\r
589 Runnable chainedCompletionCallback = loadComplete;
\r
590 if (group == null || group.isDisposed()) {
\r
592 group = new PGroup(c, SWT.NONE);
\r
593 // group.addListener(SWT.KeyUp, filterActivationListener);
\r
594 // group.addListener(SWT.KeyDown, filterActivationListener);
\r
595 // group.addListener(SWT.FocusIn, filterActivationListener);
\r
596 // group.addListener(SWT.FocusOut, filterActivationListener);
\r
597 // group.addListener(SWT.MouseDown, filterActivationListener);
\r
598 // group.addListener(SWT.MouseUp, filterActivationListener);
\r
599 // group.addListener(SWT.MouseDoubleClick, filterActivationListener);
\r
600 // group.addListener(SWT.Arm, filterActivationListener);
\r
601 if (lastGroup != null) {
\r
602 group.moveBelow(lastGroup);
\r
604 group.moveAbove(null);
\r
607 installMouseEventAdapter(group, new SWTMouseEventAdapter(group, externalEventHandler));
\r
609 groups.put(desc.lib, group);
\r
611 Boolean shouldBeExpanded = expandedGroups.get(symbolGroupToKey(desc.lib));
\r
612 if (shouldBeExpanded == null)
\r
613 shouldBeExpanded = defaultExpanded;
\r
614 group.setData(KEY_USER_EXPANDED, shouldBeExpanded);
\r
616 group.setExpanded(shouldBeExpanded);
\r
617 group.setFont(resourceManager.createFont(FontDescriptor.createFrom(group.getFont()).setStyle(SWT.NORMAL).increaseHeight(-1)));
\r
618 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(group);
\r
619 GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(group);
\r
620 group.addExpandListener(groupExpandListener);
\r
622 // Track group content changes if possible.
\r
623 if (desc.lib instanceof IModifiableSymbolGroup) {
\r
624 IModifiableSymbolGroup mod = (IModifiableSymbolGroup) desc.lib;
\r
625 mod.addListener(groupListener);
\r
628 if (shouldBeExpanded) {
\r
629 //System.out.println("WAS EXPANDED(" + desc.label + ", " + symbolGroupToKey(desc.lib) + ", " + shouldBeExpanded + ")");
\r
630 PGroup expandedGroup = group;
\r
631 chainedCompletionCallback = () -> {
\r
632 // Chain callback to expand this group when the loading is otherwise completed.
\r
633 ThreadUtils.asyncExec(swtThread, () -> setExpandedState(expandedGroup, true, true));
\r
634 loadComplete.run();
\r
639 group.setData(SymbolLibraryKeys.KEY_GROUP, desc.lib);
\r
640 group.setText(desc.label);
\r
641 group.setToolTipText(desc.description);
\r
643 // Hide the group immediately if necessary.
\r
644 boolean groupFiltered = !groupFilter.select(desc.label);
\r
645 group.setData(KEY_GROUP_FILTERED, Boolean.valueOf(groupFiltered));
\r
647 setGroupVisible(group, false);
\r
649 syncRefreshScrolledComposite();
\r
651 final PGroup group_ = group;
\r
652 Runnable newCompletionCallback = chainedCompletionCallback;
\r
653 exec.execute(() -> {
\r
654 populateGroups(exec, group_, iter, groupFilter, loadId, newCompletionCallback);
\r
660 protected void installMouseEventAdapter(Control onControl, SWTMouseEventAdapter eventAdapter) {
\r
661 onControl.addMouseListener(eventAdapter);
\r
662 onControl.addMouseTrackListener(eventAdapter);
\r
663 onControl.addMouseMoveListener(eventAdapter);
\r
664 onControl.addMouseWheelListener(eventAdapter);
\r
669 * @return <code>null</code> if GalleryViewer is currently being created
\r
671 GalleryViewer initializeGroup(final PGroup group) {
\r
672 if (group.isDisposed())
\r
675 //System.out.println("initializeGroup(" + group.getText() + ")");
\r
677 synchronized (group) {
\r
678 if (group.getData(KEY_VIEWER_INITIALIZED) != null) {
\r
679 return (GalleryViewer) group.getData(SymbolLibraryKeys.KEY_GALLERY);
\r
681 group.setData(KEY_VIEWER_INITIALIZED, Boolean.TRUE);
\r
684 //System.out.println("initializing group: " + group.getText());
\r
686 // NOTE: this will NOT stop to wait until the SWT/AWT UI
\r
687 // population has been completed.
\r
688 GalleryViewer viewer = new GalleryViewer(group);
\r
690 ISymbolGroup input = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);
\r
691 initializeViewer(group, input, viewer);
\r
693 groupViewers.put(input, viewer);
\r
694 group.setData(SymbolLibraryKeys.KEY_GALLERY, viewer);
\r
696 //System.out.println("initialized group: " + group.getText());
\r
701 void initializeViewer(final PGroup group, final ISymbolGroup input, final GalleryViewer viewer) {
\r
702 GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(viewer.getControl());
\r
703 viewer.addDragSupport(new DragSourceParticipant());
\r
704 viewer.setAlign(FlowLayout.Align.Left);
\r
705 viewer.getDiagram().setHint(SynchronizationHints.ERROR_HANDLER, errorHandler);
\r
707 viewer.setContentProvider(new IStructuredContentProvider() {
\r
710 * Returns the elements in the input, which must be either an array or a
\r
711 * <code>Collection</code>.
\r
714 public Object[] getElements(Object inputElement) {
\r
715 if(inputElement == null) return new Object[0];
\r
716 return ((ISymbolGroup)inputElement).getItems();
\r
720 * This implementation does nothing.
\r
723 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
728 * This implementation does nothing.
\r
731 public void dispose() {
\r
736 viewer.setLabelProvider(new LabelProvider());
\r
737 viewer.setInput(input);
\r
739 // Add event handler that closes libraries on double clicks into empty
\r
740 // space in library.
\r
741 viewer.getCanvasContext().getEventHandlerStack().add(new IEventHandler() {
\r
743 public int getEventMask() {
\r
744 return EventTypes.MouseDoubleClickMask;
\r
748 public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {
\r
749 if (externalEventHandler.handleEvent(e))
\r
752 if (e instanceof MouseDoubleClickedEvent) {
\r
753 PickRequest req = new PickRequest(((MouseDoubleClickedEvent) e).controlPosition);
\r
754 Collection<IElement> result = new ArrayList<IElement>();
\r
755 DiagramUtils.pick(viewer.getDiagram(), req, result);
\r
756 if (!result.isEmpty())
\r
759 //System.out.println("NOTHING CLICKED");
\r
760 if (group.isDisposed())
\r
762 group.getDisplay().asyncExec(() -> {
\r
763 if (group.isDisposed())
\r
766 boolean exp = !group.getExpanded();
\r
767 group.setData(KEY_USER_EXPANDED, Boolean.valueOf(exp));
\r
768 setGroupExpandedWithoutNotification(group, exp);
\r
769 refreshScrolledComposite();
\r
778 static String toPatternString(String filter) {
\r
779 return DefaultFilterStrategy.defaultToPatternString(filter, true);
\r
782 static class SymbolItemFilter extends ViewerFilter {
\r
783 private final String string;
\r
784 private final Matcher m;
\r
786 public SymbolItemFilter(String string, Pattern pattern) {
\r
787 this.string = string;
\r
788 this.m = pattern.matcher("");
\r
792 public boolean select(Viewer viewer, Object parentElement, Object element) {
\r
793 if (element instanceof ISymbolItem) {
\r
794 ISymbolItem item = (ISymbolItem) element;
\r
795 return matchesFilter(item.getName()) || matchesFilter(item.getDescription());
\r
796 } else if (element instanceof ISymbolGroup) {
\r
797 ISymbolGroup group = (ISymbolGroup) element;
\r
798 return matchesFilter(group.getName());
\r
803 private boolean matchesFilter(String str) {
\r
804 m.reset(str.toLowerCase());
\r
805 boolean matches = m.matches();
\r
806 //System.out.println(pattern + ": " + str + ": " + (matches ? "PASS" : "FAIL"));
\r
811 public int hashCode() {
\r
812 return string == null ? 0 : string.hashCode();
\r
816 public boolean equals(Object obj) {
\r
821 if (getClass() != obj.getClass())
\r
823 SymbolItemFilter other = (SymbolItemFilter) obj;
\r
824 if (string == null) {
\r
825 if (other.string != null)
\r
827 } else if (!string.equals(other.string))
\r
833 static Pattern toPattern(String filterText) {
\r
834 String regExFilter = toPatternString(filterText);
\r
835 Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
\r
839 static IFilter composeFilter(final FilterConfiguration config) {
\r
840 final Mode mode = config.getMode();
\r
841 final List<Pattern> patterns = new ArrayList<Pattern>();
\r
842 for (GroupFilter f : config.getFilters()) {
\r
844 patterns.add(toPattern(f.getFilterText()));
\r
846 return new IFilter() {
\r
848 public boolean select(Object toTest) {
\r
849 if (patterns.isEmpty())
\r
852 String s = (String) toTest;
\r
855 for (Pattern pat : patterns) {
\r
856 Matcher m = pat.matcher(s.toLowerCase());
\r
857 //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
\r
863 for (Pattern pat : patterns) {
\r
864 Matcher m = pat.matcher(s.toLowerCase());
\r
865 //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
\r
871 throw new Error("Shouldn't happen");
\r
877 void updateFilterConfiguration(FilterConfiguration config) {
\r
878 this.config = config;
\r
879 IFilter filter = composeFilter(config);
\r
880 this.currentGroupFilter = filter;
\r
883 void applyGroupFilters() {
\r
884 IFilter groupFilter = this.currentGroupFilter;
\r
885 final boolean[] changed = new boolean[] { false };
\r
887 Control[] grps = c.getChildren();
\r
888 for (Control ctrl : grps) {
\r
889 final PGroup grp = (PGroup) ctrl;
\r
890 boolean visible = grp.getVisible();
\r
891 boolean shouldBeVisible = groupFilter.select(grp.getText());
\r
892 boolean change = visible != shouldBeVisible;
\r
893 changed[0] |= change;
\r
895 grp.setData(KEY_GROUP_FILTERED, Boolean.valueOf(!shouldBeVisible));
\r
897 setGroupVisible(grp, shouldBeVisible);
\r
901 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
903 public void run() {
\r
904 if (c.isDisposed())
\r
908 syncRefreshScrolledComposite();
\r
915 * Filters the symbol groups and makes them visible/invisible as necessary.
\r
916 * Invoke only from the SWT thread.
\r
918 * @param text the filter text given by the client
\r
919 * @return <code>true</code> if all groups were successfully filtered
\r
920 * without asynchronous results
\r
922 boolean filterGroups(String text) {
\r
923 //System.out.println("FILTERING WITH TEXT: " + text);
\r
925 String regExFilter = toPatternString(text);
\r
926 Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
\r
928 this.currentFilterPattern = pattern;
\r
929 final boolean[] changed = new boolean[] { false };
\r
930 boolean filteringComplete = true;
\r
932 ViewerFilter filter = null;
\r
933 if (regExFilter != null)
\r
934 filter = new SymbolItemFilter(regExFilter, pattern);
\r
936 Control[] grps = c.getChildren();
\r
937 for (Control ctrl : grps) {
\r
938 final PGroup grp = (PGroup) ctrl;
\r
939 if (grp.isDisposed())
\r
941 Boolean contentsChanged = filterGroup(grp, filter);
\r
942 if (contentsChanged == null)
\r
943 filteringComplete = false;
\r
945 changed[0] = contentsChanged;
\r
948 ThreadUtils.asyncExec(swtThread, new Runnable() {
\r
950 public void run() {
\r
951 if (c.isDisposed())
\r
955 syncRefreshScrolledComposite();
\r
960 return filteringComplete;
\r
963 static boolean objectEquals(Object o1, Object o2) {
\r
964 if (o1==o2) return true;
\r
965 if (o1==null && o2==null) return true;
\r
966 if (o1==null || o2==null) return false;
\r
967 return o1.equals(o2);
\r
972 * @return <code>true</code> if the filtering caused changes in the group,
\r
973 * <code>false</code> if not, and <code>null</code> if filtering
\r
974 * could not be performed yet, meaning results need to be asked
\r
977 private Boolean filterGroup(PGroup grp, ViewerFilter filter) {
\r
978 boolean changed = false;
\r
979 GalleryViewer viewer = initializeGroup(grp);
\r
980 if (viewer == null)
\r
983 ViewerFilter lastFilter = viewer.getFilter();
\r
985 boolean groupFiltered = Boolean.TRUE.equals(grp.getData(KEY_GROUP_FILTERED));
\r
986 boolean userExpanded = Boolean.TRUE.equals(grp.getData(KEY_USER_EXPANDED));
\r
987 final boolean expanded = grp.getExpanded();
\r
988 final boolean visible = grp.getVisible();
\r
989 final boolean filterChanged = !objectEquals(filter, lastFilter);
\r
991 // Find out how much data would be shown with the new filter.
\r
992 viewer.setFilter(filter);
\r
993 Object[] elements = viewer.getFilteredElements();
\r
995 ISymbolGroup symbolGroup = (ISymbolGroup) grp.getData(SymbolLibraryKeys.KEY_GROUP);
\r
996 boolean filterMatchesGroup = filter != null && filter.select(viewer, null, symbolGroup);
\r
997 boolean shouldBeVisible = !groupFiltered && (elements.length > 0 || filterMatchesGroup);
\r
998 boolean shouldBeExpanded = shouldBeVisible && (filter != null || userExpanded);
\r
1000 // System.out.format("%40s: visible/should be = %5s %5s, expanded/user expanded/should be = %5s %5s %5s\n",
\r
1002 // String.valueOf(visible),
\r
1003 // String.valueOf(shouldBeVisible),
\r
1004 // String.valueOf(expanded),
\r
1005 // String.valueOf(userExpanded),
\r
1006 // String.valueOf(shouldBeExpanded));
\r
1008 if (filterChanged || visible != shouldBeVisible || expanded != shouldBeExpanded) {
\r
1011 if (shouldBeVisible == userExpanded) {
\r
1012 if (expanded != shouldBeExpanded)
\r
1013 setGroupExpandedWithoutNotification(grp, shouldBeExpanded);
\r
1014 setGroupVisible(grp, shouldBeVisible);
\r
1016 if (filter != null) {
\r
1017 if (shouldBeVisible) {
\r
1018 // The user has not expanded this group but the group contains
\r
1019 // stuff that matches the non-empty filter => show the group.
\r
1020 setGroupExpandedWithoutNotification(grp, true);
\r
1021 setGroupVisible(grp, true);
\r
1023 // The user has expanded this group but it does not contain items
\r
1024 // should should be shown with the current non-empty filter => hide the group.
\r
1025 setGroupExpandedWithoutNotification(grp, true);
\r
1026 setGroupVisible(grp, false);
\r
1029 // All groups should be visible. Some should be expanded and others not.
\r
1030 if (expanded != userExpanded)
\r
1031 setGroupExpandedWithoutNotification(grp, userExpanded);
\r
1033 setGroupVisible(grp, true);
\r
1037 if (shouldBeExpanded) {
\r
1038 viewer.refreshWithContent(elements);
\r
1042 // String label = grp.getText();
\r
1043 // Matcher m = pattern.matcher(label.toLowerCase());
\r
1044 // boolean visible = m.matches();
\r
1045 // if (visible != grp.getVisible()) {
\r
1046 // changed = true;
\r
1047 // setGroupVisible(grp, visible);
\r
1053 void setGroupExpandedWithoutNotification(PGroup grp, boolean expanded) {
\r
1054 // Ok, don't need to remove/add expand listener, PGroup will not notify
\r
1055 // listeners when setExpanded is invoked.
\r
1056 //grp.removeExpandListener(groupExpandListener);
\r
1057 storeGroupExpandedState(grp, expanded);
\r
1058 grp.setExpanded(expanded);
\r
1059 //grp.addExpandListener(groupExpandListener);
\r
1062 void setGroupVisible(PGroup group, boolean visible) {
\r
1063 GridData gd = (GridData) group.getLayoutData();
\r
1064 gd.exclude = !visible;
\r
1065 group.setVisible(visible);
\r
1068 boolean isGroupFiltered(String label) {
\r
1069 return !currentFilterPattern.matcher(label.toLowerCase()).matches();
\r
1072 class DragSourceParticipant extends AbstractDiagramParticipant implements IDragSourceParticipant {
\r
1073 @Reference Selection selection;
\r
1074 @Dependency PointerInteractor pi;
\r
1075 @Dependency TransformUtil util;
\r
1076 @Dependency PickContext pickContext;
\r
1079 public int canDrag(MouseDragBegin me) {
\r
1080 if (me.button != MouseEvent.LEFT_BUTTON) return 0;
\r
1081 if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return 0;
\r
1082 assertDependencies();
\r
1084 PickRequest req = new PickRequest(me.startCanvasPos);
\r
1085 req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
1086 List<IElement> picks = new ArrayList<IElement>();
\r
1087 pickContext.pick(diagram, req, picks);
\r
1088 Set<IElement> sel = selection.getSelection(me.mouseId);
\r
1090 if (Collections.disjoint(sel, picks)) return 0;
\r
1092 return DnDConstants.ACTION_COPY;
\r
1096 public Transferable dragStart(DragGestureEvent e) {
\r
1098 AWTChassis chassis = (AWTChassis) e.getComponent();
\r
1099 ICanvasContext cc = chassis.getCanvasContext();
\r
1100 Selection sel = cc.getSingleItem(Selection.class);
\r
1102 Set<IElement> ss = sel.getSelection(0);
\r
1103 if (ss.isEmpty()) return null;
\r
1104 Object[] res = new Object[ss.size()];
\r
1106 for (IElement ee : ss)
\r
1107 res[index++] = ee.getHint(ElementHints.KEY_OBJECT);
\r
1109 ISelection object = new StructuredSelection(res);
\r
1111 LocalObjectTransferable local = new LocalObjectTransferable(object);
\r
1113 StringBuilder json = new StringBuilder();
\r
1115 json.append(" \"type\" : \"Symbol\",");
\r
1116 json.append(" \"res\" : [");
\r
1118 for(int i=0;i<res.length;i++) {
\r
1119 if(pos > 0) json.append(",");
\r
1120 Object r = res[i];
\r
1121 if(r instanceof IdentifiedObject) {
\r
1122 Object id = ((IdentifiedObject) r).getId();
\r
1123 if(id instanceof IAdaptable) {
\r
1124 Object resource = ((IAdaptable) id).getAdapter(Resource.class);
\r
1125 if(resource != null) {
\r
1126 long rid = ((Resource)resource).getResourceId();
\r
1127 json.append(Long.toString(rid));
\r
1133 json.append("] }");
\r
1135 StringSelection text = new StringSelection(json.toString());
\r
1136 PlaintextTransfer plainText = new PlaintextTransfer(json.toString());
\r
1138 return new MultiTransferable(local, text, plainText);
\r
1143 public int getAllowedOps() {
\r
1144 return DnDConstants.ACTION_COPY;
\r
1147 public void dragDropEnd(DragSourceDropEvent dsde) {
\r
1148 // System.out.println("dragDropEnd: " + dsde);
\r
1149 LocalObjectTransfer.getTransfer().clear();
\r
1152 public void dragEnter(DragSourceDragEvent dsde) {
\r
1155 public void dragExit(DragSourceEvent dse) {
\r
1158 public void dragOver(DragSourceDragEvent dsde) {
\r
1161 public void dropActionChanged(DragSourceDragEvent dsde) {
\r
1165 ExpandListener groupExpandListener = new ExpandListener() {
\r
1167 public void itemCollapsed(ExpandEvent e) {
\r
1168 final PGroup group = (PGroup) e.widget;
\r
1169 group.setData(KEY_USER_EXPANDED, Boolean.FALSE);
\r
1170 storeGroupExpandedState(group, false);
\r
1171 //System.out.println("item collapsed: " + group + ", " + sc.getClientArea());
\r
1172 refreshScrolledComposite();
\r
1175 public void itemExpanded(ExpandEvent e) {
\r
1176 final PGroup group = (PGroup) e.widget;
\r
1177 group.setData(KEY_USER_EXPANDED, Boolean.TRUE);
\r
1178 storeGroupExpandedState(group, true);
\r
1179 //System.out.println("item expanded: " + group + ", " + sc.getClientArea());
\r
1180 ThreadUtils.asyncExec(swtThread, () -> {
\r
1181 GalleryViewer viewer = initializeGroup(group);
\r
1182 if (viewer == null)
\r
1184 ThreadUtils.asyncExec(swtThread, () -> {
\r
1185 if (viewer.getControl().isDisposed())
\r
1188 refreshScrolledComposite();
\r
1194 public boolean isDefaultExpanded() {
\r
1195 return defaultExpanded;
\r
1198 public void setDefaultExpanded(boolean defaultExpanded) {
\r
1199 this.defaultExpanded = defaultExpanded;
\r
1202 Runnable disposer(final Widget w) {
\r
1203 return new Runnable() {
\r
1205 public void run() {
\r
1206 if (w.isDisposed())
\r
1214 * Invoke from SWT thread only.
\r
1216 * @param targetState
\r
1218 public void setAllExpandedStates(boolean targetState) {
\r
1219 setDefaultExpanded(targetState);
\r
1220 Control[] grps = c.getChildren();
\r
1221 boolean changed = false;
\r
1222 for (Control control : grps)
\r
1223 changed |= setExpandedState((PGroup) control, targetState, false);
\r
1225 refreshScrolledComposite();
\r
1229 * Invoke from SWT thread only.
\r
1232 * @param targetState
\r
1235 boolean setExpandedState(PGroup grp, boolean targetState, boolean force) {
\r
1236 if (grp.isDisposed())
\r
1239 storeGroupExpandedState(grp, targetState);
\r
1240 grp.setData(KEY_USER_EXPANDED, Boolean.valueOf(targetState));
\r
1241 if ((force || grp.getExpanded() != targetState) && grp.getVisible()) {
\r
1242 final GalleryViewer viewer = initializeGroup(grp);
\r
1243 setGroupExpandedWithoutNotification(grp, targetState);
\r
1244 ThreadUtils.asyncExec(swtThread, () -> {
\r
1245 if (!grp.isDisposed()) {
\r
1246 if (viewer != null)
\r
1248 refreshScrolledComposite();
\r
1256 class ImageLoader implements Runnable {
\r
1258 private final ImageProxy imageProxy;
\r
1259 private final ISymbolItem item;
\r
1261 public ImageLoader(ImageProxy imageProxy, ISymbolItem item) {
\r
1262 this.imageProxy = imageProxy;
\r
1267 public void run() {
\r
1268 // SVG images using the SVGUniverse in SVGCache must use
\r
1269 // AWT thread for all operations.
\r
1270 ThreadUtils.asyncExec(AWTThread.getThreadAccess(), () -> runBlocking());
\r
1273 private void runBlocking() {
\r
1275 ISymbolGroup group = item.getGroup();
\r
1276 if (group == null)
\r
1277 throw new ProvisionException("No ISymbolGroup available for ISymbolItem " + item);
\r
1279 GalleryViewer viewer = groupViewers.get(group);
\r
1280 if (viewer == null) {
\r
1281 // This is normal if this composite has been disposed while these are being ran.
\r
1282 //throw new ProvisionException("No GalleryViewer available ISymbolGroup " + group);
\r
1283 imageProxy.setSource(DefaultImages.UNKNOWN2.get());
\r
1287 IHintContext hints = viewer.getDiagram();
\r
1288 if (hints == null)
\r
1289 throw new ProvisionException("No diagram available for GalleryViewer of group " + group);
\r
1291 hints.setHint(ISymbolItem.KEY_ELEMENT_CLASS_LISTENER, new ElementClassListener(imageCache, disposed, item));
\r
1292 final ElementClass ec = item.getElementClass(hints);
\r
1294 // Without this the symbol library will at times
\r
1295 // not update the final graphics for the symbol.
\r
1296 // It will keep displaying the hourglass pending icon instead.
\r
1297 symbolUpdate(disposed, imageProxy, ec);
\r
1298 } catch (ProvisionException e) {
\r
1299 ExceptionUtils.logWarning("Failed to provide element class for symbol item " + item, e);
\r
1300 imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
\r
1301 } catch (Exception e) {
\r
1302 ExceptionUtils.logError(e);
\r
1303 imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
\r
1309 static class ElementClassListener implements org.simantics.db.procedure.Listener<ElementClass> {
\r
1310 private Map<ISymbolItem, SoftReference<ImageProxy>> imageCache;
\r
1311 private final AtomicBoolean disposed;
\r
1312 private final ISymbolItem item;
\r
1314 public ElementClassListener(Map<ISymbolItem, SoftReference<ImageProxy>> imageCache, AtomicBoolean disposed, ISymbolItem item) {
\r
1315 this.imageCache = imageCache;
\r
1316 this.disposed = disposed;
\r
1321 public void execute(final ElementClass ec) {
\r
1322 //System.out.println("SYMBOL CHANGED: " + item + " - disposed=" + disposed + " - " + ec);
\r
1324 final ImageProxy[] imageProxy = { null };
\r
1325 SoftReference<ImageProxy> proxyRef = imageCache.get(item);
\r
1326 if (proxyRef != null)
\r
1327 imageProxy[0] = proxyRef.get();
\r
1328 if (imageProxy[0] != null)
\r
1329 scheduleSymbolUpdate(disposed, imageProxy[0], ec);
\r
1333 public void exception(Throwable t) {
\r
1334 Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error in ElementClass request.", t));
\r
1338 public boolean isDisposed() {
\r
1339 //System.out.println("ElementClassListener.isDisposed " + item + " - " + disposed.get());
\r
1340 return disposed.get();
\r
1344 public FilterArea getFilterArea() {
\r
1348 public static void scheduleSymbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {
\r
1349 if (disposed.get())
\r
1351 ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() {
\r
1353 public void run() {
\r
1354 if (disposed.get())
\r
1356 symbolUpdate(disposed, imageProxy, ec);
\r
1361 public static void symbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {
\r
1362 StaticSymbol ss = ec.getSingleItem(StaticSymbol.class);
\r
1363 Image source = ss == null ? DefaultImages.UNKNOWN2.get() : ss.getImage();
\r
1364 imageProxy.setSource(source);
\r
1367 Runnable filterActivator = new Runnable() {
\r
1369 public void run() {
\r
1373 Listener filterActivationListener = new Listener() {
\r
1375 public void handleEvent(Event event) {
\r
1376 //System.out.println("event: " + event);
\r
1377 filterActivator.run();
\r
1381 ISymbolGroupListener groupListener = new ISymbolGroupListener() {
\r
1383 public void itemsChanged(ISymbolGroup group) {
\r
1384 //System.out.println("symbol group changed: " + group);
\r
1385 GalleryViewer viewer = groupViewers.get(group);
\r
1386 if (viewer != null) {
\r
1387 ISymbolItem[] input = group.getItems();
\r
1388 viewer.setInput(input);
\r
1393 IEventHandler externalEventHandler = new IEventHandler() {
\r
1395 public int getEventMask() {
\r
1396 return EventTypes.AnyMask;
\r
1399 public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {
\r
1400 IEventHandler handler = SymbolLibraryComposite.this.eventHandler;
\r
1401 return handler != null && EventTypes.passes(handler, e) ? handler.handleEvent(e) : false;
\r
1405 protected volatile IEventHandler eventHandler;
\r
1408 * @param eventHandler
\r
1410 public void setEventHandler(IEventHandler eventHandler) {
\r
1411 this.eventHandler = eventHandler;
\r
1414 protected void storeGroupExpandedState(PGroup group, boolean expanded) {
\r
1415 ISymbolGroup symbolGroup = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);
\r
1416 //System.out.println("setGroupExpandedWithoutNotification(" + group + ", " + expanded + ", " + symbolGroup + ")");
\r
1417 if (symbolGroup != null) {
\r
1418 Object key = symbolGroupToKey(symbolGroup);
\r
1419 expandedGroups.put(key, expanded ? Boolean.TRUE : Boolean.FALSE);
\r
1423 private static Object symbolGroupToKey(ISymbolGroup symbolGroup) {
\r
1424 if (symbolGroup instanceof IIdentifiedObject)
\r
1425 return ((IIdentifiedObject) symbolGroup).getId();
\r
1426 return new Tuple2(symbolGroup.getName(), symbolGroup.getDescription());
\r