--- /dev/null
+/*******************************************************************************\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