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