]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/symbollibrary/ui/SymbolLibraryView.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / symbollibrary / ui / SymbolLibraryView.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.symbollibrary.ui;
13
14 import java.awt.datatransfer.Transferable;
15 import java.awt.dnd.DnDConstants;
16 import java.awt.dnd.DragGestureEvent;
17 import java.awt.dnd.DragSourceDragEvent;
18 import java.awt.dnd.DragSourceDropEvent;
19 import java.awt.dnd.DragSourceEvent;
20 import java.lang.ref.SoftReference;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.EnumSet;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.TreeSet;
32 import java.util.WeakHashMap;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.Semaphore;
36 import java.util.concurrent.ThreadFactory;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.atomic.AtomicInteger;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
43 import org.eclipse.core.runtime.preferences.InstanceScope;
44 import org.eclipse.jface.action.Action;
45 import org.eclipse.jface.action.IToolBarManager;
46 import org.eclipse.jface.layout.GridDataFactory;
47 import org.eclipse.jface.layout.GridLayoutFactory;
48 import org.eclipse.jface.resource.FontDescriptor;
49 import org.eclipse.jface.resource.JFaceResources;
50 import org.eclipse.jface.resource.LocalResourceManager;
51 import org.eclipse.jface.viewers.AcceptAllFilter;
52 import org.eclipse.jface.viewers.BaseLabelProvider;
53 import org.eclipse.jface.viewers.IFilter;
54 import org.eclipse.jface.viewers.ISelection;
55 import org.eclipse.jface.viewers.IStructuredContentProvider;
56 import org.eclipse.jface.viewers.StructuredSelection;
57 import org.eclipse.jface.viewers.Viewer;
58 import org.eclipse.jface.viewers.ViewerFilter;
59 import org.eclipse.jface.window.Window;
60 import org.eclipse.nebula.widgets.pgroup.PGroup;
61 import org.eclipse.swt.SWT;
62 import org.eclipse.swt.custom.ScrolledComposite;
63 import org.eclipse.swt.events.ControlAdapter;
64 import org.eclipse.swt.events.ControlEvent;
65 import org.eclipse.swt.events.DisposeEvent;
66 import org.eclipse.swt.events.DisposeListener;
67 import org.eclipse.swt.events.ExpandEvent;
68 import org.eclipse.swt.events.ExpandListener;
69 import org.eclipse.swt.events.ModifyEvent;
70 import org.eclipse.swt.events.ModifyListener;
71 import org.eclipse.swt.graphics.Color;
72 import org.eclipse.swt.graphics.Point;
73 import org.eclipse.swt.graphics.Rectangle;
74 import org.eclipse.swt.layout.GridData;
75 import org.eclipse.swt.widgets.Composite;
76 import org.eclipse.swt.widgets.Control;
77 import org.eclipse.swt.widgets.Event;
78 import org.eclipse.swt.widgets.Listener;
79 import org.eclipse.swt.widgets.Widget;
80 import org.eclipse.ui.IMemento;
81 import org.eclipse.ui.contexts.IContextService;
82 import org.eclipse.ui.part.ViewPart;
83 import org.osgi.service.prefs.BackingStoreException;
84 import org.simantics.db.management.ISessionContextChangedListener;
85 import org.simantics.db.management.ISessionContextProvider;
86 import org.simantics.db.management.SessionContextChangedEvent;
87 import org.simantics.diagram.internal.Activator;
88 import org.simantics.diagram.symbollibrary.IModifiableSymbolGroup;
89 import org.simantics.diagram.symbollibrary.ISymbolGroup;
90 import org.simantics.diagram.symbollibrary.ISymbolGroupListener;
91 import org.simantics.diagram.symbollibrary.ISymbolItem;
92 import org.simantics.diagram.symbollibrary.ISymbolManager;
93 import org.simantics.diagram.symbollibrary.ui.FilterConfiguration.Mode;
94 import org.simantics.diagram.synchronization.ErrorHandler;
95 import org.simantics.diagram.synchronization.LogErrorHandler;
96 import org.simantics.diagram.synchronization.SynchronizationHints;
97 import org.simantics.g2d.canvas.Hints;
98 import org.simantics.g2d.canvas.ICanvasContext;
99 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
100 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
101 import org.simantics.g2d.chassis.AWTChassis;
102 import org.simantics.g2d.diagram.DiagramUtils;
103 import org.simantics.g2d.diagram.handler.PickContext;
104 import org.simantics.g2d.diagram.handler.PickRequest;
105 import org.simantics.g2d.diagram.handler.layout.FlowLayout;
106 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
107 import org.simantics.g2d.diagram.participant.Selection;
108 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
109 import org.simantics.g2d.dnd.IDragSourceParticipant;
110 import org.simantics.g2d.element.ElementClass;
111 import org.simantics.g2d.element.ElementHints;
112 import org.simantics.g2d.element.IElement;
113 import org.simantics.g2d.element.handler.StaticSymbol;
114 import org.simantics.g2d.gallery.GalleryViewer;
115 import org.simantics.g2d.gallery.ILabelProvider;
116 import org.simantics.g2d.image.DefaultImages;
117 import org.simantics.g2d.image.Image;
118 import org.simantics.g2d.image.Image.Feature;
119 import org.simantics.g2d.image.impl.ImageProxy;
120 import org.simantics.g2d.participant.TransformUtil;
121 import org.simantics.project.IProject;
122 import org.simantics.project.ProjectKeys;
123 import org.simantics.scenegraph.INode;
124 import org.simantics.scenegraph.g2d.events.EventTypes;
125 import org.simantics.scenegraph.g2d.events.IEventHandler;
126 import org.simantics.scenegraph.g2d.events.MouseEvent;
127 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
128 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
129 import org.simantics.ui.SimanticsUI;
130 import org.simantics.ui.dnd.LocalObjectTransfer;
131 import org.simantics.ui.dnd.LocalObjectTransferable;
132 import org.simantics.utils.datastructures.cache.ProvisionException;
133 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
134 import org.simantics.utils.datastructures.hints.HintTracker;
135 import org.simantics.utils.datastructures.hints.IHintContext.Key;
136 import org.simantics.utils.datastructures.hints.IHintListener;
137 import org.simantics.utils.datastructures.hints.IHintObservable;
138 import org.simantics.utils.threads.IThreadWorkQueue;
139 import org.simantics.utils.threads.SWTThread;
140 import org.simantics.utils.threads.ThreadUtils;
141 import org.simantics.utils.ui.BundleUtils;
142 import org.simantics.utils.ui.ExceptionUtils;
143 import org.simantics.utils.ui.workbench.StringMemento;
144
145 /**
146  * @author Tuukka Lehtonen
147  */
148 public class SymbolLibraryView extends ViewPart {
149
150     private static final String SYMBOL_LIBRARY_CONTEXT = "org.simantics.diagram.symbolLibrary";
151     private static final String PREF_FILTERS           = "filters";
152     private static final String TAG_FILTER_MODE        = "filterMode";
153     private static final String TAG_FILTER             = "filter";
154
155     private static final String ATTR_ACTIVE            = "active";
156     private static final String ATTR_FILTER_TEXT       = "filterText";
157     private static final String ATTR_NAME              = "name";
158
159     private static final int    FILTER_DELAY           = 500;
160
161     private static final String KEY_VIEWER_INITIALIZED = "viewer.initialized";
162     private static final String KEY_USER_EXPANDED      = "userExpanded";
163     private static final String KEY_GROUP_FILTERED     = "groupFiltered";
164
165     /** Root composite */
166     ScrolledComposite           sc;
167     Composite                   c;
168     ISessionContextProvider     sessionCtxProvider;
169     IThreadWorkQueue            swtThread;
170     boolean                     defaultExpanded = false;
171
172     /**
173      * This value is incremented each time a load method is called and symbol
174      * group population is started. It can be used by
175      * {@link #populateGroups(ExecutorService, Control, Iterator, IFilter)} to
176      * tell whether it should stop its population job because a later load
177      * will override its results anyway.
178      */
179     AtomicInteger                               loadCount              = new AtomicInteger();
180
181     Map<ISymbolGroup, PGroup>                   groups                 = new HashMap<ISymbolGroup, PGroup>();
182     Map<ISymbolGroup, GalleryViewer>            groupViewers           = new HashMap<ISymbolGroup, GalleryViewer>();
183     LocalResourceManager                        resourceManager;
184     FilterArea                                  filter;
185
186     ThreadFactory threadFactory = new ThreadFactory() {
187         @Override
188         public Thread newThread(Runnable r) {
189             Thread t = new Thread(r, "Symbol Library Loader");
190             t.setDaemon(false);
191             t.setPriority(Thread.NORM_PRIORITY);
192             return t;
193         }
194     };
195
196     Semaphore                                   loaderSemaphore        = new Semaphore(1);
197     ExecutorService                             loaderExecutor         = Executors.newCachedThreadPool(threadFactory);
198
199     /**
200      * Used to prevent annoying reloading of symbols when groups are closed and
201      * reopened by not always having to schedule an {@link ImageLoader} in
202      * {@link LabelProvider#getImage(Object)}.
203      */
204     Map<ISymbolItem, SoftReference<ImageProxy>> imageCache = new WeakHashMap<ISymbolItem, SoftReference<ImageProxy>>();
205
206     static final Pattern                        ANY                    = Pattern.compile(".*");
207     Pattern                                     currentFilterPattern   = ANY;
208
209     FilterConfiguration                         config                 = new FilterConfiguration();
210     IFilter                                     currentGroupFilter     = AcceptAllFilter.getInstance();
211
212     ErrorHandler                                errorHandler           = LogErrorHandler.INSTANCE;
213
214     static class GroupDescriptor {
215         public final ISymbolGroup lib;
216         public final String       label;
217         public final PGroup       group;
218
219         public GroupDescriptor(ISymbolGroup lib, String label, PGroup group) {
220             assert(lib != null);
221             assert(label != null);
222             this.lib = lib;
223             this.label = label;
224             this.group = group;
225         }
226     }
227
228     Comparator<GroupDescriptor> groupComparator = new Comparator<GroupDescriptor>() {
229         @Override
230         public int compare(GroupDescriptor o1, GroupDescriptor o2) {
231             return o1.label.compareToIgnoreCase(o2.label);
232         }
233     };
234
235     static final EnumSet<Feature> VOLATILE = EnumSet.of(Feature.Volatile);
236
237     static class PendingImage extends ImageProxy {
238         EnumSet<Feature> features;
239         PendingImage(Image source, EnumSet<Feature> features) {
240             super(source);
241             this.features = features;
242         }
243         @Override
244         public EnumSet<Feature> getFeatures() {
245             return features;
246         }
247     }
248
249     class LabelProvider extends BaseLabelProvider implements ILabelProvider {
250         @Override
251         public Image getImage(final Object element) {
252             ISymbolItem item = (ISymbolItem) element;
253             // Use a volatile ImageProxy to make the image loading asynchronous.
254             ImageProxy proxy = null;
255             SoftReference<ImageProxy> proxyRef = imageCache.get(item);
256             if (proxyRef != null)
257                 proxy = proxyRef.get();
258             if (proxy == null) {
259                 proxy = new PendingImage(DefaultImages.HOURGLASS.get(), VOLATILE);
260                 imageCache.put(item, new SoftReference<ImageProxy>(proxy));
261                 ThreadUtils.getNonBlockingWorkExecutor().schedule(new ImageLoader(proxy, item), 100, TimeUnit.MILLISECONDS);
262             }
263             return proxy;
264         }
265         @Override
266         public String getText(final Object element) {
267             return ((ISymbolItem) element).getName();
268         }
269         @Override
270         public String getToolTipText(Object element) {
271             ISymbolItem item = (ISymbolItem) element;
272             String name = item.getName();
273             String desc = item.getDescription();
274             return name.equals(desc) ? name : name + " - " + desc;
275         }
276
277         @Override
278         public java.awt.Image getToolTipImage(Object object) {
279             return null;
280         }
281         @Override
282         public Color getToolTipBackgroundColor(Object object) {
283             return null;
284         }
285
286         @Override
287         public Color getToolTipForegroundColor(Object object) {
288             return null;
289         }
290     }
291
292     public class ProjectTracker extends HintTracker {
293         public ProjectTracker() {
294             IHintListener symbolGroupListener = new HintListenerAdapter() {
295                 @Override
296                 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
297                     @SuppressWarnings("unchecked")
298                     Collection<ISymbolGroup> groups = (Collection<ISymbolGroup>) newValue;
299                     load(groups);
300                 }
301             };
302             addKeyHintListener(ISymbolManager.KEY_SYMBOL_GROUPS, symbolGroupListener);
303         }
304     }
305     ProjectTracker projectTracker= new ProjectTracker();
306
307     public class SessionContextTracker extends HintTracker implements ISessionContextChangedListener {
308         public SessionContextTracker() {
309             IHintListener activeProjectListener = new HintListenerAdapter() {
310                 @Override
311                 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
312                     projectTracker.track((IProject) newValue);
313                 }
314             };
315             addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener);
316         }
317         @Override
318         public void sessionContextChanged(SessionContextChangedEvent event) {
319             track(event.getNewValue());
320         }
321     }
322     SessionContextTracker sessionContextTracker = new SessionContextTracker();
323
324     void attachToSession() {
325         // Track active ISessionContext changes
326         sessionCtxProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());
327         sessionCtxProvider.addContextChangedListener(sessionContextTracker);
328
329         // Start tracking the current session context for input changes.
330         // This will/must cause applySessionContext to get called.
331         // Doing the applySessionContext initialization this way
332         // instead of directly calling it will also make sure that
333         // applySessionContext is only called once when first initialized,
334         // and not twice like with the direct invocation.
335         sessionContextTracker.track(sessionCtxProvider.getSessionContext());
336     }
337
338     public void readPreferences() {
339         FilterConfiguration config = new FilterConfiguration();
340
341         IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
342         String filters = prefs.get(PREF_FILTERS, null);
343
344         if (filters != null) {
345             IMemento memento = new StringMemento(filters);
346             for (IMemento child : memento.getChildren(TAG_FILTER)) {
347                 String name = child.getString(ATTR_NAME);
348                 String filterText = child.getString(ATTR_FILTER_TEXT);
349                 boolean active = Boolean.TRUE.equals(child.getBoolean(ATTR_ACTIVE));
350
351                 if (name != null && !name.trim().isEmpty() && filterText != null && !filterText.isEmpty()) {
352                     config.getFilters().add(new GroupFilter(name, filterText, active));
353                 }
354             }
355             Collections.sort(config.getFilters());
356
357             String filterMode = memento.getString(TAG_FILTER_MODE);
358             if (filterMode != null) {
359                 try {
360                     Mode mode = Mode.valueOf(filterMode);
361                     config.setMode(mode);
362                 } catch (IllegalArgumentException e) {
363                 }
364             }
365         }
366
367         updateFilterConfiguration(config);
368     }
369
370     void savePreferences() throws BackingStoreException {
371         IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
372
373         IMemento memento = new StringMemento();
374         for (GroupFilter f : config.getFilters()) {
375             IMemento child = memento.createChild(TAG_FILTER);
376             child.putString(ATTR_NAME, f.getName());
377             child.putString(ATTR_FILTER_TEXT, f.getFilterText());
378             child.putBoolean(ATTR_ACTIVE, f.isActive());
379         }
380         memento.putString(TAG_FILTER_MODE, config.getMode().toString());
381
382         prefs.put(PREF_FILTERS, memento.toString());
383
384         prefs.flush();
385     }
386
387     @Override
388     public void createPartControl(final Composite parent) {
389         // Prime the image, make it available.
390         DefaultImages.HOURGLASS.get();
391
392         readPreferences();
393
394         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()), parent);
395         swtThread = SWTThread.getThreadAccess(parent);
396
397         GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(parent);
398
399         filter = new FilterArea(parent, SWT.NONE);
400         GridDataFactory.fillDefaults().grab(true, false).applyTo(filter);
401         filter.getText().addModifyListener(new ModifyListener() {
402             int modCount = 0;
403             //long lastModificationTime = -1000;
404             @Override
405             public void modifyText(ModifyEvent e) {
406                 scheduleDelayedFilter(FILTER_DELAY, TimeUnit.MILLISECONDS);
407             }
408             private void scheduleDelayedFilter(long filterDelay, TimeUnit delayUnit) {
409                 final String text = filter.getText().getText();
410
411                 //long time = System.currentTimeMillis();
412                 //long delta = time - lastModificationTime;
413                 //lastModificationTime = time;
414
415                 final int count = ++modCount;
416                 ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
417                     @Override
418                     public void run() {
419                         int newCount = modCount;
420                         if (newCount != count)
421                             return;
422
423                         ThreadUtils.asyncExec(swtThread, new Runnable() {
424                             @Override
425                             public void run() {
426                                 if (sc.isDisposed())
427                                     return;
428                                 if (!filterGroups(text)) {
429                                     scheduleDelayedFilter(100, TimeUnit.MILLISECONDS);
430                                 }
431                             }
432                         });
433                     }
434                 }, filterDelay, delayUnit);
435             }
436         });
437
438         sc = new ScrolledComposite(parent, SWT.V_SCROLL);
439         GridDataFactory.fillDefaults().grab(true, true).applyTo(sc);
440         sc.setAlwaysShowScrollBars(false);
441         sc.setExpandHorizontal(false);
442         sc.setExpandVertical(false);
443         sc.getVerticalBar().setIncrement(30);
444         sc.getVerticalBar().setPageIncrement(200);
445         sc.addControlListener( new ControlAdapter() {
446             @Override
447             public void controlResized(ControlEvent e) {
448                 //System.out.println("ScrolledComposite resized: " + sc.getSize());
449                 refreshScrolledComposite();
450             }
451         });
452
453         c = new Composite(sc, 0);
454         c.setVisible(false);
455         GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(c);
456
457         sc.setContent(c);
458
459         c.addDisposeListener(new DisposeListener() {
460             @Override
461             public void widgetDisposed(DisposeEvent e) {
462                 // These should be in exactly this order to prevent them from
463                 // screwing each other up.
464                 sessionContextTracker.untrack();
465                 projectTracker.untrack();
466
467                 // Remember to shutdown the executor
468                 loaderExecutor.shutdown();
469             }});
470
471         contributeActions();
472         attachToSession();
473
474         IContextService cs = (IContextService) getSite().getService(IContextService.class);
475         cs.activateContext(SYMBOL_LIBRARY_CONTEXT);
476     }
477
478     @Override
479     public void dispose() {
480         if (sessionCtxProvider != null) {
481             sessionCtxProvider.removeContextChangedListener(sessionContextTracker);
482             sessionCtxProvider = null;
483         }
484     }
485
486     void refreshScrolledComposite() {
487         // Execute asynchronously to give the UI events triggering this method
488         // call time to run through before actually doing any resizing.
489         // Otherwise the result will lag behind reality when scrollbar
490         // visibility is toggled by the toolkit.
491         ThreadUtils.asyncExec(swtThread, new Runnable() {
492             @Override
493             public void run() {
494                 if (sc.isDisposed())
495                     return;
496                 syncRefreshScrolledComposite();
497             }
498         });
499     }
500
501     void syncRefreshScrolledComposite() {
502         // Execute asynchronously to give the UI events triggering this method
503         // call time to run through before actually doing any resizing.
504         // Otherwise the result will lag behind reality when scrollbar
505         // visibility is toggled by the toolkit.
506         Rectangle r = sc.getClientArea();
507         Point contentSize = c.computeSize(r.width, SWT.DEFAULT);
508         //System.out.println("[" + Thread.currentThread() + "] computed content size: " + contentSize + ", " + r);
509         c.setSize(contentSize);
510     }
511
512     /**
513      * (Re-)Load symbol groups, refresh the content
514      */
515     void load(Collection<ISymbolGroup> _libraries) {
516         if (_libraries == null)
517             _libraries = Collections.emptyList();
518         final Collection<ISymbolGroup> libraries = _libraries;
519         loaderExecutor.execute(new Runnable() {
520             @Override
521             public void run() {
522                 // Increment loadCount to signal that a new load cycle is on the way.
523                 Integer loadId = loadCount.incrementAndGet();
524                 try {
525                     loaderSemaphore.acquire();
526                     beginPopulate(loadId);
527                 } catch (InterruptedException e) {
528                     ExceptionUtils.logError(e);
529                 } catch (RuntimeException e) {
530                     loaderSemaphore.release();
531                     ExceptionUtils.logAndShowError(e);
532                 } catch (Error e) {
533                     loaderSemaphore.release();
534                     ExceptionUtils.logAndShowError(e);
535                 }
536             }
537
538             void beginPopulate(Integer loadId) {
539                 synchronized (groups) {
540                     // Must use toArray since groups are removed within the loop
541                     for (Iterator<Map.Entry<ISymbolGroup, PGroup>> it = groups.entrySet().iterator(); it.hasNext();) {
542                         Map.Entry<ISymbolGroup, PGroup> entry = it.next();
543                         if (!libraries.contains(entry.getKey())) {
544                             PGroup group = entry.getValue();
545                             it.remove();
546                             groupViewers.remove(entry.getKey());
547                             if (group != null && !group.isDisposed())
548                                 ThreadUtils.asyncExec(swtThread, disposer(group));
549                         }
550                     }
551                     Set<GroupDescriptor> groupDescs = new TreeSet<GroupDescriptor>(groupComparator);
552                     for (ISymbolGroup lib : libraries) {
553                         PGroup group = groups.get(lib);
554                         //String label = group != null ? group.getText() : lib.getName();
555                         String label = lib.getName();
556                         groupDescs.add(new GroupDescriptor(lib, label, group));
557                     }
558
559                     // Populate all the missing groups.
560                     IFilter groupFilter = currentGroupFilter;
561                     populateGroups(
562                             loaderExecutor,
563                             null,
564                             groupDescs.iterator(),
565                             groupFilter,
566                             loadId,
567                             new Runnable() {
568                                 @Override
569                                 public void run() {
570                                     loaderSemaphore.release();
571                                 }
572                             });
573                 }
574             }
575         });
576     }
577
578     void populateGroups(
579             final ExecutorService exec,
580             final Control lastGroup,
581             final Iterator<GroupDescriptor> iter,
582             final IFilter groupFilter,
583             final Integer loadId,
584             final Runnable loadComplete)
585     {
586         // Check whether to still continue this population or not.
587         int currentLoadId = loadCount.get();
588         if (currentLoadId != loadId) {
589             loadComplete.run();
590             return;
591         }
592
593         if (!iter.hasNext()) {
594             ThreadUtils.asyncExec(swtThread, new Runnable() {
595                 @Override
596                 public void run() {
597                     if (filter.isDisposed() || c.isDisposed())
598                         return;
599                     filter.focus();
600                     c.setVisible(true);
601                 }
602             });
603             loadComplete.run();
604             return;
605         }
606
607         final GroupDescriptor desc = iter.next();
608
609         ThreadUtils.asyncExec(swtThread, new Runnable() {
610             @Override
611             public void run() {
612                 // Must make sure that loadComplete is invoked under error
613                 // circumstances.
614                 try {
615                     populateGroup();
616                 } catch (RuntimeException e) {
617                     loadComplete.run();
618                     ExceptionUtils.logAndShowError(e);
619                 } catch (Error e) {
620                     loadComplete.run();
621                     ExceptionUtils.logAndShowError(e);
622                 }
623             }
624
625             public void populateGroup() {
626                 if (c.isDisposed()) {
627                     loaderSemaphore.release();
628                     return;
629                 }
630                 // $ SWT-begin
631                 //System.out.println("populating: " + desc.label);
632                 PGroup group = desc.group;
633                 if (group == null || group.isDisposed()) {
634
635                     group = new PGroup(c, SWT.NONE);
636 //                    group.addListener(SWT.KeyUp, filterActivationListener);
637 //                    group.addListener(SWT.KeyDown, filterActivationListener);
638 //                    group.addListener(SWT.FocusIn, filterActivationListener);
639 //                    group.addListener(SWT.FocusOut, filterActivationListener);
640 //                    group.addListener(SWT.MouseDown, filterActivationListener);
641 //                    group.addListener(SWT.MouseUp, filterActivationListener);
642 //                    group.addListener(SWT.MouseDoubleClick, filterActivationListener);
643 //                    group.addListener(SWT.Arm, filterActivationListener);
644                     if (lastGroup != null) {
645                         group.moveBelow(lastGroup);
646                     } else {
647                         group.moveAbove(null);
648                     }
649
650                     groups.put(desc.lib, group);
651
652                     group.setData(SymbolLibraryKeys.KEY_GROUP, desc.lib);
653                     group.setData(KEY_USER_EXPANDED, defaultExpanded);
654
655                     group.setExpanded(defaultExpanded);
656                     group.setFont(resourceManager.createFont(FontDescriptor.createFrom(group.getFont()).setStyle(SWT.NORMAL).increaseHeight(-1)));
657                     GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(group);
658                     GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(group);
659                     group.addExpandListener(groupExpandListener);
660
661                     // Track group content changes if possible.
662                     if (desc.lib instanceof IModifiableSymbolGroup) {
663                         IModifiableSymbolGroup mod = (IModifiableSymbolGroup) desc.lib;
664                         mod.addListener(groupListener);
665                     }
666                 }
667
668                 group.setText(desc.label);
669                 group.setToolTipText(desc.label);
670
671                 // Initialize the group: set content provider, label provider and input.
672                 // NOTE: this should not yet start loading any data, just setup the viewer.
673                 // [Tuukka @ 2009-10-24] changed group contents to be
674                 // initialized lazily when needed. See references to initializeGroup(PGroup).
675                 //initializeGroup(group);
676
677                 // Hide the group immediately if necessary.
678                 boolean groupFiltered = !groupFilter.select(desc.label);
679                 group.setData(KEY_GROUP_FILTERED, Boolean.valueOf(groupFiltered));
680                 if (groupFiltered)
681                     setGroupVisible(group, false);
682
683                 syncRefreshScrolledComposite();
684
685                 final PGroup group_ = group;
686                 exec.execute(new Runnable() {
687                     @Override
688                     public void run() {
689                         populateGroups(exec, group_, iter, groupFilter, loadId, loadComplete);
690                     }
691                 });
692             }
693         });
694     }
695
696     /**
697      * @param group
698      * @return <code>null</code> if GalleryViewer is currently being created
699      */
700     GalleryViewer initializeGroup(final PGroup group) {
701         if (group.isDisposed())
702             return null;
703
704         //System.out.println("initializeGroup(" + group + ")");
705
706         synchronized (group) {
707             if (group.getData(KEY_VIEWER_INITIALIZED) != null) {
708                 return (GalleryViewer) group.getData(SymbolLibraryKeys.KEY_GALLERY);
709             }
710             group.setData(KEY_VIEWER_INITIALIZED, Boolean.TRUE);
711         }
712
713         //System.out.println("initializing group: " + group.getText());
714
715         // NOTE: this will wait until the SWT/AWT UI population has completed
716         // and it will dispatch SWT events while waiting for AWT thread population
717         // to complete. This may in turn cause other parties to invoke this same
718         // initializeGroup method with the same group as parameter. This case
719         // needs to be handled appropriately by callers.
720         GalleryViewer viewer = new GalleryViewer(group);
721
722         ISymbolGroup input = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);
723         initializeViewer(group, input, viewer);
724
725         groupViewers.put(input, viewer);
726         group.setData(SymbolLibraryKeys.KEY_GALLERY, viewer);
727
728         //System.out.println("initialized group: " + group.getText());
729
730         return viewer;
731     }
732
733     void initializeViewer(final PGroup group, final ISymbolGroup input, final GalleryViewer viewer) {
734         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(viewer.getControl());
735         viewer.addDragSupport(new DragSourceParticipant());
736         viewer.setAlign(FlowLayout.Align.Left);
737         viewer.getDiagram().setHint(SynchronizationHints.ERROR_HANDLER, errorHandler);
738
739         viewer.setContentProvider(new IStructuredContentProvider() {
740
741             /**
742              * Returns the elements in the input, which must be either an array or a
743              * <code>Collection</code>.
744              */
745             @Override
746             public Object[] getElements(Object inputElement) {
747                 if(inputElement == null) return new Object[0];
748                 return ((ISymbolGroup)inputElement).getItems();
749             }
750
751             /**
752              * This implementation does nothing.
753              */
754             @Override
755             public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
756                 // do nothing.
757             }
758
759             /**
760              * This implementation does nothing.
761              */
762             @Override
763             public void dispose() {
764                 // do nothing.
765             }
766
767         });
768         viewer.setLabelProvider(new LabelProvider());
769         viewer.setInput(input);
770
771         // Add event handler that closes libraries on double clicks into empty
772         // space in library.
773         viewer.getCanvasContext().getEventHandlerStack().add(new IEventHandler() {
774             @Override
775             public int getEventMask() {
776                 return EventTypes.MouseDoubleClickMask;
777             }
778             @Override
779             public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {
780                 if (e instanceof MouseDoubleClickedEvent) {
781                     PickRequest req = new PickRequest(((MouseDoubleClickedEvent) e).controlPosition);
782                     Collection<IElement> result = new ArrayList<IElement>();
783                     DiagramUtils.pick(viewer.getDiagram(), req, result);
784                     if (!result.isEmpty())
785                         return false;
786
787                     //System.out.println("NOTHING CLICKED");
788                     if (group.isDisposed())
789                         return false;
790                     group.getDisplay().asyncExec(new Runnable() {
791                         @Override
792                         public void run() {
793                             if (group.isDisposed())
794                                 return;
795
796                             boolean exp = !group.getExpanded();
797                             group.setData(KEY_USER_EXPANDED, Boolean.valueOf(exp));
798                             setGroupExpandedWithoutNotification(group, exp);
799                             refreshScrolledComposite();
800                         }
801                     });
802                     return true;
803                 }
804                 return false;
805             }
806         }, 0);
807     }
808
809     static String toPatternString(String filter) {
810         if (!filter.isEmpty()) {
811             // Force searching in lowercase.
812             filter = filter.toLowerCase();
813
814             // Construct a regular expression from the specified text.
815             String regExFilter = filter
816             .replace("\\", "\\\\")   // \ -> \\
817             .replace(".", "\\.")     // . -> \.
818             .replace("*", ".*")      // * -> Any 0..n characters
819             .replace("?", ".")       // ? -> Any single character
820             .replace("+", "\\+")     // + -> \+
821             .replace("(", "\\(")     // ( -> \(
822             .replace(")", "\\)")     // ) -> \)
823             .replace("[", "\\[")     // [ -> \[
824             .replace("]", "\\]")     // ] -> \]
825             .replace("{", "\\{")     // { -> \{
826             .replace("}", "\\}")     // } -> \}
827             .replace("^", "\\^")     // ^ -> \^
828             .replace("$", "\\$")     // $ -> \$
829             .replace("|", ".*|")     // $ -> \$
830             //.replace("|", "\\|")     // | -> \|
831             .replace("&&", "\\&&")   // && -> \&&
832             ;
833
834             if (!regExFilter.startsWith(".*"))
835                 regExFilter = ".*" + regExFilter ;
836             if (!regExFilter.endsWith(".*"))
837                 regExFilter += ".*" ;
838
839             return regExFilter;
840         }
841         return null;
842     }
843
844     static class SymbolItemFilter extends ViewerFilter {
845         private final String string;
846         private final Pattern pattern;
847
848         public SymbolItemFilter(String string, Pattern pattern) {
849             this.string = string;
850             this.pattern = pattern;
851         }
852
853         @Override
854         public boolean select(Viewer viewer, Object parentElement, Object element) {
855             ISymbolItem item = (ISymbolItem) element;
856             String name = item.getName();
857             Matcher m = pattern.matcher(name.toLowerCase());
858             //System.out.println(name + ": " + (m.matches() ? "PASS" : "FAIL"));
859             return m.matches();
860         }
861
862         @Override
863         public int hashCode() {
864             return string == null ? 0 : string.hashCode();
865         }
866
867         @Override
868         public boolean equals(Object obj) {
869             if (this == obj)
870                 return true;
871             if (obj == null)
872                 return false;
873             if (getClass() != obj.getClass())
874                 return false;
875             SymbolItemFilter other = (SymbolItemFilter) obj;
876             if (string == null) {
877                 if (other.string != null)
878                     return false;
879             } else if (!string.equals(other.string))
880                 return false;
881             return true;
882         }
883     }
884
885     static Pattern toPattern(String filterText) {
886         String regExFilter = toPatternString(filterText);
887         Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
888         return pattern;
889     }
890
891     static IFilter composeFilter(final FilterConfiguration config) {
892         final Mode mode = config.getMode();
893         final List<Pattern> patterns = new ArrayList<Pattern>();
894         for (GroupFilter f : config.getFilters()) {
895             if (f.isActive())
896                 patterns.add(toPattern(f.getFilterText()));
897         }
898         return new IFilter() {
899             @Override
900             public boolean select(Object toTest) {
901                 if (patterns.isEmpty())
902                     return true;
903
904                 String s = (String) toTest;
905                 switch (mode) {
906                     case AND:
907                         for (Pattern pat : patterns) {
908                             Matcher m = pat.matcher(s.toLowerCase());
909                             //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
910                             if (!m.matches())
911                                 return false;
912                         }
913                         return true;
914                     case OR:
915                         for (Pattern pat : patterns) {
916                             Matcher m = pat.matcher(s.toLowerCase());
917                             //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
918                             if (m.matches())
919                                 return true;
920                         }
921                         return false;
922                     default:
923                         throw new Error("Shouldn't happen");
924                 }
925             }
926         };
927     }
928
929     void updateFilterConfiguration(FilterConfiguration config) {
930         this.config = config;
931         IFilter filter = composeFilter(config);
932         this.currentGroupFilter = filter;
933     }
934
935     void applyGroupFilters() {
936         IFilter groupFilter = this.currentGroupFilter;
937         final boolean[] changed = new boolean[] { false };
938
939         Control[] grps = c.getChildren();
940         for (Control ctrl : grps) {
941             final PGroup grp = (PGroup) ctrl;
942             boolean visible = grp.getVisible();
943             boolean shouldBeVisible = groupFilter.select(grp.getText());
944             boolean change = visible != shouldBeVisible;
945             changed[0] |= change;
946
947             grp.setData(KEY_GROUP_FILTERED, Boolean.valueOf(!shouldBeVisible));
948             if (change) {
949                 setGroupVisible(grp, shouldBeVisible);
950             }
951         }
952
953         ThreadUtils.asyncExec(swtThread, new Runnable() {
954             @Override
955             public void run() {
956                 if (c.isDisposed())
957                     return;
958                 if (changed[0]) {
959                     c.layout(true);
960                     syncRefreshScrolledComposite();
961                 }
962             }
963         });
964     }
965
966     /**
967      * Filters the symbol groups and makes them visible/invisible as necessary.
968      * Invoke only from the SWT thread.
969      * 
970      * @param text the filter text given by the client
971      * @return <code>true</code> if all groups were successfully filtered
972      *         without asynchronous results
973      */
974     boolean filterGroups(String text) {
975         //System.out.println("FILTERING WITH TEXT: " + text);
976
977         String regExFilter = toPatternString(text);
978         Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
979
980         this.currentFilterPattern = pattern;
981         final boolean[] changed = new boolean[] { false };
982         boolean filteringComplete = true;
983
984         ViewerFilter filter = null;
985         if (regExFilter != null)
986             filter = new SymbolItemFilter(regExFilter, pattern);
987
988         Control[] grps = c.getChildren();
989         for (Control ctrl : grps) {
990             final PGroup grp = (PGroup) ctrl;
991             if (grp.isDisposed())
992                 continue;
993             Boolean contentsChanged = filterGroup(grp, filter);
994             if (contentsChanged == null)
995                 filteringComplete = false;
996             else
997                 changed[0] = contentsChanged;
998         }
999
1000         ThreadUtils.asyncExec(swtThread, new Runnable() {
1001             @Override
1002             public void run() {
1003                 if (c.isDisposed())
1004                     return;
1005                 if (changed[0]) {
1006                     c.layout(true);
1007                     syncRefreshScrolledComposite();
1008                 }
1009             }
1010         });
1011
1012         return filteringComplete;
1013     }
1014
1015     static boolean objectEquals(Object o1, Object o2) {
1016         if (o1==o2) return true;
1017         if (o1==null && o2==null) return true;
1018         if (o1==null || o2==null) return false;
1019         return o1.equals(o2);
1020     }
1021
1022     /**
1023      * @param grp
1024      * @return <code>true</code> if the filtering caused changes in the group,
1025      *         <code>false</code> if not, and <code>null</code> if filtering
1026      *         could not be performed yet, meaning results need to be asked
1027      *         later
1028      */
1029     private Boolean filterGroup(PGroup grp, ViewerFilter filter) {
1030         boolean changed = false;
1031         GalleryViewer viewer = initializeGroup(grp);
1032         if (viewer == null)
1033             return null;
1034
1035         ViewerFilter lastFilter = viewer.getFilter();
1036
1037         boolean groupFiltered = Boolean.TRUE.equals(grp.getData(KEY_GROUP_FILTERED));
1038         boolean userExpanded = Boolean.TRUE.equals(grp.getData(KEY_USER_EXPANDED));
1039         final boolean expanded = grp.getExpanded();
1040         final boolean visible = grp.getVisible();
1041         final boolean filterChanged = !objectEquals(filter, lastFilter);
1042
1043         // Find out how much data would be shown with the new filter.
1044         viewer.setFilter(filter);
1045         Object[] elements = viewer.getFilteredElements();
1046
1047         boolean shouldBeVisible = !groupFiltered && elements.length > 0;
1048         boolean shouldBeExpanded = shouldBeVisible && (filter != null || userExpanded);
1049
1050 //        System.out.format("%40s: visible/should be = %5s %5s,  expanded/user expanded/should be = %5s %5s %5s\n",
1051 //                grp.getText(),
1052 //                String.valueOf(visible),
1053 //                String.valueOf(shouldBeVisible),
1054 //                String.valueOf(expanded),
1055 //                String.valueOf(userExpanded),
1056 //                String.valueOf(shouldBeExpanded));
1057
1058         if (filterChanged || visible != shouldBeVisible || expanded != shouldBeExpanded) {
1059             changed = true;
1060
1061             if (shouldBeVisible == userExpanded) {
1062                 if (expanded != shouldBeExpanded)
1063                     setGroupExpandedWithoutNotification(grp, shouldBeExpanded);
1064                 setGroupVisible(grp, shouldBeVisible);
1065             } else {
1066                 if (filter != null) {
1067                     if (shouldBeVisible) {
1068                         // The user has not expanded this group but the group contains
1069                         // stuff that matches the non-empty filter => show the group.
1070                         setGroupExpandedWithoutNotification(grp, true);
1071                         setGroupVisible(grp, true);
1072                     } else {
1073                         // The user has expanded this group but it does not contain items
1074                         // should should be shown with the current non-empty filter => hide the group.
1075                         setGroupExpandedWithoutNotification(grp, true);
1076                         setGroupVisible(grp, false);
1077                     }
1078                 } else {
1079                     // All groups should be visible. Some should be expanded and others not.
1080                     if (expanded != userExpanded)
1081                         setGroupExpandedWithoutNotification(grp, userExpanded);
1082                     if (!visible)
1083                         setGroupVisible(grp, true);
1084                 }
1085             }
1086
1087             if (shouldBeExpanded) {
1088                 viewer.refreshWithContent(elements);
1089             }
1090         }
1091
1092 //        String label = grp.getText();
1093 //        Matcher m = pattern.matcher(label.toLowerCase());
1094 //        boolean visible = m.matches();
1095 //        if (visible != grp.getVisible()) {
1096 //            changed = true;
1097 //            setGroupVisible(grp, visible);
1098 //        }
1099
1100         return changed;
1101     }
1102
1103     void setGroupExpandedWithoutNotification(PGroup grp, boolean expanded) {
1104         // Ok, don't need to remove/add expand listener, PGroup will not notify
1105         // listeners when setExpanded is invoked.
1106         //grp.removeExpandListener(groupExpandListener);
1107         grp.setExpanded(expanded);
1108         //grp.addExpandListener(groupExpandListener);
1109     }
1110
1111     void setGroupVisible(PGroup group, boolean visible) {
1112         GridData gd = (GridData) group.getLayoutData();
1113         gd.exclude = !visible;
1114         group.setVisible(visible);
1115     }
1116
1117     boolean isGroupFiltered(String label) {
1118         return !currentFilterPattern.matcher(label.toLowerCase()).matches();
1119     }
1120
1121     class DragSourceParticipant extends AbstractDiagramParticipant implements IDragSourceParticipant {
1122         @Reference  Selection selection;
1123         @Dependency PointerInteractor pi;
1124         @Dependency TransformUtil util;
1125         @Dependency PickContext pickContext;
1126
1127         @Override
1128         public int canDrag(MouseDragBegin me) {
1129             if (me.button != MouseEvent.LEFT_BUTTON) return 0;
1130             if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return 0;
1131             assertDependencies();
1132
1133             PickRequest         req                     = new PickRequest(me.startCanvasPos);
1134             req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
1135             List<IElement>      picks                   = new ArrayList<IElement>();
1136             pickContext.pick(diagram, req, picks);
1137             Set<IElement>       sel                     = selection.getSelection(me.mouseId);
1138
1139             if (Collections.disjoint(sel, picks)) return 0;
1140             // Box Select
1141             return DnDConstants.ACTION_LINK;
1142         }
1143
1144         @Override
1145         public Transferable dragStart(DragGestureEvent e) {
1146             AWTChassis chassis = (AWTChassis) e.getComponent();
1147             ICanvasContext cc = chassis.getCanvasContext();
1148             Selection sel = cc.getSingleItem(Selection.class);
1149
1150             Set<IElement> ss = sel.getSelection(0);
1151             if (ss.isEmpty()) return null;
1152             Object[] res = new Object[ss.size()];
1153             int index = 0;
1154             for (IElement ee : ss)
1155                 res[index++] = ee.getHint(ElementHints.KEY_OBJECT);
1156
1157             ISelection object = new StructuredSelection(res);
1158
1159             return new LocalObjectTransferable(object);
1160         }
1161
1162         @Override
1163         public int getAllowedOps() {
1164             return DnDConstants.ACTION_COPY;
1165         }
1166         @Override
1167         public void dragDropEnd(DragSourceDropEvent dsde) {
1168 //            System.out.println("dragDropEnd: " + dsde);
1169             LocalObjectTransfer.getTransfer().clear();
1170         }
1171         @Override
1172         public void dragEnter(DragSourceDragEvent dsde) {
1173         }
1174         @Override
1175         public void dragExit(DragSourceEvent dse) {
1176         }
1177         @Override
1178         public void dragOver(DragSourceDragEvent dsde) {
1179         }
1180         @Override
1181         public void dropActionChanged(DragSourceDragEvent dsde) {
1182         }
1183     }
1184
1185     ExpandListener groupExpandListener = new ExpandListener() {
1186         @Override
1187         public void itemCollapsed(ExpandEvent e) {
1188             final PGroup group = (PGroup) e.widget;
1189             group.setData(KEY_USER_EXPANDED, Boolean.FALSE);
1190             //System.out.println("item collapsed: " + group + ", " + sc.getClientArea());
1191             refreshScrolledComposite();
1192         }
1193         @Override
1194         public void itemExpanded(ExpandEvent e) {
1195             final PGroup group = (PGroup) e.widget;
1196             group.setData(KEY_USER_EXPANDED, Boolean.TRUE);
1197             //System.out.println("item expanded: " + group + ", " + sc.getClientArea());
1198             final GalleryViewer viewer = initializeGroup(group);
1199             if (viewer == null)
1200                 return;
1201             ThreadUtils.asyncExec(swtThread, new Runnable() {
1202                 @Override
1203                 public void run() {
1204                     viewer.refresh();
1205                     refreshScrolledComposite();
1206                 }
1207             });
1208         }
1209     };
1210
1211     @Override
1212     public void setFocus() {
1213         c.setFocus();
1214     }
1215
1216     public boolean isDefaultExpanded() {
1217         return defaultExpanded;
1218     }
1219
1220     public void setDefaultExpanded(boolean defaultExpanded) {
1221         this.defaultExpanded = defaultExpanded;
1222     }
1223
1224     Runnable disposer(final Widget w) {
1225         return new Runnable() {
1226             @Override
1227             public void run() {
1228                 if (w.isDisposed())
1229                     return;
1230                 w.dispose();
1231             }
1232         };
1233     }
1234
1235     void contributeActions() {
1236         IToolBarManager toolbar = getViewSite().getActionBars().getToolBarManager();
1237         toolbar.add(new Action("Collapse All",
1238                 BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/collapseall.gif")) {
1239             @Override
1240             public void run() {
1241                 setAllExpandedStates(false);
1242             }
1243         });
1244         toolbar.add(new Action("Expand All",
1245                 BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/expandall.gif")) {
1246             @Override
1247             public void run() {
1248                 setAllExpandedStates(true);
1249             }
1250         });
1251         toolbar.add(new Action("Configure Filters",
1252                 BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/filter_ps.gif")) {
1253             @Override
1254             public void run() {
1255                 FilterConfiguration clone = new FilterConfiguration(config);
1256                 FilterDialog fd = new FilterDialog(getViewSite().getShell(), Activator.getDefault().getDialogSettings(), clone);
1257                 int result = fd.open();
1258                 if (result != Window.OK)
1259                     return;
1260
1261                 updateFilterConfiguration(clone);
1262                 applyGroupFilters();
1263
1264                 try {
1265                     savePreferences();
1266                 } catch (BackingStoreException e) {
1267                     ExceptionUtils.logAndShowError(e);
1268                 }
1269             }
1270         });
1271     }
1272
1273     void setAllExpandedStates(boolean targetState) {
1274         Boolean targetStateObj = Boolean.valueOf(targetState);
1275         boolean changed = false;
1276         setDefaultExpanded(targetState);
1277         Control[] grps = c.getChildren();
1278         //for (final PGroup grp : groups.values().toArray(new PGroup[0])) {
1279         for (Control control : grps) {
1280             final PGroup grp = (PGroup) control;
1281             grp.setData(KEY_USER_EXPANDED, targetStateObj);
1282             if (!grp.isDisposed() && grp.getExpanded() != targetState && grp.getVisible()) {
1283                 final GalleryViewer viewer = initializeGroup(grp);
1284                 setGroupExpandedWithoutNotification(grp, targetState);
1285                 ThreadUtils.asyncExec(swtThread, new Runnable() {
1286                     @Override
1287                     public void run() {
1288                         if (!grp.isDisposed()) {
1289                             if (viewer != null)
1290                                 viewer.refresh();
1291                             refreshScrolledComposite();
1292                         }
1293                     }
1294                 });
1295                 changed = true;
1296             }
1297         }
1298         if (changed)
1299             refreshScrolledComposite();
1300     }
1301
1302     class ImageLoader implements Runnable {
1303
1304         private final ImageProxy  imageProxy;
1305         private final ISymbolItem item;
1306
1307         public ImageLoader(ImageProxy imageProxy, ISymbolItem item) {
1308             this.imageProxy = imageProxy;
1309             this.item = item;
1310         }
1311
1312         @Override
1313         public void run() {
1314             ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
1315                 @Override
1316                 public void run() {
1317                     runBlocking();
1318                 }
1319             });
1320         }
1321
1322         private void runBlocking() {
1323             try {
1324                 IHintObservable hints = null;
1325                 ISymbolGroup group = item.getGroup();
1326                 if (group == null)
1327                     throw new ProvisionException("No ISymbolGroup available for ISymbolItem " + item);
1328
1329                 GalleryViewer viewer = groupViewers.get(group);
1330                 if (viewer == null) {
1331                     // This is normal if this composite has been disposed while these are being ran.
1332                     //throw new ProvisionException("No GalleryViewer available ISymbolGroup " + group);
1333                     imageProxy.setSource(DefaultImages.UNKNOWN2.get());
1334                     return;
1335                 }
1336
1337                 hints = viewer.getDiagram();
1338                 if (hints == null)
1339                     throw new ProvisionException("No diagram available for GalleryViewer of group " + group);
1340
1341                 ElementClass ec = item.getElementClass(hints);
1342                 StaticSymbol ss = ec.getSingleItem(StaticSymbol.class);
1343                 Image source = ss == null ? DefaultImages.UNKNOWN2.get() : ss.getImage();
1344                 imageProxy.setSource(source);
1345             } catch (ProvisionException e) {
1346                 ExceptionUtils.logWarning("Failed to provide element class for symbol item " + item, e);
1347                 imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
1348             } catch (Exception e) {
1349                 ExceptionUtils.logError(e);
1350                 imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
1351             } finally {
1352             }
1353         }
1354     }
1355
1356     public FilterArea getFilterArea() {
1357         return filter;
1358     }
1359
1360     Runnable filterActivator = new Runnable() {
1361         @Override
1362         public void run() {
1363             filter.focus();
1364         }
1365     };
1366     Listener filterActivationListener = new Listener() {
1367         @Override
1368         public void handleEvent(Event event) {
1369             //System.out.println("event: " + event);
1370             filterActivator.run();
1371         }
1372     };
1373
1374     ISymbolGroupListener groupListener = new ISymbolGroupListener() {
1375         @Override
1376         public void itemsChanged(ISymbolGroup group) {
1377             GalleryViewer viewer = groupViewers.get(group);
1378             if (viewer != null) {
1379                 ISymbolItem[] input = group.getItems();
1380                 viewer.setInput(input);
1381             }
1382         }
1383     };
1384
1385     @Override
1386     @SuppressWarnings("rawtypes")
1387     public Object getAdapter(Class adapter) {
1388         // For supporting Scene Graph viewer
1389         if (adapter == INode[].class) {
1390             List<INode> result = new ArrayList<INode>(groupViewers.size());
1391             for (GalleryViewer viewer : groupViewers.values()) {
1392                 result.add(viewer.getCanvasContext().getSceneGraph());
1393             }
1394             return result.toArray(new INode[result.size()]);
1395         }
1396         return super.getAdapter(adapter);
1397     }
1398
1399 }