]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/symbollibrary/ui/SymbolLibraryView.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / symbollibrary / ui / SymbolLibraryView.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/symbollibrary/ui/SymbolLibraryView.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/symbollibrary/ui/SymbolLibraryView.java
new file mode 100644 (file)
index 0000000..beb3408
--- /dev/null
@@ -0,0 +1,1399 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.symbollibrary.ui;\r
+\r
+import java.awt.datatransfer.Transferable;\r
+import java.awt.dnd.DnDConstants;\r
+import java.awt.dnd.DragGestureEvent;\r
+import java.awt.dnd.DragSourceDragEvent;\r
+import java.awt.dnd.DragSourceDropEvent;\r
+import java.awt.dnd.DragSourceEvent;\r
+import java.lang.ref.SoftReference;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.EnumSet;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeSet;\r
+import java.util.WeakHashMap;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.Semaphore;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.TimeUnit;\r
+import java.util.concurrent.atomic.AtomicInteger;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;\r
+import org.eclipse.core.runtime.preferences.InstanceScope;\r
+import org.eclipse.jface.action.Action;\r
+import org.eclipse.jface.action.IToolBarManager;\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.layout.GridLayoutFactory;\r
+import org.eclipse.jface.resource.FontDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.viewers.AcceptAllFilter;\r
+import org.eclipse.jface.viewers.BaseLabelProvider;\r
+import org.eclipse.jface.viewers.IFilter;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.IStructuredContentProvider;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.jface.viewers.ViewerFilter;\r
+import org.eclipse.jface.window.Window;\r
+import org.eclipse.nebula.widgets.pgroup.PGroup;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.custom.ScrolledComposite;\r
+import org.eclipse.swt.events.ControlAdapter;\r
+import org.eclipse.swt.events.ControlEvent;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.ExpandEvent;\r
+import org.eclipse.swt.events.ExpandListener;\r
+import org.eclipse.swt.events.ModifyEvent;\r
+import org.eclipse.swt.events.ModifyListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.layout.GridData;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Listener;\r
+import org.eclipse.swt.widgets.Widget;\r
+import org.eclipse.ui.IMemento;\r
+import org.eclipse.ui.contexts.IContextService;\r
+import org.eclipse.ui.part.ViewPart;\r
+import org.osgi.service.prefs.BackingStoreException;\r
+import org.simantics.db.management.ISessionContextChangedListener;\r
+import org.simantics.db.management.ISessionContextProvider;\r
+import org.simantics.db.management.SessionContextChangedEvent;\r
+import org.simantics.diagram.internal.Activator;\r
+import org.simantics.diagram.symbollibrary.IModifiableSymbolGroup;\r
+import org.simantics.diagram.symbollibrary.ISymbolGroup;\r
+import org.simantics.diagram.symbollibrary.ISymbolGroupListener;\r
+import org.simantics.diagram.symbollibrary.ISymbolItem;\r
+import org.simantics.diagram.symbollibrary.ISymbolManager;\r
+import org.simantics.diagram.symbollibrary.ui.FilterConfiguration.Mode;\r
+import org.simantics.diagram.synchronization.ErrorHandler;\r
+import org.simantics.diagram.synchronization.LogErrorHandler;\r
+import org.simantics.diagram.synchronization.SynchronizationHints;\r
+import org.simantics.g2d.canvas.Hints;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
+import org.simantics.g2d.chassis.AWTChassis;\r
+import org.simantics.g2d.diagram.DiagramUtils;\r
+import org.simantics.g2d.diagram.handler.PickContext;\r
+import org.simantics.g2d.diagram.handler.PickRequest;\r
+import org.simantics.g2d.diagram.handler.layout.FlowLayout;\r
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
+import org.simantics.g2d.diagram.participant.Selection;\r
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;\r
+import org.simantics.g2d.dnd.IDragSourceParticipant;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.StaticSymbol;\r
+import org.simantics.g2d.gallery.GalleryViewer;\r
+import org.simantics.g2d.gallery.ILabelProvider;\r
+import org.simantics.g2d.image.DefaultImages;\r
+import org.simantics.g2d.image.Image;\r
+import org.simantics.g2d.image.Image.Feature;\r
+import org.simantics.g2d.image.impl.ImageProxy;\r
+import org.simantics.g2d.participant.TransformUtil;\r
+import org.simantics.project.IProject;\r
+import org.simantics.project.ProjectKeys;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.g2d.events.EventTypes;\r
+import org.simantics.scenegraph.g2d.events.IEventHandler;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
+import org.simantics.ui.SimanticsUI;\r
+import org.simantics.ui.dnd.LocalObjectTransfer;\r
+import org.simantics.ui.dnd.LocalObjectTransferable;\r
+import org.simantics.utils.datastructures.cache.ProvisionException;\r
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
+import org.simantics.utils.datastructures.hints.HintTracker;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintListener;\r
+import org.simantics.utils.datastructures.hints.IHintObservable;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.ui.BundleUtils;\r
+import org.simantics.utils.ui.ExceptionUtils;\r
+import org.simantics.utils.ui.workbench.StringMemento;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class SymbolLibraryView extends ViewPart {\r
+\r
+    private static final String SYMBOL_LIBRARY_CONTEXT = "org.simantics.diagram.symbolLibrary";\r
+    private static final String PREF_FILTERS           = "filters";\r
+    private static final String TAG_FILTER_MODE        = "filterMode";\r
+    private static final String TAG_FILTER             = "filter";\r
+\r
+    private static final String ATTR_ACTIVE            = "active";\r
+    private static final String ATTR_FILTER_TEXT       = "filterText";\r
+    private static final String ATTR_NAME              = "name";\r
+\r
+    private static final int    FILTER_DELAY           = 500;\r
+\r
+    private static final String KEY_VIEWER_INITIALIZED = "viewer.initialized";\r
+    private static final String KEY_USER_EXPANDED      = "userExpanded";\r
+    private static final String KEY_GROUP_FILTERED     = "groupFiltered";\r
+\r
+    /** Root composite */\r
+    ScrolledComposite           sc;\r
+    Composite                   c;\r
+    ISessionContextProvider     sessionCtxProvider;\r
+    IThreadWorkQueue            swtThread;\r
+    boolean                     defaultExpanded = false;\r
+\r
+    /**\r
+     * This value is incremented each time a load method is called and symbol\r
+     * group population is started. It can be used by\r
+     * {@link #populateGroups(ExecutorService, Control, Iterator, IFilter)} to\r
+     * tell whether it should stop its population job because a later load\r
+     * will override its results anyway.\r
+     */\r
+    AtomicInteger                               loadCount              = new AtomicInteger();\r
+\r
+    Map<ISymbolGroup, PGroup>                   groups                 = new HashMap<ISymbolGroup, PGroup>();\r
+    Map<ISymbolGroup, GalleryViewer>            groupViewers           = new HashMap<ISymbolGroup, GalleryViewer>();\r
+    LocalResourceManager                        resourceManager;\r
+    FilterArea                                  filter;\r
+\r
+    ThreadFactory threadFactory = new ThreadFactory() {\r
+        @Override\r
+        public Thread newThread(Runnable r) {\r
+            Thread t = new Thread(r, "Symbol Library Loader");\r
+            t.setDaemon(false);\r
+            t.setPriority(Thread.NORM_PRIORITY);\r
+            return t;\r
+        }\r
+    };\r
+\r
+    Semaphore                                   loaderSemaphore        = new Semaphore(1);\r
+    ExecutorService                             loaderExecutor         = Executors.newCachedThreadPool(threadFactory);\r
+\r
+    /**\r
+     * Used to prevent annoying reloading of symbols when groups are closed and\r
+     * reopened by not always having to schedule an {@link ImageLoader} in\r
+     * {@link LabelProvider#getImage(Object)}.\r
+     */\r
+    Map<ISymbolItem, SoftReference<ImageProxy>> imageCache = new WeakHashMap<ISymbolItem, SoftReference<ImageProxy>>();\r
+\r
+    static final Pattern                        ANY                    = Pattern.compile(".*");\r
+    Pattern                                     currentFilterPattern   = ANY;\r
+\r
+    FilterConfiguration                         config                 = new FilterConfiguration();\r
+    IFilter                                     currentGroupFilter     = AcceptAllFilter.getInstance();\r
+\r
+    ErrorHandler                                errorHandler           = LogErrorHandler.INSTANCE;\r
+\r
+    static class GroupDescriptor {\r
+        public final ISymbolGroup lib;\r
+        public final String       label;\r
+        public final PGroup       group;\r
+\r
+        public GroupDescriptor(ISymbolGroup lib, String label, PGroup group) {\r
+            assert(lib != null);\r
+            assert(label != null);\r
+            this.lib = lib;\r
+            this.label = label;\r
+            this.group = group;\r
+        }\r
+    }\r
+\r
+    Comparator<GroupDescriptor> groupComparator = new Comparator<GroupDescriptor>() {\r
+        @Override\r
+        public int compare(GroupDescriptor o1, GroupDescriptor o2) {\r
+            return o1.label.compareToIgnoreCase(o2.label);\r
+        }\r
+    };\r
+\r
+    static final EnumSet<Feature> VOLATILE = EnumSet.of(Feature.Volatile);\r
+\r
+    static class PendingImage extends ImageProxy {\r
+        EnumSet<Feature> features;\r
+        PendingImage(Image source, EnumSet<Feature> features) {\r
+            super(source);\r
+            this.features = features;\r
+        }\r
+        @Override\r
+        public EnumSet<Feature> getFeatures() {\r
+            return features;\r
+        }\r
+    }\r
+\r
+    class LabelProvider extends BaseLabelProvider implements ILabelProvider {\r
+        @Override\r
+        public Image getImage(final Object element) {\r
+            ISymbolItem item = (ISymbolItem) element;\r
+            // Use a volatile ImageProxy to make the image loading asynchronous.\r
+            ImageProxy proxy = null;\r
+            SoftReference<ImageProxy> proxyRef = imageCache.get(item);\r
+            if (proxyRef != null)\r
+                proxy = proxyRef.get();\r
+            if (proxy == null) {\r
+                proxy = new PendingImage(DefaultImages.HOURGLASS.get(), VOLATILE);\r
+                imageCache.put(item, new SoftReference<ImageProxy>(proxy));\r
+                ThreadUtils.getNonBlockingWorkExecutor().schedule(new ImageLoader(proxy, item), 100, TimeUnit.MILLISECONDS);\r
+            }\r
+            return proxy;\r
+        }\r
+        @Override\r
+        public String getText(final Object element) {\r
+            return ((ISymbolItem) element).getName();\r
+        }\r
+        @Override\r
+        public String getToolTipText(Object element) {\r
+            ISymbolItem item = (ISymbolItem) element;\r
+            String name = item.getName();\r
+            String desc = item.getDescription();\r
+            return name.equals(desc) ? name : name + " - " + desc;\r
+        }\r
+\r
+        @Override\r
+        public java.awt.Image getToolTipImage(Object object) {\r
+            return null;\r
+        }\r
+        @Override\r
+        public Color getToolTipBackgroundColor(Object object) {\r
+            return null;\r
+        }\r
+\r
+        @Override\r
+        public Color getToolTipForegroundColor(Object object) {\r
+            return null;\r
+        }\r
+    }\r
+\r
+    public class ProjectTracker extends HintTracker {\r
+        public ProjectTracker() {\r
+            IHintListener symbolGroupListener = new HintListenerAdapter() {\r
+                @Override\r
+                public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+                    @SuppressWarnings("unchecked")\r
+                    Collection<ISymbolGroup> groups = (Collection<ISymbolGroup>) newValue;\r
+                    load(groups);\r
+                }\r
+            };\r
+            addKeyHintListener(ISymbolManager.KEY_SYMBOL_GROUPS, symbolGroupListener);\r
+        }\r
+    }\r
+    ProjectTracker projectTracker= new ProjectTracker();\r
+\r
+    public class SessionContextTracker extends HintTracker implements ISessionContextChangedListener {\r
+        public SessionContextTracker() {\r
+            IHintListener activeProjectListener = new HintListenerAdapter() {\r
+                @Override\r
+                public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
+                    projectTracker.track((IProject) newValue);\r
+                }\r
+            };\r
+            addKeyHintListener(ProjectKeys.KEY_PROJECT, activeProjectListener);\r
+        }\r
+        @Override\r
+        public void sessionContextChanged(SessionContextChangedEvent event) {\r
+            track(event.getNewValue());\r
+        }\r
+    }\r
+    SessionContextTracker sessionContextTracker = new SessionContextTracker();\r
+\r
+    void attachToSession() {\r
+        // Track active ISessionContext changes\r
+        sessionCtxProvider = SimanticsUI.getSessionContextProvider(getViewSite().getWorkbenchWindow());\r
+        sessionCtxProvider.addContextChangedListener(sessionContextTracker);\r
+\r
+        // Start tracking the current session context for input changes.\r
+        // This will/must cause applySessionContext to get called.\r
+        // Doing the applySessionContext initialization this way\r
+        // instead of directly calling it will also make sure that\r
+        // applySessionContext is only called once when first initialized,\r
+        // and not twice like with the direct invocation.\r
+        sessionContextTracker.track(sessionCtxProvider.getSessionContext());\r
+    }\r
+\r
+    public void readPreferences() {\r
+        FilterConfiguration config = new FilterConfiguration();\r
+\r
+        IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);\r
+        String filters = prefs.get(PREF_FILTERS, null);\r
+\r
+        if (filters != null) {\r
+            IMemento memento = new StringMemento(filters);\r
+            for (IMemento child : memento.getChildren(TAG_FILTER)) {\r
+                String name = child.getString(ATTR_NAME);\r
+                String filterText = child.getString(ATTR_FILTER_TEXT);\r
+                boolean active = Boolean.TRUE.equals(child.getBoolean(ATTR_ACTIVE));\r
+\r
+                if (name != null && !name.trim().isEmpty() && filterText != null && !filterText.isEmpty()) {\r
+                    config.getFilters().add(new GroupFilter(name, filterText, active));\r
+                }\r
+            }\r
+            Collections.sort(config.getFilters());\r
+\r
+            String filterMode = memento.getString(TAG_FILTER_MODE);\r
+            if (filterMode != null) {\r
+                try {\r
+                    Mode mode = Mode.valueOf(filterMode);\r
+                    config.setMode(mode);\r
+                } catch (IllegalArgumentException e) {\r
+                }\r
+            }\r
+        }\r
+\r
+        updateFilterConfiguration(config);\r
+    }\r
+\r
+    void savePreferences() throws BackingStoreException {\r
+        IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);\r
+\r
+        IMemento memento = new StringMemento();\r
+        for (GroupFilter f : config.getFilters()) {\r
+            IMemento child = memento.createChild(TAG_FILTER);\r
+            child.putString(ATTR_NAME, f.getName());\r
+            child.putString(ATTR_FILTER_TEXT, f.getFilterText());\r
+            child.putBoolean(ATTR_ACTIVE, f.isActive());\r
+        }\r
+        memento.putString(TAG_FILTER_MODE, config.getMode().toString());\r
+\r
+        prefs.put(PREF_FILTERS, memento.toString());\r
+\r
+        prefs.flush();\r
+    }\r
+\r
+    @Override\r
+    public void createPartControl(final Composite parent) {\r
+        // Prime the image, make it available.\r
+        DefaultImages.HOURGLASS.get();\r
+\r
+        readPreferences();\r
+\r
+        this.resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()), parent);\r
+        swtThread = SWTThread.getThreadAccess(parent);\r
+\r
+        GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(parent);\r
+\r
+        filter = new FilterArea(parent, SWT.NONE);\r
+        GridDataFactory.fillDefaults().grab(true, false).applyTo(filter);\r
+        filter.getText().addModifyListener(new ModifyListener() {\r
+            int modCount = 0;\r
+            //long lastModificationTime = -1000;\r
+            @Override\r
+            public void modifyText(ModifyEvent e) {\r
+                scheduleDelayedFilter(FILTER_DELAY, TimeUnit.MILLISECONDS);\r
+            }\r
+            private void scheduleDelayedFilter(long filterDelay, TimeUnit delayUnit) {\r
+                final String text = filter.getText().getText();\r
+\r
+                //long time = System.currentTimeMillis();\r
+                //long delta = time - lastModificationTime;\r
+                //lastModificationTime = time;\r
+\r
+                final int count = ++modCount;\r
+                ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        int newCount = modCount;\r
+                        if (newCount != count)\r
+                            return;\r
+\r
+                        ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+                            @Override\r
+                            public void run() {\r
+                                if (sc.isDisposed())\r
+                                    return;\r
+                                if (!filterGroups(text)) {\r
+                                    scheduleDelayedFilter(100, TimeUnit.MILLISECONDS);\r
+                                }\r
+                            }\r
+                        });\r
+                    }\r
+                }, filterDelay, delayUnit);\r
+            }\r
+        });\r
+\r
+        sc = new ScrolledComposite(parent, SWT.V_SCROLL);\r
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(sc);\r
+        sc.setAlwaysShowScrollBars(false);\r
+        sc.setExpandHorizontal(false);\r
+        sc.setExpandVertical(false);\r
+        sc.getVerticalBar().setIncrement(30);\r
+        sc.getVerticalBar().setPageIncrement(200);\r
+        sc.addControlListener( new ControlAdapter() {\r
+            @Override\r
+            public void controlResized(ControlEvent e) {\r
+                //System.out.println("ScrolledComposite resized: " + sc.getSize());\r
+                refreshScrolledComposite();\r
+            }\r
+        });\r
+\r
+        c = new Composite(sc, 0);\r
+        c.setVisible(false);\r
+        GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(c);\r
+\r
+        sc.setContent(c);\r
+\r
+        c.addDisposeListener(new DisposeListener() {\r
+            @Override\r
+            public void widgetDisposed(DisposeEvent e) {\r
+                // These should be in exactly this order to prevent them from\r
+                // screwing each other up.\r
+                sessionContextTracker.untrack();\r
+                projectTracker.untrack();\r
+\r
+                // Remember to shutdown the executor\r
+                loaderExecutor.shutdown();\r
+            }});\r
+\r
+        contributeActions();\r
+        attachToSession();\r
+\r
+        IContextService cs = (IContextService) getSite().getService(IContextService.class);\r
+        cs.activateContext(SYMBOL_LIBRARY_CONTEXT);\r
+    }\r
+\r
+    @Override\r
+    public void dispose() {\r
+        if (sessionCtxProvider != null) {\r
+            sessionCtxProvider.removeContextChangedListener(sessionContextTracker);\r
+            sessionCtxProvider = null;\r
+        }\r
+    }\r
+\r
+    void refreshScrolledComposite() {\r
+        // Execute asynchronously to give the UI events triggering this method\r
+        // call time to run through before actually doing any resizing.\r
+        // Otherwise the result will lag behind reality when scrollbar\r
+        // visibility is toggled by the toolkit.\r
+        ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                if (sc.isDisposed())\r
+                    return;\r
+                syncRefreshScrolledComposite();\r
+            }\r
+        });\r
+    }\r
+\r
+    void syncRefreshScrolledComposite() {\r
+        // Execute asynchronously to give the UI events triggering this method\r
+        // call time to run through before actually doing any resizing.\r
+        // Otherwise the result will lag behind reality when scrollbar\r
+        // visibility is toggled by the toolkit.\r
+        Rectangle r = sc.getClientArea();\r
+        Point contentSize = c.computeSize(r.width, SWT.DEFAULT);\r
+        //System.out.println("[" + Thread.currentThread() + "] computed content size: " + contentSize + ", " + r);\r
+        c.setSize(contentSize);\r
+    }\r
+\r
+    /**\r
+     * (Re-)Load symbol groups, refresh the content\r
+     */\r
+    void load(Collection<ISymbolGroup> _libraries) {\r
+        if (_libraries == null)\r
+            _libraries = Collections.emptyList();\r
+        final Collection<ISymbolGroup> libraries = _libraries;\r
+        loaderExecutor.execute(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                // Increment loadCount to signal that a new load cycle is on the way.\r
+                Integer loadId = loadCount.incrementAndGet();\r
+                try {\r
+                    loaderSemaphore.acquire();\r
+                    beginPopulate(loadId);\r
+                } catch (InterruptedException e) {\r
+                    ExceptionUtils.logError(e);\r
+                } catch (RuntimeException e) {\r
+                    loaderSemaphore.release();\r
+                    ExceptionUtils.logAndShowError(e);\r
+                } catch (Error e) {\r
+                    loaderSemaphore.release();\r
+                    ExceptionUtils.logAndShowError(e);\r
+                }\r
+            }\r
+\r
+            void beginPopulate(Integer loadId) {\r
+                synchronized (groups) {\r
+                    // Must use toArray since groups are removed within the loop\r
+                    for (Iterator<Map.Entry<ISymbolGroup, PGroup>> it = groups.entrySet().iterator(); it.hasNext();) {\r
+                        Map.Entry<ISymbolGroup, PGroup> entry = it.next();\r
+                        if (!libraries.contains(entry.getKey())) {\r
+                            PGroup group = entry.getValue();\r
+                            it.remove();\r
+                            groupViewers.remove(entry.getKey());\r
+                            if (group != null && !group.isDisposed())\r
+                                ThreadUtils.asyncExec(swtThread, disposer(group));\r
+                        }\r
+                    }\r
+                    Set<GroupDescriptor> groupDescs = new TreeSet<GroupDescriptor>(groupComparator);\r
+                    for (ISymbolGroup lib : libraries) {\r
+                        PGroup group = groups.get(lib);\r
+                        //String label = group != null ? group.getText() : lib.getName();\r
+                        String label = lib.getName();\r
+                        groupDescs.add(new GroupDescriptor(lib, label, group));\r
+                    }\r
+\r
+                    // Populate all the missing groups.\r
+                    IFilter groupFilter = currentGroupFilter;\r
+                    populateGroups(\r
+                            loaderExecutor,\r
+                            null,\r
+                            groupDescs.iterator(),\r
+                            groupFilter,\r
+                            loadId,\r
+                            new Runnable() {\r
+                                @Override\r
+                                public void run() {\r
+                                    loaderSemaphore.release();\r
+                                }\r
+                            });\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    void populateGroups(\r
+            final ExecutorService exec,\r
+            final Control lastGroup,\r
+            final Iterator<GroupDescriptor> iter,\r
+            final IFilter groupFilter,\r
+            final Integer loadId,\r
+            final Runnable loadComplete)\r
+    {\r
+        // Check whether to still continue this population or not.\r
+        int currentLoadId = loadCount.get();\r
+        if (currentLoadId != loadId) {\r
+            loadComplete.run();\r
+            return;\r
+        }\r
+\r
+        if (!iter.hasNext()) {\r
+            ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    if (filter.isDisposed() || c.isDisposed())\r
+                        return;\r
+                    filter.focus();\r
+                    c.setVisible(true);\r
+                }\r
+            });\r
+            loadComplete.run();\r
+            return;\r
+        }\r
+\r
+        final GroupDescriptor desc = iter.next();\r
+\r
+        ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                // Must make sure that loadComplete is invoked under error\r
+                // circumstances.\r
+                try {\r
+                    populateGroup();\r
+                } catch (RuntimeException e) {\r
+                    loadComplete.run();\r
+                    ExceptionUtils.logAndShowError(e);\r
+                } catch (Error e) {\r
+                    loadComplete.run();\r
+                    ExceptionUtils.logAndShowError(e);\r
+                }\r
+            }\r
+\r
+            public void populateGroup() {\r
+                if (c.isDisposed()) {\r
+                    loaderSemaphore.release();\r
+                    return;\r
+                }\r
+                // $ SWT-begin\r
+                //System.out.println("populating: " + desc.label);\r
+                PGroup group = desc.group;\r
+                if (group == null || group.isDisposed()) {\r
+\r
+                    group = new PGroup(c, SWT.NONE);\r
+//                    group.addListener(SWT.KeyUp, filterActivationListener);\r
+//                    group.addListener(SWT.KeyDown, filterActivationListener);\r
+//                    group.addListener(SWT.FocusIn, filterActivationListener);\r
+//                    group.addListener(SWT.FocusOut, filterActivationListener);\r
+//                    group.addListener(SWT.MouseDown, filterActivationListener);\r
+//                    group.addListener(SWT.MouseUp, filterActivationListener);\r
+//                    group.addListener(SWT.MouseDoubleClick, filterActivationListener);\r
+//                    group.addListener(SWT.Arm, filterActivationListener);\r
+                    if (lastGroup != null) {\r
+                        group.moveBelow(lastGroup);\r
+                    } else {\r
+                        group.moveAbove(null);\r
+                    }\r
+\r
+                    groups.put(desc.lib, group);\r
+\r
+                    group.setData(SymbolLibraryKeys.KEY_GROUP, desc.lib);\r
+                    group.setData(KEY_USER_EXPANDED, defaultExpanded);\r
+\r
+                    group.setExpanded(defaultExpanded);\r
+                    group.setFont(resourceManager.createFont(FontDescriptor.createFrom(group.getFont()).setStyle(SWT.NORMAL).increaseHeight(-1)));\r
+                    GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(group);\r
+                    GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(group);\r
+                    group.addExpandListener(groupExpandListener);\r
+\r
+                    // Track group content changes if possible.\r
+                    if (desc.lib instanceof IModifiableSymbolGroup) {\r
+                        IModifiableSymbolGroup mod = (IModifiableSymbolGroup) desc.lib;\r
+                        mod.addListener(groupListener);\r
+                    }\r
+                }\r
+\r
+                group.setText(desc.label);\r
+                group.setToolTipText(desc.label);\r
+\r
+                // Initialize the group: set content provider, label provider and input.\r
+                // NOTE: this should not yet start loading any data, just setup the viewer.\r
+                // [Tuukka @ 2009-10-24] changed group contents to be\r
+                // initialized lazily when needed. See references to initializeGroup(PGroup).\r
+                //initializeGroup(group);\r
+\r
+                // Hide the group immediately if necessary.\r
+                boolean groupFiltered = !groupFilter.select(desc.label);\r
+                group.setData(KEY_GROUP_FILTERED, Boolean.valueOf(groupFiltered));\r
+                if (groupFiltered)\r
+                    setGroupVisible(group, false);\r
+\r
+                syncRefreshScrolledComposite();\r
+\r
+                final PGroup group_ = group;\r
+                exec.execute(new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        populateGroups(exec, group_, iter, groupFilter, loadId, loadComplete);\r
+                    }\r
+                });\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * @param group\r
+     * @return <code>null</code> if GalleryViewer is currently being created\r
+     */\r
+    GalleryViewer initializeGroup(final PGroup group) {\r
+        if (group.isDisposed())\r
+            return null;\r
+\r
+        //System.out.println("initializeGroup(" + group + ")");\r
+\r
+        synchronized (group) {\r
+            if (group.getData(KEY_VIEWER_INITIALIZED) != null) {\r
+                return (GalleryViewer) group.getData(SymbolLibraryKeys.KEY_GALLERY);\r
+            }\r
+            group.setData(KEY_VIEWER_INITIALIZED, Boolean.TRUE);\r
+        }\r
+\r
+        //System.out.println("initializing group: " + group.getText());\r
+\r
+        // NOTE: this will wait until the SWT/AWT UI population has completed\r
+        // and it will dispatch SWT events while waiting for AWT thread population\r
+        // to complete. This may in turn cause other parties to invoke this same\r
+        // initializeGroup method with the same group as parameter. This case\r
+        // needs to be handled appropriately by callers.\r
+        GalleryViewer viewer = new GalleryViewer(group);\r
+\r
+        ISymbolGroup input = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);\r
+        initializeViewer(group, input, viewer);\r
+\r
+        groupViewers.put(input, viewer);\r
+        group.setData(SymbolLibraryKeys.KEY_GALLERY, viewer);\r
+\r
+        //System.out.println("initialized group: " + group.getText());\r
+\r
+        return viewer;\r
+    }\r
+\r
+    void initializeViewer(final PGroup group, final ISymbolGroup input, final GalleryViewer viewer) {\r
+        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(viewer.getControl());\r
+        viewer.addDragSupport(new DragSourceParticipant());\r
+        viewer.setAlign(FlowLayout.Align.Left);\r
+        viewer.getDiagram().setHint(SynchronizationHints.ERROR_HANDLER, errorHandler);\r
+\r
+        viewer.setContentProvider(new IStructuredContentProvider() {\r
+\r
+            /**\r
+             * Returns the elements in the input, which must be either an array or a\r
+             * <code>Collection</code>.\r
+             */\r
+            @Override\r
+            public Object[] getElements(Object inputElement) {\r
+                if(inputElement == null) return new Object[0];\r
+                return ((ISymbolGroup)inputElement).getItems();\r
+            }\r
+\r
+            /**\r
+             * This implementation does nothing.\r
+             */\r
+            @Override\r
+            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+                // do nothing.\r
+            }\r
+\r
+            /**\r
+             * This implementation does nothing.\r
+             */\r
+            @Override\r
+            public void dispose() {\r
+                // do nothing.\r
+            }\r
+\r
+        });\r
+        viewer.setLabelProvider(new LabelProvider());\r
+        viewer.setInput(input);\r
+\r
+        // Add event handler that closes libraries on double clicks into empty\r
+        // space in library.\r
+        viewer.getCanvasContext().getEventHandlerStack().add(new IEventHandler() {\r
+            @Override\r
+            public int getEventMask() {\r
+                return EventTypes.MouseDoubleClickMask;\r
+            }\r
+            @Override\r
+            public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {\r
+                if (e instanceof MouseDoubleClickedEvent) {\r
+                    PickRequest req = new PickRequest(((MouseDoubleClickedEvent) e).controlPosition);\r
+                    Collection<IElement> result = new ArrayList<IElement>();\r
+                    DiagramUtils.pick(viewer.getDiagram(), req, result);\r
+                    if (!result.isEmpty())\r
+                        return false;\r
+\r
+                    //System.out.println("NOTHING CLICKED");\r
+                    if (group.isDisposed())\r
+                        return false;\r
+                    group.getDisplay().asyncExec(new Runnable() {\r
+                        @Override\r
+                        public void run() {\r
+                            if (group.isDisposed())\r
+                                return;\r
+\r
+                            boolean exp = !group.getExpanded();\r
+                            group.setData(KEY_USER_EXPANDED, Boolean.valueOf(exp));\r
+                            setGroupExpandedWithoutNotification(group, exp);\r
+                            refreshScrolledComposite();\r
+                        }\r
+                    });\r
+                    return true;\r
+                }\r
+                return false;\r
+            }\r
+        }, 0);\r
+    }\r
+\r
+    static String toPatternString(String filter) {\r
+        if (!filter.isEmpty()) {\r
+            // Force searching in lowercase.\r
+            filter = filter.toLowerCase();\r
+\r
+            // Construct a regular expression from the specified text.\r
+            String regExFilter = filter\r
+            .replace("\\", "\\\\")   // \ -> \\\r
+            .replace(".", "\\.")     // . -> \.\r
+            .replace("*", ".*")      // * -> Any 0..n characters\r
+            .replace("?", ".")       // ? -> Any single character\r
+            .replace("+", "\\+")     // + -> \+\r
+            .replace("(", "\\(")     // ( -> \(\r
+            .replace(")", "\\)")     // ) -> \)\r
+            .replace("[", "\\[")     // [ -> \[\r
+            .replace("]", "\\]")     // ] -> \]\r
+            .replace("{", "\\{")     // { -> \{\r
+            .replace("}", "\\}")     // } -> \}\r
+            .replace("^", "\\^")     // ^ -> \^\r
+            .replace("$", "\\$")     // $ -> \$\r
+            .replace("|", ".*|")     // $ -> \$\r
+            //.replace("|", "\\|")     // | -> \|\r
+            .replace("&&", "\\&&")   // && -> \&&\r
+            ;\r
+\r
+            if (!regExFilter.startsWith(".*"))\r
+                regExFilter = ".*" + regExFilter ;\r
+            if (!regExFilter.endsWith(".*"))\r
+                regExFilter += ".*" ;\r
+\r
+            return regExFilter;\r
+        }\r
+        return null;\r
+    }\r
+\r
+    static class SymbolItemFilter extends ViewerFilter {\r
+        private final String string;\r
+        private final Pattern pattern;\r
+\r
+        public SymbolItemFilter(String string, Pattern pattern) {\r
+            this.string = string;\r
+            this.pattern = pattern;\r
+        }\r
+\r
+        @Override\r
+        public boolean select(Viewer viewer, Object parentElement, Object element) {\r
+            ISymbolItem item = (ISymbolItem) element;\r
+            String name = item.getName();\r
+            Matcher m = pattern.matcher(name.toLowerCase());\r
+            //System.out.println(name + ": " + (m.matches() ? "PASS" : "FAIL"));\r
+            return m.matches();\r
+        }\r
+\r
+        @Override\r
+        public int hashCode() {\r
+            return string == null ? 0 : string.hashCode();\r
+        }\r
+\r
+        @Override\r
+        public boolean equals(Object obj) {\r
+            if (this == obj)\r
+                return true;\r
+            if (obj == null)\r
+                return false;\r
+            if (getClass() != obj.getClass())\r
+                return false;\r
+            SymbolItemFilter other = (SymbolItemFilter) obj;\r
+            if (string == null) {\r
+                if (other.string != null)\r
+                    return false;\r
+            } else if (!string.equals(other.string))\r
+                return false;\r
+            return true;\r
+        }\r
+    }\r
+\r
+    static Pattern toPattern(String filterText) {\r
+        String regExFilter = toPatternString(filterText);\r
+        Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;\r
+        return pattern;\r
+    }\r
+\r
+    static IFilter composeFilter(final FilterConfiguration config) {\r
+        final Mode mode = config.getMode();\r
+        final List<Pattern> patterns = new ArrayList<Pattern>();\r
+        for (GroupFilter f : config.getFilters()) {\r
+            if (f.isActive())\r
+                patterns.add(toPattern(f.getFilterText()));\r
+        }\r
+        return new IFilter() {\r
+            @Override\r
+            public boolean select(Object toTest) {\r
+                if (patterns.isEmpty())\r
+                    return true;\r
+\r
+                String s = (String) toTest;\r
+                switch (mode) {\r
+                    case AND:\r
+                        for (Pattern pat : patterns) {\r
+                            Matcher m = pat.matcher(s.toLowerCase());\r
+                            //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));\r
+                            if (!m.matches())\r
+                                return false;\r
+                        }\r
+                        return true;\r
+                    case OR:\r
+                        for (Pattern pat : patterns) {\r
+                            Matcher m = pat.matcher(s.toLowerCase());\r
+                            //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));\r
+                            if (m.matches())\r
+                                return true;\r
+                        }\r
+                        return false;\r
+                    default:\r
+                        throw new Error("Shouldn't happen");\r
+                }\r
+            }\r
+        };\r
+    }\r
+\r
+    void updateFilterConfiguration(FilterConfiguration config) {\r
+        this.config = config;\r
+        IFilter filter = composeFilter(config);\r
+        this.currentGroupFilter = filter;\r
+    }\r
+\r
+    void applyGroupFilters() {\r
+        IFilter groupFilter = this.currentGroupFilter;\r
+        final boolean[] changed = new boolean[] { false };\r
+\r
+        Control[] grps = c.getChildren();\r
+        for (Control ctrl : grps) {\r
+            final PGroup grp = (PGroup) ctrl;\r
+            boolean visible = grp.getVisible();\r
+            boolean shouldBeVisible = groupFilter.select(grp.getText());\r
+            boolean change = visible != shouldBeVisible;\r
+            changed[0] |= change;\r
+\r
+            grp.setData(KEY_GROUP_FILTERED, Boolean.valueOf(!shouldBeVisible));\r
+            if (change) {\r
+                setGroupVisible(grp, shouldBeVisible);\r
+            }\r
+        }\r
+\r
+        ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                if (c.isDisposed())\r
+                    return;\r
+                if (changed[0]) {\r
+                    c.layout(true);\r
+                    syncRefreshScrolledComposite();\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    /**\r
+     * Filters the symbol groups and makes them visible/invisible as necessary.\r
+     * Invoke only from the SWT thread.\r
+     * \r
+     * @param text the filter text given by the client\r
+     * @return <code>true</code> if all groups were successfully filtered\r
+     *         without asynchronous results\r
+     */\r
+    boolean filterGroups(String text) {\r
+        //System.out.println("FILTERING WITH TEXT: " + text);\r
+\r
+        String regExFilter = toPatternString(text);\r
+        Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;\r
+\r
+        this.currentFilterPattern = pattern;\r
+        final boolean[] changed = new boolean[] { false };\r
+        boolean filteringComplete = true;\r
+\r
+        ViewerFilter filter = null;\r
+        if (regExFilter != null)\r
+            filter = new SymbolItemFilter(regExFilter, pattern);\r
+\r
+        Control[] grps = c.getChildren();\r
+        for (Control ctrl : grps) {\r
+            final PGroup grp = (PGroup) ctrl;\r
+            if (grp.isDisposed())\r
+                continue;\r
+            Boolean contentsChanged = filterGroup(grp, filter);\r
+            if (contentsChanged == null)\r
+                filteringComplete = false;\r
+            else\r
+                changed[0] = contentsChanged;\r
+        }\r
+\r
+        ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                if (c.isDisposed())\r
+                    return;\r
+                if (changed[0]) {\r
+                    c.layout(true);\r
+                    syncRefreshScrolledComposite();\r
+                }\r
+            }\r
+        });\r
+\r
+        return filteringComplete;\r
+    }\r
+\r
+    static boolean objectEquals(Object o1, Object o2) {\r
+        if (o1==o2) return true;\r
+        if (o1==null && o2==null) return true;\r
+        if (o1==null || o2==null) return false;\r
+        return o1.equals(o2);\r
+    }\r
+\r
+    /**\r
+     * @param grp\r
+     * @return <code>true</code> if the filtering caused changes in the group,\r
+     *         <code>false</code> if not, and <code>null</code> if filtering\r
+     *         could not be performed yet, meaning results need to be asked\r
+     *         later\r
+     */\r
+    private Boolean filterGroup(PGroup grp, ViewerFilter filter) {\r
+        boolean changed = false;\r
+        GalleryViewer viewer = initializeGroup(grp);\r
+        if (viewer == null)\r
+            return null;\r
+\r
+        ViewerFilter lastFilter = viewer.getFilter();\r
+\r
+        boolean groupFiltered = Boolean.TRUE.equals(grp.getData(KEY_GROUP_FILTERED));\r
+        boolean userExpanded = Boolean.TRUE.equals(grp.getData(KEY_USER_EXPANDED));\r
+        final boolean expanded = grp.getExpanded();\r
+        final boolean visible = grp.getVisible();\r
+        final boolean filterChanged = !objectEquals(filter, lastFilter);\r
+\r
+        // Find out how much data would be shown with the new filter.\r
+        viewer.setFilter(filter);\r
+        Object[] elements = viewer.getFilteredElements();\r
+\r
+        boolean shouldBeVisible = !groupFiltered && elements.length > 0;\r
+        boolean shouldBeExpanded = shouldBeVisible && (filter != null || userExpanded);\r
+\r
+//        System.out.format("%40s: visible/should be = %5s %5s,  expanded/user expanded/should be = %5s %5s %5s\n",\r
+//                grp.getText(),\r
+//                String.valueOf(visible),\r
+//                String.valueOf(shouldBeVisible),\r
+//                String.valueOf(expanded),\r
+//                String.valueOf(userExpanded),\r
+//                String.valueOf(shouldBeExpanded));\r
+\r
+        if (filterChanged || visible != shouldBeVisible || expanded != shouldBeExpanded) {\r
+            changed = true;\r
+\r
+            if (shouldBeVisible == userExpanded) {\r
+                if (expanded != shouldBeExpanded)\r
+                    setGroupExpandedWithoutNotification(grp, shouldBeExpanded);\r
+                setGroupVisible(grp, shouldBeVisible);\r
+            } else {\r
+                if (filter != null) {\r
+                    if (shouldBeVisible) {\r
+                        // The user has not expanded this group but the group contains\r
+                        // stuff that matches the non-empty filter => show the group.\r
+                        setGroupExpandedWithoutNotification(grp, true);\r
+                        setGroupVisible(grp, true);\r
+                    } else {\r
+                        // The user has expanded this group but it does not contain items\r
+                        // should should be shown with the current non-empty filter => hide the group.\r
+                        setGroupExpandedWithoutNotification(grp, true);\r
+                        setGroupVisible(grp, false);\r
+                    }\r
+                } else {\r
+                    // All groups should be visible. Some should be expanded and others not.\r
+                    if (expanded != userExpanded)\r
+                        setGroupExpandedWithoutNotification(grp, userExpanded);\r
+                    if (!visible)\r
+                        setGroupVisible(grp, true);\r
+                }\r
+            }\r
+\r
+            if (shouldBeExpanded) {\r
+                viewer.refreshWithContent(elements);\r
+            }\r
+        }\r
+\r
+//        String label = grp.getText();\r
+//        Matcher m = pattern.matcher(label.toLowerCase());\r
+//        boolean visible = m.matches();\r
+//        if (visible != grp.getVisible()) {\r
+//            changed = true;\r
+//            setGroupVisible(grp, visible);\r
+//        }\r
+\r
+        return changed;\r
+    }\r
+\r
+    void setGroupExpandedWithoutNotification(PGroup grp, boolean expanded) {\r
+        // Ok, don't need to remove/add expand listener, PGroup will not notify\r
+        // listeners when setExpanded is invoked.\r
+        //grp.removeExpandListener(groupExpandListener);\r
+        grp.setExpanded(expanded);\r
+        //grp.addExpandListener(groupExpandListener);\r
+    }\r
+\r
+    void setGroupVisible(PGroup group, boolean visible) {\r
+        GridData gd = (GridData) group.getLayoutData();\r
+        gd.exclude = !visible;\r
+        group.setVisible(visible);\r
+    }\r
+\r
+    boolean isGroupFiltered(String label) {\r
+        return !currentFilterPattern.matcher(label.toLowerCase()).matches();\r
+    }\r
+\r
+    class DragSourceParticipant extends AbstractDiagramParticipant implements IDragSourceParticipant {\r
+        @Reference  Selection selection;\r
+        @Dependency PointerInteractor pi;\r
+        @Dependency TransformUtil util;\r
+        @Dependency PickContext pickContext;\r
+\r
+        @Override\r
+        public int canDrag(MouseDragBegin me) {\r
+            if (me.button != MouseEvent.LEFT_BUTTON) return 0;\r
+            if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return 0;\r
+            assertDependencies();\r
+\r
+            PickRequest        req                     = new PickRequest(me.startCanvasPos);\r
+            req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;\r
+            List<IElement>     picks                   = new ArrayList<IElement>();\r
+            pickContext.pick(diagram, req, picks);\r
+            Set<IElement>      sel                     = selection.getSelection(me.mouseId);\r
+\r
+            if (Collections.disjoint(sel, picks)) return 0;\r
+            // Box Select\r
+            return DnDConstants.ACTION_LINK;\r
+        }\r
+\r
+        @Override\r
+        public Transferable dragStart(DragGestureEvent e) {\r
+            AWTChassis chassis = (AWTChassis) e.getComponent();\r
+            ICanvasContext cc = chassis.getCanvasContext();\r
+            Selection sel = cc.getSingleItem(Selection.class);\r
+\r
+            Set<IElement> ss = sel.getSelection(0);\r
+            if (ss.isEmpty()) return null;\r
+            Object[] res = new Object[ss.size()];\r
+            int index = 0;\r
+            for (IElement ee : ss)\r
+                res[index++] = ee.getHint(ElementHints.KEY_OBJECT);\r
+\r
+            ISelection object = new StructuredSelection(res);\r
+\r
+            return new LocalObjectTransferable(object);\r
+        }\r
+\r
+        @Override\r
+        public int getAllowedOps() {\r
+            return DnDConstants.ACTION_COPY;\r
+        }\r
+        @Override\r
+        public void dragDropEnd(DragSourceDropEvent dsde) {\r
+//            System.out.println("dragDropEnd: " + dsde);\r
+            LocalObjectTransfer.getTransfer().clear();\r
+        }\r
+        @Override\r
+        public void dragEnter(DragSourceDragEvent dsde) {\r
+        }\r
+        @Override\r
+        public void dragExit(DragSourceEvent dse) {\r
+        }\r
+        @Override\r
+        public void dragOver(DragSourceDragEvent dsde) {\r
+        }\r
+        @Override\r
+        public void dropActionChanged(DragSourceDragEvent dsde) {\r
+        }\r
+    }\r
+\r
+    ExpandListener groupExpandListener = new ExpandListener() {\r
+        @Override\r
+        public void itemCollapsed(ExpandEvent e) {\r
+            final PGroup group = (PGroup) e.widget;\r
+            group.setData(KEY_USER_EXPANDED, Boolean.FALSE);\r
+            //System.out.println("item collapsed: " + group + ", " + sc.getClientArea());\r
+            refreshScrolledComposite();\r
+        }\r
+        @Override\r
+        public void itemExpanded(ExpandEvent e) {\r
+            final PGroup group = (PGroup) e.widget;\r
+            group.setData(KEY_USER_EXPANDED, Boolean.TRUE);\r
+            //System.out.println("item expanded: " + group + ", " + sc.getClientArea());\r
+            final GalleryViewer viewer = initializeGroup(group);\r
+            if (viewer == null)\r
+                return;\r
+            ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    viewer.refresh();\r
+                    refreshScrolledComposite();\r
+                }\r
+            });\r
+        }\r
+    };\r
+\r
+    @Override\r
+    public void setFocus() {\r
+        c.setFocus();\r
+    }\r
+\r
+    public boolean isDefaultExpanded() {\r
+        return defaultExpanded;\r
+    }\r
+\r
+    public void setDefaultExpanded(boolean defaultExpanded) {\r
+        this.defaultExpanded = defaultExpanded;\r
+    }\r
+\r
+    Runnable disposer(final Widget w) {\r
+        return new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                if (w.isDisposed())\r
+                    return;\r
+                w.dispose();\r
+            }\r
+        };\r
+    }\r
+\r
+    void contributeActions() {\r
+        IToolBarManager toolbar = getViewSite().getActionBars().getToolBarManager();\r
+        toolbar.add(new Action("Collapse All",\r
+                BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/collapseall.gif")) {\r
+            @Override\r
+            public void run() {\r
+                setAllExpandedStates(false);\r
+            }\r
+        });\r
+        toolbar.add(new Action("Expand All",\r
+                BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/expandall.gif")) {\r
+            @Override\r
+            public void run() {\r
+                setAllExpandedStates(true);\r
+            }\r
+        });\r
+        toolbar.add(new Action("Configure Filters",\r
+                BundleUtils.getImageDescriptorFromPlugin(Activator.PLUGIN_ID, "icons/filter_ps.gif")) {\r
+            @Override\r
+            public void run() {\r
+                FilterConfiguration clone = new FilterConfiguration(config);\r
+                FilterDialog fd = new FilterDialog(getViewSite().getShell(), Activator.getDefault().getDialogSettings(), clone);\r
+                int result = fd.open();\r
+                if (result != Window.OK)\r
+                    return;\r
+\r
+                updateFilterConfiguration(clone);\r
+                applyGroupFilters();\r
+\r
+                try {\r
+                    savePreferences();\r
+                } catch (BackingStoreException e) {\r
+                    ExceptionUtils.logAndShowError(e);\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    void setAllExpandedStates(boolean targetState) {\r
+        Boolean targetStateObj = Boolean.valueOf(targetState);\r
+        boolean changed = false;\r
+        setDefaultExpanded(targetState);\r
+        Control[] grps = c.getChildren();\r
+        //for (final PGroup grp : groups.values().toArray(new PGroup[0])) {\r
+        for (Control control : grps) {\r
+            final PGroup grp = (PGroup) control;\r
+            grp.setData(KEY_USER_EXPANDED, targetStateObj);\r
+            if (!grp.isDisposed() && grp.getExpanded() != targetState && grp.getVisible()) {\r
+                final GalleryViewer viewer = initializeGroup(grp);\r
+                setGroupExpandedWithoutNotification(grp, targetState);\r
+                ThreadUtils.asyncExec(swtThread, new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        if (!grp.isDisposed()) {\r
+                            if (viewer != null)\r
+                                viewer.refresh();\r
+                            refreshScrolledComposite();\r
+                        }\r
+                    }\r
+                });\r
+                changed = true;\r
+            }\r
+        }\r
+        if (changed)\r
+            refreshScrolledComposite();\r
+    }\r
+\r
+    class ImageLoader implements Runnable {\r
+\r
+        private final ImageProxy  imageProxy;\r
+        private final ISymbolItem item;\r
+\r
+        public ImageLoader(ImageProxy imageProxy, ISymbolItem item) {\r
+            this.imageProxy = imageProxy;\r
+            this.item = item;\r
+        }\r
+\r
+        @Override\r
+        public void run() {\r
+            ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                    runBlocking();\r
+                }\r
+            });\r
+        }\r
+\r
+        private void runBlocking() {\r
+            try {\r
+                IHintObservable hints = null;\r
+                ISymbolGroup group = item.getGroup();\r
+                if (group == null)\r
+                    throw new ProvisionException("No ISymbolGroup available for ISymbolItem " + item);\r
+\r
+                GalleryViewer viewer = groupViewers.get(group);\r
+                if (viewer == null) {\r
+                    // This is normal if this composite has been disposed while these are being ran.\r
+                    //throw new ProvisionException("No GalleryViewer available ISymbolGroup " + group);\r
+                    imageProxy.setSource(DefaultImages.UNKNOWN2.get());\r
+                    return;\r
+                }\r
+\r
+                hints = viewer.getDiagram();\r
+                if (hints == null)\r
+                    throw new ProvisionException("No diagram available for GalleryViewer of group " + group);\r
+\r
+                ElementClass ec = item.getElementClass(hints);\r
+                StaticSymbol ss = ec.getSingleItem(StaticSymbol.class);\r
+                Image source = ss == null ? DefaultImages.UNKNOWN2.get() : ss.getImage();\r
+                imageProxy.setSource(source);\r
+            } catch (ProvisionException e) {\r
+                ExceptionUtils.logWarning("Failed to provide element class for symbol item " + item, e);\r
+                imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());\r
+            } catch (Exception e) {\r
+                ExceptionUtils.logError(e);\r
+                imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());\r
+            } finally {\r
+            }\r
+        }\r
+    }\r
+\r
+    public FilterArea getFilterArea() {\r
+        return filter;\r
+    }\r
+\r
+    Runnable filterActivator = new Runnable() {\r
+        @Override\r
+        public void run() {\r
+            filter.focus();\r
+        }\r
+    };\r
+    Listener filterActivationListener = new Listener() {\r
+        @Override\r
+        public void handleEvent(Event event) {\r
+            //System.out.println("event: " + event);\r
+            filterActivator.run();\r
+        }\r
+    };\r
+\r
+    ISymbolGroupListener groupListener = new ISymbolGroupListener() {\r
+        @Override\r
+        public void itemsChanged(ISymbolGroup group) {\r
+            GalleryViewer viewer = groupViewers.get(group);\r
+            if (viewer != null) {\r
+                ISymbolItem[] input = group.getItems();\r
+                viewer.setInput(input);\r
+            }\r
+        }\r
+    };\r
+\r
+    @Override\r
+    @SuppressWarnings("rawtypes")\r
+    public Object getAdapter(Class adapter) {\r
+        // For supporting Scene Graph viewer\r
+        if (adapter == INode[].class) {\r
+            List<INode> result = new ArrayList<INode>(groupViewers.size());\r
+            for (GalleryViewer viewer : groupViewers.values()) {\r
+                result.add(viewer.getCanvasContext().getSceneGraph());\r
+            }\r
+            return result.toArray(new INode[result.size()]);\r
+        }\r
+        return super.getAdapter(adapter);\r
+    }\r
+\r
+}\r