-/*******************************************************************************\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.StringSelection;\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.Semaphore;\r
-import java.util.concurrent.SynchronousQueue;\r
-import java.util.concurrent.ThreadFactory;\r
-import java.util.concurrent.ThreadPoolExecutor;\r
-import java.util.concurrent.TimeUnit;\r
-import java.util.concurrent.atomic.AtomicBoolean;\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.IAdaptable;\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.Status;\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.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.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.common.procedure.adapter.ListenerAdapter;\r
-import org.simantics.db.common.request.UnaryRead;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.diagram.internal.Activator;\r
-import org.simantics.diagram.symbolcontribution.CompositeSymbolGroup;\r
-import org.simantics.diagram.symbolcontribution.IIdentifiedObject;\r
-import org.simantics.diagram.symbolcontribution.ISymbolProvider;\r
-import org.simantics.diagram.symbolcontribution.IdentifiedObject;\r
-import org.simantics.diagram.symbolcontribution.SymbolProviderFactory;\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.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.event.adapter.SWTMouseEventAdapter;\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.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.scl.runtime.tuple.Tuple2;\r
-import org.simantics.ui.SimanticsUI;\r
-import org.simantics.ui.dnd.LocalObjectTransfer;\r
-import org.simantics.ui.dnd.LocalObjectTransferable;\r
-import org.simantics.ui.dnd.MultiTransferable;\r
-import org.simantics.ui.dnd.PlaintextTransfer;\r
-import org.simantics.utils.datastructures.cache.ProvisionException;\r
-import org.simantics.utils.datastructures.hints.IHintContext;\r
-import org.simantics.utils.threads.AWTThread;\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.ErrorLogger;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class SymbolLibraryComposite extends Composite {\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
- IThreadWorkQueue swtThread;\r
- boolean defaultExpanded = false;\r
- ISymbolProvider symbolProvider;\r
- AtomicBoolean disposed = new AtomicBoolean(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<>();\r
- Map<ISymbolGroup, GalleryViewer> groupViewers = new HashMap<>();\r
- Map<Object, Boolean> expandedGroups = new HashMap<>();\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 = new ThreadPoolExecutor(0, Integer.MAX_VALUE,\r
- 2L, TimeUnit.SECONDS,\r
- new SynchronousQueue<Runnable>(),\r
- 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 String description;\r
- public final PGroup group;\r
-\r
- public GroupDescriptor(ISymbolGroup lib, String label, String description, PGroup group) {\r
- assert(lib != null);\r
- assert(label != null);\r
- this.lib = lib;\r
- this.label = label;\r
- this.description = description;\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 SymbolLibraryComposite(final Composite parent, int style, SymbolProviderFactory symbolProvider) {\r
- super(parent, style);\r
- init(parent, style);\r
- SimanticsUI.getSession().asyncRequest(new CreateSymbolProvider(symbolProvider), new SymbolProviderListener());\r
- addDisposeListener(new DisposeListener() {\r
- @Override\r
- public void widgetDisposed(DisposeEvent e) {\r
- disposed.set(true);\r
- }\r
- });\r
- }\r
-\r
- /**\r
- *\r
- */\r
- static class CreateSymbolProvider extends UnaryRead<SymbolProviderFactory, ISymbolProvider> {\r
- public CreateSymbolProvider(SymbolProviderFactory factory) {\r
- super(factory);\r
- }\r
- @Override\r
- public ISymbolProvider perform(ReadGraph graph) throws DatabaseException {\r
- //System.out.println("CreateSymbolProvider.perform: " + parameter);\r
- ISymbolProvider provider = parameter.create(graph);\r
- //print(provider);\r
- return provider;\r
- }\r
- }\r
-\r
- @SuppressWarnings("unused")\r
- private static void print(ISymbolProvider provider) {\r
- for (ISymbolGroup grp : provider.getSymbolGroups()) {\r
- System.out.println("GROUP: " + grp);\r
- if (grp instanceof CompositeSymbolGroup) {\r
- CompositeSymbolGroup cgrp = (CompositeSymbolGroup) grp;\r
- for (ISymbolGroup grp2 : cgrp.getGroups()) {\r
- System.out.println("\tGROUP: " + grp2);\r
- }\r
- }\r
- }\r
- }\r
-\r
- /**\r
- *\r
- */\r
- class SymbolProviderListener extends ListenerAdapter<ISymbolProvider> {\r
- @Override\r
- public void exception(Throwable t) {\r
- ErrorLogger.defaultLogError(t);\r
- }\r
- @Override\r
- public void execute(ISymbolProvider result) {\r
- //System.out.println("SymbolProviderListener: " + result);\r
- symbolProvider = result;\r
- if (result != null) {\r
- Collection<ISymbolGroup> groups = result.getSymbolGroups();\r
- //print(result);\r
- load(groups);\r
- }\r
- }\r
- public boolean isDisposed() {\r
- boolean result = SymbolLibraryComposite.this.isDisposed(); \r
- return result;\r
- }\r
- }\r
-\r
- private void init(final Composite parent, int style) {\r
- GridLayoutFactory.fillDefaults().spacing(0,0).applyTo(this);\r
-// setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));\r
-\r
- this.resourceManager = new LocalResourceManager(JFaceResources.getResources(getDisplay()), this);\r
- swtThread = SWTThread.getThreadAccess(this);\r
-\r
- filter = new FilterArea(this, 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(this, 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
- //sc.setBackground(sc.getDisplay().getSystemColor(SWT.COLOR_RED));\r
-\r
- c = new Composite(sc, 0);\r
- c.setVisible(false);\r
- GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(c);\r
- //c.setBackground(c.getDisplay().getSystemColor(SWT.COLOR_BLUE));\r
-\r
- sc.setContent(c);\r
-\r
- // No event context <-> mouse on empty space in symbol library\r
- SWTMouseEventAdapter noContextEventAdapter = new SWTMouseEventAdapter(null, externalEventHandler);\r
- installMouseEventAdapter(sc, noContextEventAdapter);\r
- installMouseEventAdapter(c, noContextEventAdapter);\r
-\r
- c.addDisposeListener(new DisposeListener() {\r
- @Override\r
- public void widgetDisposed(DisposeEvent e) {\r
- // Remember to shutdown the executor\r
- loaderExecutor.shutdown();\r
- groupViewers.clear();\r
- }\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
- if (loaderExecutor.isShutdown())\r
- return;\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
- String description = lib.getDescription();\r
- groupDescs.add(new GroupDescriptor(lib, label, description, 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
- loadComplete.run();\r
- return;\r
- }\r
- // $ SWT-begin\r
- //System.out.println("populating: " + desc.label);\r
- PGroup group = desc.group;\r
- Runnable chainedCompletionCallback = loadComplete;\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
- installMouseEventAdapter(group, new SWTMouseEventAdapter(group, externalEventHandler));\r
-\r
- groups.put(desc.lib, group);\r
-\r
- Boolean shouldBeExpanded = expandedGroups.get(symbolGroupToKey(desc.lib));\r
- if (shouldBeExpanded == null)\r
- shouldBeExpanded = defaultExpanded;\r
- group.setData(KEY_USER_EXPANDED, shouldBeExpanded);\r
-\r
- group.setExpanded(shouldBeExpanded);\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
- if (shouldBeExpanded) {\r
- //System.out.println("WAS EXPANDED(" + desc.label + ", " + symbolGroupToKey(desc.lib) + ", " + shouldBeExpanded + ")");\r
- PGroup expandedGroup = group;\r
- chainedCompletionCallback = () -> {\r
- // Chain callback to expand this group when the loading is otherwise completed.\r
- ThreadUtils.asyncExec(swtThread, () -> setExpandedState(expandedGroup, true, true));\r
- loadComplete.run();\r
- };\r
- }\r
- }\r
-\r
- group.setData(SymbolLibraryKeys.KEY_GROUP, desc.lib);\r
- group.setText(desc.label);\r
- group.setToolTipText(desc.description);\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
- Runnable newCompletionCallback = chainedCompletionCallback;\r
- exec.execute(() -> {\r
- populateGroups(exec, group_, iter, groupFilter, loadId, newCompletionCallback);\r
- });\r
- }\r
- });\r
- }\r
-\r
- protected void installMouseEventAdapter(Control onControl, SWTMouseEventAdapter eventAdapter) {\r
- onControl.addMouseListener(eventAdapter);\r
- onControl.addMouseTrackListener(eventAdapter);\r
- onControl.addMouseMoveListener(eventAdapter);\r
- onControl.addMouseWheelListener(eventAdapter);\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.getText() + ")");\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 NOT stop to wait until the SWT/AWT UI\r
- // population has been completed.\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
-\r
- @Override\r
- public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {\r
- if (externalEventHandler.handleEvent(e))\r
- return true;\r
-\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(() -> {\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
- return true;\r
- }\r
- return false;\r
- }\r
- }, 0);\r
- }\r
-\r
- static String toPatternString(String filter) {\r
- return DefaultFilterStrategy.defaultToPatternString(filter, true);\r
- }\r
-\r
- static class SymbolItemFilter extends ViewerFilter {\r
- private final String string;\r
- private final Matcher m;\r
-\r
- public SymbolItemFilter(String string, Pattern pattern) {\r
- this.string = string;\r
- this.m = pattern.matcher("");\r
- }\r
-\r
- @Override\r
- public boolean select(Viewer viewer, Object parentElement, Object element) {\r
- if (element instanceof ISymbolItem) {\r
- ISymbolItem item = (ISymbolItem) element;\r
- return matchesFilter(item.getName()) || matchesFilter(item.getDescription());\r
- } else if (element instanceof ISymbolGroup) {\r
- ISymbolGroup group = (ISymbolGroup) element;\r
- return matchesFilter(group.getName());\r
- }\r
- return false;\r
- }\r
-\r
- private boolean matchesFilter(String str) {\r
- m.reset(str.toLowerCase());\r
- boolean matches = m.matches();\r
- //System.out.println(pattern + ": " + str + ": " + (matches ? "PASS" : "FAIL"));\r
- return 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
- ISymbolGroup symbolGroup = (ISymbolGroup) grp.getData(SymbolLibraryKeys.KEY_GROUP);\r
- boolean filterMatchesGroup = filter != null && filter.select(viewer, null, symbolGroup);\r
- boolean shouldBeVisible = !groupFiltered && (elements.length > 0 || filterMatchesGroup);\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
- storeGroupExpandedState(grp, expanded);\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_COPY;\r
- }\r
-\r
- @Override\r
- public Transferable dragStart(DragGestureEvent e) {\r
- \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
- LocalObjectTransferable local = new LocalObjectTransferable(object);\r
- \r
- StringBuilder json = new StringBuilder();\r
- json.append("{");\r
- json.append(" \"type\" : \"Symbol\",");\r
- json.append(" \"res\" : [");\r
- int pos = 0;\r
- for(int i=0;i<res.length;i++) {\r
- if(pos > 0) json.append(",");\r
- Object r = res[i];\r
- if(r instanceof IdentifiedObject) {\r
- Object id = ((IdentifiedObject) r).getId();\r
- if(id instanceof IAdaptable) {\r
- Object resource = ((IAdaptable) id).getAdapter(Resource.class);\r
- if(resource != null) {\r
- long rid = ((Resource)resource).getResourceId();\r
- json.append(Long.toString(rid));\r
- pos++;\r
- }\r
- }\r
- }\r
- }\r
- json.append("] }");\r
- \r
- StringSelection text = new StringSelection(json.toString());\r
- PlaintextTransfer plainText = new PlaintextTransfer(json.toString()); \r
- \r
- return new MultiTransferable(local, text, plainText);\r
- \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
- storeGroupExpandedState(group, 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
- storeGroupExpandedState(group, true);\r
- //System.out.println("item expanded: " + group + ", " + sc.getClientArea());\r
- ThreadUtils.asyncExec(swtThread, () -> {\r
- GalleryViewer viewer = initializeGroup(group);\r
- if (viewer == null)\r
- return;\r
- ThreadUtils.asyncExec(swtThread, () -> {\r
- if (viewer.getControl().isDisposed())\r
- return;\r
- viewer.refresh();\r
- refreshScrolledComposite();\r
- });\r
- });\r
- }\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
- /**\r
- * Invoke from SWT thread only.\r
- * \r
- * @param targetState\r
- */\r
- public void setAllExpandedStates(boolean targetState) {\r
- setDefaultExpanded(targetState);\r
- Control[] grps = c.getChildren();\r
- boolean changed = false;\r
- for (Control control : grps)\r
- changed |= setExpandedState((PGroup) control, targetState, false);\r
- if (changed)\r
- refreshScrolledComposite();\r
- }\r
-\r
- /**\r
- * Invoke from SWT thread only.\r
- * \r
- * @param grp\r
- * @param targetState\r
- * @return\r
- */\r
- boolean setExpandedState(PGroup grp, boolean targetState, boolean force) {\r
- if (grp.isDisposed())\r
- return false;\r
-\r
- storeGroupExpandedState(grp, targetState);\r
- grp.setData(KEY_USER_EXPANDED, Boolean.valueOf(targetState));\r
- if ((force || grp.getExpanded() != targetState) && grp.getVisible()) {\r
- final GalleryViewer viewer = initializeGroup(grp);\r
- setGroupExpandedWithoutNotification(grp, targetState);\r
- ThreadUtils.asyncExec(swtThread, () -> {\r
- if (!grp.isDisposed()) {\r
- if (viewer != null)\r
- viewer.refresh();\r
- refreshScrolledComposite();\r
- }\r
- });\r
- return true;\r
- }\r
- return false;\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
- // SVG images using the SVGUniverse in SVGCache must use\r
- // AWT thread for all operations.\r
- ThreadUtils.asyncExec(AWTThread.getThreadAccess(), () -> runBlocking());\r
- }\r
-\r
- private void runBlocking() {\r
- try {\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
- IHintContext hints = viewer.getDiagram();\r
- if (hints == null)\r
- throw new ProvisionException("No diagram available for GalleryViewer of group " + group);\r
-\r
- hints.setHint(ISymbolItem.KEY_ELEMENT_CLASS_LISTENER, new ElementClassListener(imageCache, disposed, item));\r
- final ElementClass ec = item.getElementClass(hints);\r
-\r
- // Without this the symbol library will at times\r
- // not update the final graphics for the symbol.\r
- // It will keep displaying the hourglass pending icon instead.\r
- symbolUpdate(disposed, imageProxy, ec);\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
- static class ElementClassListener implements org.simantics.db.procedure.Listener<ElementClass> {\r
- private Map<ISymbolItem, SoftReference<ImageProxy>> imageCache;\r
- private final AtomicBoolean disposed;\r
- private final ISymbolItem item;\r
-\r
- public ElementClassListener(Map<ISymbolItem, SoftReference<ImageProxy>> imageCache, AtomicBoolean disposed, ISymbolItem item) {\r
- this.imageCache = imageCache;\r
- this.disposed = disposed;\r
- this.item = item;\r
- }\r
-\r
- @Override\r
- public void execute(final ElementClass ec) {\r
- //System.out.println("SYMBOL CHANGED: " + item + " - disposed=" + disposed + " - " + ec);\r
-\r
- final ImageProxy[] imageProxy = { null };\r
- SoftReference<ImageProxy> proxyRef = imageCache.get(item);\r
- if (proxyRef != null)\r
- imageProxy[0] = proxyRef.get();\r
- if (imageProxy[0] != null)\r
- scheduleSymbolUpdate(disposed, imageProxy[0], ec);\r
- }\r
-\r
- @Override\r
- public void exception(Throwable t) {\r
- Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error in ElementClass request.", t));\r
- }\r
-\r
- @Override\r
- public boolean isDisposed() {\r
- //System.out.println("ElementClassListener.isDisposed " + item + " - " + disposed.get());\r
- return disposed.get();\r
- }\r
- }\r
-\r
- public FilterArea getFilterArea() {\r
- return filter;\r
- }\r
-\r
- public static void scheduleSymbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {\r
- if (disposed.get())\r
- return;\r
- ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() {\r
- @Override\r
- public void run() {\r
- if (disposed.get())\r
- return;\r
- symbolUpdate(disposed, imageProxy, ec);\r
- }\r
- });\r
- }\r
-\r
- public static void symbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {\r
- StaticSymbol ss = ec.getSingleItem(StaticSymbol.class);\r
- Image source = ss == null ? DefaultImages.UNKNOWN2.get() : ss.getImage();\r
- imageProxy.setSource(source);\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
- //System.out.println("symbol group changed: " + 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
- IEventHandler externalEventHandler = new IEventHandler() {\r
- @Override\r
- public int getEventMask() {\r
- return EventTypes.AnyMask;\r
- }\r
- @Override\r
- public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {\r
- IEventHandler handler = SymbolLibraryComposite.this.eventHandler;\r
- return handler != null && EventTypes.passes(handler, e) ? handler.handleEvent(e) : false;\r
- }\r
- };\r
-\r
- protected volatile IEventHandler eventHandler;\r
-\r
- /**\r
- * @param eventHandler\r
- */\r
- public void setEventHandler(IEventHandler eventHandler) {\r
- this.eventHandler = eventHandler;\r
- }\r
-\r
- protected void storeGroupExpandedState(PGroup group, boolean expanded) {\r
- ISymbolGroup symbolGroup = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);\r
- //System.out.println("setGroupExpandedWithoutNotification(" + group + ", " + expanded + ", " + symbolGroup + ")");\r
- if (symbolGroup != null) {\r
- Object key = symbolGroupToKey(symbolGroup);\r
- expandedGroups.put(key, expanded ? Boolean.TRUE : Boolean.FALSE);\r
- }\r
- }\r
-\r
- private static Object symbolGroupToKey(ISymbolGroup symbolGroup) {\r
- if (symbolGroup instanceof IIdentifiedObject)\r
- return ((IIdentifiedObject) symbolGroup).getId();\r
- return new Tuple2(symbolGroup.getName(), symbolGroup.getDescription());\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.symbollibrary.ui;
+
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.WeakHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.viewers.AcceptAllFilter;
+import org.eclipse.jface.viewers.BaseLabelProvider;
+import org.eclipse.jface.viewers.IFilter;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerFilter;
+import org.eclipse.nebula.widgets.pgroup.PGroup;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.ExpandEvent;
+import org.eclipse.swt.events.ExpandListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Widget;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.procedure.adapter.ListenerAdapter;
+import org.simantics.db.common.request.UnaryRead;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.diagram.internal.Activator;
+import org.simantics.diagram.symbolcontribution.CompositeSymbolGroup;
+import org.simantics.diagram.symbolcontribution.IIdentifiedObject;
+import org.simantics.diagram.symbolcontribution.ISymbolProvider;
+import org.simantics.diagram.symbolcontribution.IdentifiedObject;
+import org.simantics.diagram.symbolcontribution.SymbolProviderFactory;
+import org.simantics.diagram.symbollibrary.IModifiableSymbolGroup;
+import org.simantics.diagram.symbollibrary.ISymbolGroup;
+import org.simantics.diagram.symbollibrary.ISymbolGroupListener;
+import org.simantics.diagram.symbollibrary.ISymbolItem;
+import org.simantics.diagram.symbollibrary.ui.FilterConfiguration.Mode;
+import org.simantics.diagram.synchronization.ErrorHandler;
+import org.simantics.diagram.synchronization.LogErrorHandler;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.chassis.AWTChassis;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.PickRequest;
+import org.simantics.g2d.diagram.handler.layout.FlowLayout;
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
+import org.simantics.g2d.dnd.IDragSourceParticipant;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.StaticSymbol;
+import org.simantics.g2d.event.adapter.SWTMouseEventAdapter;
+import org.simantics.g2d.gallery.GalleryViewer;
+import org.simantics.g2d.gallery.ILabelProvider;
+import org.simantics.g2d.image.DefaultImages;
+import org.simantics.g2d.image.Image;
+import org.simantics.g2d.image.Image.Feature;
+import org.simantics.g2d.image.impl.ImageProxy;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.scenegraph.g2d.events.EventTypes;
+import org.simantics.scenegraph.g2d.events.IEventHandler;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
+import org.simantics.scl.runtime.tuple.Tuple2;
+import org.simantics.ui.SimanticsUI;
+import org.simantics.ui.dnd.LocalObjectTransfer;
+import org.simantics.ui.dnd.LocalObjectTransferable;
+import org.simantics.ui.dnd.MultiTransferable;
+import org.simantics.ui.dnd.PlaintextTransfer;
+import org.simantics.utils.datastructures.cache.ProvisionException;
+import org.simantics.utils.datastructures.hints.IHintContext;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.ExceptionUtils;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class SymbolLibraryComposite extends Composite {
+
+ private static final int FILTER_DELAY = 500;
+
+ private static final String KEY_VIEWER_INITIALIZED = "viewer.initialized";
+ private static final String KEY_USER_EXPANDED = "userExpanded";
+ private static final String KEY_GROUP_FILTERED = "groupFiltered";
+
+ /** Root composite */
+ ScrolledComposite sc;
+ Composite c;
+ IThreadWorkQueue swtThread;
+ boolean defaultExpanded = false;
+ ISymbolProvider symbolProvider;
+ AtomicBoolean disposed = new AtomicBoolean(false);
+
+ /**
+ * This value is incremented each time a load method is called and symbol
+ * group population is started. It can be used by
+ * {@link #populateGroups(ExecutorService, Control, Iterator, IFilter)} to
+ * tell whether it should stop its population job because a later load
+ * will override its results anyway.
+ */
+ AtomicInteger loadCount = new AtomicInteger();
+
+ Map<ISymbolGroup, PGroup> groups = new HashMap<>();
+ Map<ISymbolGroup, GalleryViewer> groupViewers = new HashMap<>();
+ Map<Object, Boolean> expandedGroups = new HashMap<>();
+ LocalResourceManager resourceManager;
+ FilterArea filter;
+
+ ThreadFactory threadFactory = new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, "Symbol Library Loader");
+ t.setDaemon(false);
+ t.setPriority(Thread.NORM_PRIORITY);
+ return t;
+ }
+ };
+
+ Semaphore loaderSemaphore = new Semaphore(1);
+ ExecutorService loaderExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+ 2L, TimeUnit.SECONDS,
+ new SynchronousQueue<Runnable>(),
+ threadFactory);
+
+ /**
+ * Used to prevent annoying reloading of symbols when groups are closed and
+ * reopened by not always having to schedule an {@link ImageLoader} in
+ * {@link LabelProvider#getImage(Object)}.
+ */
+ Map<ISymbolItem, SoftReference<ImageProxy>> imageCache = new WeakHashMap<ISymbolItem, SoftReference<ImageProxy>>();
+
+ static final Pattern ANY = Pattern.compile(".*");
+ Pattern currentFilterPattern = ANY;
+
+ FilterConfiguration config = new FilterConfiguration();
+ IFilter currentGroupFilter = AcceptAllFilter.getInstance();
+
+ ErrorHandler errorHandler = LogErrorHandler.INSTANCE;
+
+ static class GroupDescriptor {
+ public final ISymbolGroup lib;
+ public final String label;
+ public final String description;
+ public final PGroup group;
+
+ public GroupDescriptor(ISymbolGroup lib, String label, String description, PGroup group) {
+ assert(lib != null);
+ assert(label != null);
+ this.lib = lib;
+ this.label = label;
+ this.description = description;
+ this.group = group;
+ }
+ }
+
+ Comparator<GroupDescriptor> groupComparator = new Comparator<GroupDescriptor>() {
+ @Override
+ public int compare(GroupDescriptor o1, GroupDescriptor o2) {
+ return o1.label.compareToIgnoreCase(o2.label);
+ }
+ };
+
+ static final EnumSet<Feature> VOLATILE = EnumSet.of(Feature.Volatile);
+
+ static class PendingImage extends ImageProxy {
+ EnumSet<Feature> features;
+ PendingImage(Image source, EnumSet<Feature> features) {
+ super(source);
+ this.features = features;
+ }
+ @Override
+ public EnumSet<Feature> getFeatures() {
+ return features;
+ }
+ }
+
+ class LabelProvider extends BaseLabelProvider implements ILabelProvider {
+ @Override
+ public Image getImage(final Object element) {
+ ISymbolItem item = (ISymbolItem) element;
+ // Use a volatile ImageProxy to make the image loading asynchronous.
+ ImageProxy proxy = null;
+ SoftReference<ImageProxy> proxyRef = imageCache.get(item);
+ if (proxyRef != null)
+ proxy = proxyRef.get();
+ if (proxy == null) {
+ proxy = new PendingImage(DefaultImages.HOURGLASS.get(), VOLATILE);
+ imageCache.put(item, new SoftReference<ImageProxy>(proxy));
+ ThreadUtils.getNonBlockingWorkExecutor().schedule(new ImageLoader(proxy, item), 100, TimeUnit.MILLISECONDS);
+ }
+ return proxy;
+ }
+ @Override
+ public String getText(final Object element) {
+ return ((ISymbolItem) element).getName();
+ }
+ @Override
+ public String getToolTipText(Object element) {
+ ISymbolItem item = (ISymbolItem) element;
+ String name = item.getName();
+ String desc = item.getDescription();
+ return name.equals(desc) ? name : name + " - " + desc;
+ }
+
+ @Override
+ public java.awt.Image getToolTipImage(Object object) {
+ return null;
+ }
+ @Override
+ public Color getToolTipBackgroundColor(Object object) {
+ return null;
+ }
+
+ @Override
+ public Color getToolTipForegroundColor(Object object) {
+ return null;
+ }
+ }
+
+ public SymbolLibraryComposite(final Composite parent, int style, SymbolProviderFactory symbolProvider) {
+ super(parent, style);
+ init(parent, style);
+ SimanticsUI.getSession().asyncRequest(new CreateSymbolProvider(symbolProvider), new SymbolProviderListener());
+ addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ disposed.set(true);
+ }
+ });
+ }
+
+ /**
+ *
+ */
+ static class CreateSymbolProvider extends UnaryRead<SymbolProviderFactory, ISymbolProvider> {
+ public CreateSymbolProvider(SymbolProviderFactory factory) {
+ super(factory);
+ }
+ @Override
+ public ISymbolProvider perform(ReadGraph graph) throws DatabaseException {
+ //System.out.println("CreateSymbolProvider.perform: " + parameter);
+ ISymbolProvider provider = parameter.create(graph);
+ //print(provider);
+ return provider;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static void print(ISymbolProvider provider) {
+ for (ISymbolGroup grp : provider.getSymbolGroups()) {
+ System.out.println("GROUP: " + grp);
+ if (grp instanceof CompositeSymbolGroup) {
+ CompositeSymbolGroup cgrp = (CompositeSymbolGroup) grp;
+ for (ISymbolGroup grp2 : cgrp.getGroups()) {
+ System.out.println("\tGROUP: " + grp2);
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ class SymbolProviderListener extends ListenerAdapter<ISymbolProvider> {
+ @Override
+ public void exception(Throwable t) {
+ ErrorLogger.defaultLogError(t);
+ }
+ @Override
+ public void execute(ISymbolProvider result) {
+ //System.out.println("SymbolProviderListener: " + result);
+ symbolProvider = result;
+ if (result != null) {
+ Collection<ISymbolGroup> groups = result.getSymbolGroups();
+ //print(result);
+ load(groups);
+ }
+ }
+ public boolean isDisposed() {
+ boolean result = SymbolLibraryComposite.this.isDisposed();
+ return result;
+ }
+ }
+
+ private void init(final Composite parent, int style) {
+ GridLayoutFactory.fillDefaults().spacing(0,0).applyTo(this);
+// setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));
+
+ this.resourceManager = new LocalResourceManager(JFaceResources.getResources(getDisplay()), this);
+ swtThread = SWTThread.getThreadAccess(this);
+
+ filter = new FilterArea(this, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, false).applyTo(filter);
+ filter.getText().addModifyListener(new ModifyListener() {
+ int modCount = 0;
+ //long lastModificationTime = -1000;
+ @Override
+ public void modifyText(ModifyEvent e) {
+ scheduleDelayedFilter(FILTER_DELAY, TimeUnit.MILLISECONDS);
+ }
+ private void scheduleDelayedFilter(long filterDelay, TimeUnit delayUnit) {
+ final String text = filter.getText().getText();
+
+ //long time = System.currentTimeMillis();
+ //long delta = time - lastModificationTime;
+ //lastModificationTime = time;
+
+ final int count = ++modCount;
+ ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {
+ @Override
+ public void run() {
+ int newCount = modCount;
+ if (newCount != count)
+ return;
+
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ if (sc.isDisposed())
+ return;
+ if (!filterGroups(text)) {
+ scheduleDelayedFilter(100, TimeUnit.MILLISECONDS);
+ }
+ }
+ });
+ }
+ }, filterDelay, delayUnit);
+ }
+ });
+
+ sc = new ScrolledComposite(this, SWT.V_SCROLL);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(sc);
+ sc.setAlwaysShowScrollBars(false);
+ sc.setExpandHorizontal(false);
+ sc.setExpandVertical(false);
+ sc.getVerticalBar().setIncrement(30);
+ sc.getVerticalBar().setPageIncrement(200);
+ sc.addControlListener( new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ //System.out.println("ScrolledComposite resized: " + sc.getSize());
+ refreshScrolledComposite();
+ }
+ });
+ //sc.setBackground(sc.getDisplay().getSystemColor(SWT.COLOR_RED));
+
+ c = new Composite(sc, 0);
+ c.setVisible(false);
+ GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(c);
+ //c.setBackground(c.getDisplay().getSystemColor(SWT.COLOR_BLUE));
+
+ sc.setContent(c);
+
+ // No event context <-> mouse on empty space in symbol library
+ SWTMouseEventAdapter noContextEventAdapter = new SWTMouseEventAdapter(null, externalEventHandler);
+ installMouseEventAdapter(sc, noContextEventAdapter);
+ installMouseEventAdapter(c, noContextEventAdapter);
+
+ c.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent e) {
+ // Remember to shutdown the executor
+ loaderExecutor.shutdown();
+ groupViewers.clear();
+ }
+ });
+ }
+
+ void refreshScrolledComposite() {
+ // Execute asynchronously to give the UI events triggering this method
+ // call time to run through before actually doing any resizing.
+ // Otherwise the result will lag behind reality when scrollbar
+ // visibility is toggled by the toolkit.
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ if (sc.isDisposed())
+ return;
+ syncRefreshScrolledComposite();
+ }
+ });
+ }
+
+ void syncRefreshScrolledComposite() {
+ // Execute asynchronously to give the UI events triggering this method
+ // call time to run through before actually doing any resizing.
+ // Otherwise the result will lag behind reality when scrollbar
+ // visibility is toggled by the toolkit.
+ Rectangle r = sc.getClientArea();
+ Point contentSize = c.computeSize(r.width, SWT.DEFAULT);
+ //System.out.println("[" + Thread.currentThread() + "] computed content size: " + contentSize + ", " + r);
+ c.setSize(contentSize);
+ }
+
+ /**
+ * (Re-)Load symbol groups, refresh the content
+ */
+ void load(Collection<ISymbolGroup> _libraries) {
+ if (_libraries == null)
+ _libraries = Collections.emptyList();
+ final Collection<ISymbolGroup> libraries = _libraries;
+ if (loaderExecutor.isShutdown())
+ return;
+ loaderExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ // Increment loadCount to signal that a new load cycle is on the way.
+ Integer loadId = loadCount.incrementAndGet();
+ try {
+ loaderSemaphore.acquire();
+ beginPopulate(loadId);
+ } catch (InterruptedException e) {
+ ExceptionUtils.logError(e);
+ } catch (RuntimeException e) {
+ loaderSemaphore.release();
+ ExceptionUtils.logAndShowError(e);
+ } catch (Error e) {
+ loaderSemaphore.release();
+ ExceptionUtils.logAndShowError(e);
+ }
+ }
+
+ void beginPopulate(Integer loadId) {
+ synchronized (groups) {
+ // Must use toArray since groups are removed within the loop
+ for (Iterator<Map.Entry<ISymbolGroup, PGroup>> it = groups.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<ISymbolGroup, PGroup> entry = it.next();
+ if (!libraries.contains(entry.getKey())) {
+ PGroup group = entry.getValue();
+ it.remove();
+ groupViewers.remove(entry.getKey());
+ if (group != null && !group.isDisposed())
+ ThreadUtils.asyncExec(swtThread, disposer(group));
+ }
+ }
+ Set<GroupDescriptor> groupDescs = new TreeSet<GroupDescriptor>(groupComparator);
+ for (ISymbolGroup lib : libraries) {
+ PGroup group = groups.get(lib);
+ //String label = group != null ? group.getText() : lib.getName();
+ String label = lib.getName();
+ String description = lib.getDescription();
+ groupDescs.add(new GroupDescriptor(lib, label, description, group));
+ }
+
+ // Populate all the missing groups.
+ IFilter groupFilter = currentGroupFilter;
+ populateGroups(
+ loaderExecutor,
+ null,
+ groupDescs.iterator(),
+ groupFilter,
+ loadId,
+ new Runnable() {
+ @Override
+ public void run() {
+ loaderSemaphore.release();
+ }
+ });
+ }
+ }
+ });
+ }
+
+ void populateGroups(
+ final ExecutorService exec,
+ final Control lastGroup,
+ final Iterator<GroupDescriptor> iter,
+ final IFilter groupFilter,
+ final Integer loadId,
+ final Runnable loadComplete)
+ {
+ // Check whether to still continue this population or not.
+ int currentLoadId = loadCount.get();
+ if (currentLoadId != loadId) {
+ loadComplete.run();
+ return;
+ }
+
+ if (!iter.hasNext()) {
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ if (filter.isDisposed() || c.isDisposed())
+ return;
+ //filter.focus();
+ c.setVisible(true);
+ }
+ });
+ loadComplete.run();
+ return;
+ }
+
+ final GroupDescriptor desc = iter.next();
+
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ // Must make sure that loadComplete is invoked under error
+ // circumstances.
+ try {
+ populateGroup();
+ } catch (RuntimeException e) {
+ loadComplete.run();
+ ExceptionUtils.logAndShowError(e);
+ } catch (Error e) {
+ loadComplete.run();
+ ExceptionUtils.logAndShowError(e);
+ }
+ }
+
+ public void populateGroup() {
+ if (c.isDisposed()) {
+ loadComplete.run();
+ return;
+ }
+ // $ SWT-begin
+ //System.out.println("populating: " + desc.label);
+ PGroup group = desc.group;
+ Runnable chainedCompletionCallback = loadComplete;
+ if (group == null || group.isDisposed()) {
+
+ group = new PGroup(c, SWT.NONE);
+// group.addListener(SWT.KeyUp, filterActivationListener);
+// group.addListener(SWT.KeyDown, filterActivationListener);
+// group.addListener(SWT.FocusIn, filterActivationListener);
+// group.addListener(SWT.FocusOut, filterActivationListener);
+// group.addListener(SWT.MouseDown, filterActivationListener);
+// group.addListener(SWT.MouseUp, filterActivationListener);
+// group.addListener(SWT.MouseDoubleClick, filterActivationListener);
+// group.addListener(SWT.Arm, filterActivationListener);
+ if (lastGroup != null) {
+ group.moveBelow(lastGroup);
+ } else {
+ group.moveAbove(null);
+ }
+
+ installMouseEventAdapter(group, new SWTMouseEventAdapter(group, externalEventHandler));
+
+ groups.put(desc.lib, group);
+
+ Boolean shouldBeExpanded = expandedGroups.get(symbolGroupToKey(desc.lib));
+ if (shouldBeExpanded == null)
+ shouldBeExpanded = defaultExpanded;
+ group.setData(KEY_USER_EXPANDED, shouldBeExpanded);
+
+ group.setExpanded(shouldBeExpanded);
+ group.setFont(resourceManager.createFont(FontDescriptor.createFrom(group.getFont()).setStyle(SWT.NORMAL).increaseHeight(-1)));
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(group);
+ GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(group);
+ group.addExpandListener(groupExpandListener);
+
+ // Track group content changes if possible.
+ if (desc.lib instanceof IModifiableSymbolGroup) {
+ IModifiableSymbolGroup mod = (IModifiableSymbolGroup) desc.lib;
+ mod.addListener(groupListener);
+ }
+
+ if (shouldBeExpanded) {
+ //System.out.println("WAS EXPANDED(" + desc.label + ", " + symbolGroupToKey(desc.lib) + ", " + shouldBeExpanded + ")");
+ PGroup expandedGroup = group;
+ chainedCompletionCallback = () -> {
+ // Chain callback to expand this group when the loading is otherwise completed.
+ ThreadUtils.asyncExec(swtThread, () -> setExpandedState(expandedGroup, true, true));
+ loadComplete.run();
+ };
+ }
+ }
+
+ group.setData(SymbolLibraryKeys.KEY_GROUP, desc.lib);
+ group.setText(desc.label);
+ group.setToolTipText(desc.description);
+
+ // Hide the group immediately if necessary.
+ boolean groupFiltered = !groupFilter.select(desc.label);
+ group.setData(KEY_GROUP_FILTERED, Boolean.valueOf(groupFiltered));
+ if (groupFiltered)
+ setGroupVisible(group, false);
+
+ syncRefreshScrolledComposite();
+
+ final PGroup group_ = group;
+ Runnable newCompletionCallback = chainedCompletionCallback;
+ exec.execute(() -> {
+ populateGroups(exec, group_, iter, groupFilter, loadId, newCompletionCallback);
+ });
+ }
+ });
+ }
+
+ protected void installMouseEventAdapter(Control onControl, SWTMouseEventAdapter eventAdapter) {
+ onControl.addMouseListener(eventAdapter);
+ onControl.addMouseTrackListener(eventAdapter);
+ onControl.addMouseMoveListener(eventAdapter);
+ onControl.addMouseWheelListener(eventAdapter);
+ }
+
+ /**
+ * @param group
+ * @return <code>null</code> if GalleryViewer is currently being created
+ */
+ GalleryViewer initializeGroup(final PGroup group) {
+ if (group.isDisposed())
+ return null;
+
+ //System.out.println("initializeGroup(" + group.getText() + ")");
+
+ synchronized (group) {
+ if (group.getData(KEY_VIEWER_INITIALIZED) != null) {
+ return (GalleryViewer) group.getData(SymbolLibraryKeys.KEY_GALLERY);
+ }
+ group.setData(KEY_VIEWER_INITIALIZED, Boolean.TRUE);
+ }
+
+ //System.out.println("initializing group: " + group.getText());
+
+ // NOTE: this will NOT stop to wait until the SWT/AWT UI
+ // population has been completed.
+ GalleryViewer viewer = new GalleryViewer(group);
+
+ ISymbolGroup input = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);
+ initializeViewer(group, input, viewer);
+
+ groupViewers.put(input, viewer);
+ group.setData(SymbolLibraryKeys.KEY_GALLERY, viewer);
+
+ //System.out.println("initialized group: " + group.getText());
+
+ return viewer;
+ }
+
+ void initializeViewer(final PGroup group, final ISymbolGroup input, final GalleryViewer viewer) {
+ GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false).applyTo(viewer.getControl());
+ viewer.addDragSupport(new DragSourceParticipant());
+ viewer.setAlign(FlowLayout.Align.Left);
+ viewer.getDiagram().setHint(SynchronizationHints.ERROR_HANDLER, errorHandler);
+
+ viewer.setContentProvider(new IStructuredContentProvider() {
+
+ /**
+ * Returns the elements in the input, which must be either an array or a
+ * <code>Collection</code>.
+ */
+ @Override
+ public Object[] getElements(Object inputElement) {
+ if(inputElement == null) return new Object[0];
+ return ((ISymbolGroup)inputElement).getItems();
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ // do nothing.
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ @Override
+ public void dispose() {
+ // do nothing.
+ }
+
+ });
+ viewer.setLabelProvider(new LabelProvider());
+ viewer.setInput(input);
+
+ // Add event handler that closes libraries on double clicks into empty
+ // space in library.
+ viewer.getCanvasContext().getEventHandlerStack().add(new IEventHandler() {
+ @Override
+ public int getEventMask() {
+ return EventTypes.MouseDoubleClickMask;
+ }
+
+ @Override
+ public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {
+ if (externalEventHandler.handleEvent(e))
+ return true;
+
+ if (e instanceof MouseDoubleClickedEvent) {
+ PickRequest req = new PickRequest(((MouseDoubleClickedEvent) e).controlPosition);
+ Collection<IElement> result = new ArrayList<IElement>();
+ DiagramUtils.pick(viewer.getDiagram(), req, result);
+ if (!result.isEmpty())
+ return false;
+
+ //System.out.println("NOTHING CLICKED");
+ if (group.isDisposed())
+ return false;
+ group.getDisplay().asyncExec(() -> {
+ if (group.isDisposed())
+ return;
+
+ boolean exp = !group.getExpanded();
+ group.setData(KEY_USER_EXPANDED, Boolean.valueOf(exp));
+ setGroupExpandedWithoutNotification(group, exp);
+ refreshScrolledComposite();
+ });
+ return true;
+ }
+ return false;
+ }
+ }, 0);
+ }
+
+ static String toPatternString(String filter) {
+ return DefaultFilterStrategy.defaultToPatternString(filter, true);
+ }
+
+ static class SymbolItemFilter extends ViewerFilter {
+ private final String string;
+ private final Matcher m;
+
+ public SymbolItemFilter(String string, Pattern pattern) {
+ this.string = string;
+ this.m = pattern.matcher("");
+ }
+
+ @Override
+ public boolean select(Viewer viewer, Object parentElement, Object element) {
+ if (element instanceof ISymbolItem) {
+ ISymbolItem item = (ISymbolItem) element;
+ return matchesFilter(item.getName()) || matchesFilter(item.getDescription());
+ } else if (element instanceof ISymbolGroup) {
+ ISymbolGroup group = (ISymbolGroup) element;
+ return matchesFilter(group.getName());
+ }
+ return false;
+ }
+
+ private boolean matchesFilter(String str) {
+ m.reset(str.toLowerCase());
+ boolean matches = m.matches();
+ //System.out.println(pattern + ": " + str + ": " + (matches ? "PASS" : "FAIL"));
+ return matches;
+ }
+
+ @Override
+ public int hashCode() {
+ return string == null ? 0 : string.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SymbolItemFilter other = (SymbolItemFilter) obj;
+ if (string == null) {
+ if (other.string != null)
+ return false;
+ } else if (!string.equals(other.string))
+ return false;
+ return true;
+ }
+ }
+
+ static Pattern toPattern(String filterText) {
+ String regExFilter = toPatternString(filterText);
+ Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
+ return pattern;
+ }
+
+ static IFilter composeFilter(final FilterConfiguration config) {
+ final Mode mode = config.getMode();
+ final List<Pattern> patterns = new ArrayList<Pattern>();
+ for (GroupFilter f : config.getFilters()) {
+ if (f.isActive())
+ patterns.add(toPattern(f.getFilterText()));
+ }
+ return new IFilter() {
+ @Override
+ public boolean select(Object toTest) {
+ if (patterns.isEmpty())
+ return true;
+
+ String s = (String) toTest;
+ switch (mode) {
+ case AND:
+ for (Pattern pat : patterns) {
+ Matcher m = pat.matcher(s.toLowerCase());
+ //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
+ if (!m.matches())
+ return false;
+ }
+ return true;
+ case OR:
+ for (Pattern pat : patterns) {
+ Matcher m = pat.matcher(s.toLowerCase());
+ //System.out.println(s + ": " + (m.matches() ? "PASS" : "FAIL"));
+ if (m.matches())
+ return true;
+ }
+ return false;
+ default:
+ throw new Error("Shouldn't happen");
+ }
+ }
+ };
+ }
+
+ void updateFilterConfiguration(FilterConfiguration config) {
+ this.config = config;
+ IFilter filter = composeFilter(config);
+ this.currentGroupFilter = filter;
+ }
+
+ void applyGroupFilters() {
+ IFilter groupFilter = this.currentGroupFilter;
+ final boolean[] changed = new boolean[] { false };
+
+ Control[] grps = c.getChildren();
+ for (Control ctrl : grps) {
+ final PGroup grp = (PGroup) ctrl;
+ boolean visible = grp.getVisible();
+ boolean shouldBeVisible = groupFilter.select(grp.getText());
+ boolean change = visible != shouldBeVisible;
+ changed[0] |= change;
+
+ grp.setData(KEY_GROUP_FILTERED, Boolean.valueOf(!shouldBeVisible));
+ if (change) {
+ setGroupVisible(grp, shouldBeVisible);
+ }
+ }
+
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ if (c.isDisposed())
+ return;
+ if (changed[0]) {
+ c.layout(true);
+ syncRefreshScrolledComposite();
+ }
+ }
+ });
+ }
+
+ /**
+ * Filters the symbol groups and makes them visible/invisible as necessary.
+ * Invoke only from the SWT thread.
+ *
+ * @param text the filter text given by the client
+ * @return <code>true</code> if all groups were successfully filtered
+ * without asynchronous results
+ */
+ boolean filterGroups(String text) {
+ //System.out.println("FILTERING WITH TEXT: " + text);
+
+ String regExFilter = toPatternString(text);
+ Pattern pattern = regExFilter != null ? Pattern.compile(regExFilter) : ANY;
+
+ this.currentFilterPattern = pattern;
+ final boolean[] changed = new boolean[] { false };
+ boolean filteringComplete = true;
+
+ ViewerFilter filter = null;
+ if (regExFilter != null)
+ filter = new SymbolItemFilter(regExFilter, pattern);
+
+ Control[] grps = c.getChildren();
+ for (Control ctrl : grps) {
+ final PGroup grp = (PGroup) ctrl;
+ if (grp.isDisposed())
+ continue;
+ Boolean contentsChanged = filterGroup(grp, filter);
+ if (contentsChanged == null)
+ filteringComplete = false;
+ else
+ changed[0] = contentsChanged;
+ }
+
+ ThreadUtils.asyncExec(swtThread, new Runnable() {
+ @Override
+ public void run() {
+ if (c.isDisposed())
+ return;
+ if (changed[0]) {
+ c.layout(true);
+ syncRefreshScrolledComposite();
+ }
+ }
+ });
+
+ return filteringComplete;
+ }
+
+ static boolean objectEquals(Object o1, Object o2) {
+ if (o1==o2) return true;
+ if (o1==null && o2==null) return true;
+ if (o1==null || o2==null) return false;
+ return o1.equals(o2);
+ }
+
+ /**
+ * @param grp
+ * @return <code>true</code> if the filtering caused changes in the group,
+ * <code>false</code> if not, and <code>null</code> if filtering
+ * could not be performed yet, meaning results need to be asked
+ * later
+ */
+ private Boolean filterGroup(PGroup grp, ViewerFilter filter) {
+ boolean changed = false;
+ GalleryViewer viewer = initializeGroup(grp);
+ if (viewer == null)
+ return null;
+
+ ViewerFilter lastFilter = viewer.getFilter();
+
+ boolean groupFiltered = Boolean.TRUE.equals(grp.getData(KEY_GROUP_FILTERED));
+ boolean userExpanded = Boolean.TRUE.equals(grp.getData(KEY_USER_EXPANDED));
+ final boolean expanded = grp.getExpanded();
+ final boolean visible = grp.getVisible();
+ final boolean filterChanged = !objectEquals(filter, lastFilter);
+ final ISymbolGroup symbolGroup = (ISymbolGroup) grp.getData(SymbolLibraryKeys.KEY_GROUP);
+ final boolean filterMatchesGroup = filter != null && filter.select(viewer, null, symbolGroup);
+
+ // Find out how much data would be shown with the new filter.
+ viewer.setFilter(filterMatchesGroup ? null : filter);
+ Object[] elements = viewer.getFilteredElements();
+
+ boolean shouldBeVisible = !groupFiltered && (elements.length > 0 || filterMatchesGroup);
+ boolean shouldBeExpanded = shouldBeVisible && (filter != null || userExpanded);
+
+// System.out.format("%40s: filterMatchesGroup(%s) = %s, visible/should be = %5s %5s, expanded/user expanded/should be = %5s %5s %5s\n",
+// grp.getText(),
+// symbolGroup.getName(),
+// String.valueOf(filterMatchesGroup),
+// String.valueOf(visible),
+// String.valueOf(shouldBeVisible),
+// String.valueOf(expanded),
+// String.valueOf(userExpanded),
+// String.valueOf(shouldBeExpanded));
+
+ if (filterChanged || visible != shouldBeVisible || expanded != shouldBeExpanded) {
+ changed = true;
+
+ if (shouldBeVisible == userExpanded) {
+ if (expanded != shouldBeExpanded)
+ setGroupExpandedWithoutNotification(grp, shouldBeExpanded);
+ setGroupVisible(grp, shouldBeVisible);
+ } else {
+ if (filter != null) {
+ if (shouldBeVisible) {
+ // The user has not expanded this group but the group contains
+ // stuff that matches the non-empty filter => show the group.
+ setGroupExpandedWithoutNotification(grp, true);
+ setGroupVisible(grp, true);
+ } else {
+ // The user has expanded this group but it does not contain items
+ // should should be shown with the current non-empty filter => hide the group.
+ setGroupExpandedWithoutNotification(grp, true);
+ setGroupVisible(grp, false);
+ }
+ } else {
+ // All groups should be visible. Some should be expanded and others not.
+ if (expanded != userExpanded)
+ setGroupExpandedWithoutNotification(grp, userExpanded);
+ if (!visible)
+ setGroupVisible(grp, true);
+ }
+ }
+
+ if (shouldBeExpanded) {
+ viewer.refreshWithContent(elements);
+ }
+ }
+
+// String label = grp.getText();
+// Matcher m = pattern.matcher(label.toLowerCase());
+// boolean visible = m.matches();
+// if (visible != grp.getVisible()) {
+// changed = true;
+// setGroupVisible(grp, visible);
+// }
+
+ return changed;
+ }
+
+ void setGroupExpandedWithoutNotification(PGroup grp, boolean expanded) {
+ // Ok, don't need to remove/add expand listener, PGroup will not notify
+ // listeners when setExpanded is invoked.
+ //grp.removeExpandListener(groupExpandListener);
+ storeGroupExpandedState(grp, expanded);
+ grp.setExpanded(expanded);
+ //grp.addExpandListener(groupExpandListener);
+ }
+
+ void setGroupVisible(PGroup group, boolean visible) {
+ GridData gd = (GridData) group.getLayoutData();
+ gd.exclude = !visible;
+ group.setVisible(visible);
+ }
+
+ boolean isGroupFiltered(String label) {
+ return !currentFilterPattern.matcher(label.toLowerCase()).matches();
+ }
+
+ class DragSourceParticipant extends AbstractDiagramParticipant implements IDragSourceParticipant {
+ @Reference Selection selection;
+ @Dependency PointerInteractor pi;
+ @Dependency TransformUtil util;
+ @Dependency PickContext pickContext;
+
+ @Override
+ public int canDrag(MouseDragBegin me) {
+ if (me.button != MouseEvent.LEFT_BUTTON) return 0;
+ if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return 0;
+ assertDependencies();
+
+ PickRequest req = new PickRequest(me.startCanvasPos);
+ req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
+ List<IElement> picks = new ArrayList<IElement>();
+ pickContext.pick(diagram, req, picks);
+ Set<IElement> sel = selection.getSelection(me.mouseId);
+
+ if (Collections.disjoint(sel, picks)) return 0;
+ // Box Select
+ return DnDConstants.ACTION_COPY;
+ }
+
+ @Override
+ public Transferable dragStart(DragGestureEvent e) {
+
+ AWTChassis chassis = (AWTChassis) e.getComponent();
+ ICanvasContext cc = chassis.getCanvasContext();
+ Selection sel = cc.getSingleItem(Selection.class);
+
+ Set<IElement> ss = sel.getSelection(0);
+ if (ss.isEmpty()) return null;
+ Object[] res = new Object[ss.size()];
+ int index = 0;
+ for (IElement ee : ss)
+ res[index++] = ee.getHint(ElementHints.KEY_OBJECT);
+
+ ISelection object = new StructuredSelection(res);
+
+ LocalObjectTransferable local = new LocalObjectTransferable(object);
+
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+ json.append(" \"type\" : \"Symbol\",");
+ json.append(" \"res\" : [");
+ int pos = 0;
+ for(int i=0;i<res.length;i++) {
+ if(pos > 0) json.append(",");
+ Object r = res[i];
+ if(r instanceof IdentifiedObject) {
+ Object id = ((IdentifiedObject) r).getId();
+ if(id instanceof IAdaptable) {
+ Object resource = ((IAdaptable) id).getAdapter(Resource.class);
+ if(resource != null) {
+ long rid = ((Resource)resource).getResourceId();
+ json.append(Long.toString(rid));
+ pos++;
+ }
+ }
+ }
+ }
+ json.append("] }");
+
+ StringSelection text = new StringSelection(json.toString());
+ PlaintextTransfer plainText = new PlaintextTransfer(json.toString());
+
+ return new MultiTransferable(local, text, plainText);
+
+ }
+
+ @Override
+ public int getAllowedOps() {
+ return DnDConstants.ACTION_COPY;
+ }
+ @Override
+ public void dragDropEnd(DragSourceDropEvent dsde) {
+// System.out.println("dragDropEnd: " + dsde);
+ LocalObjectTransfer.getTransfer().clear();
+ }
+ @Override
+ public void dragEnter(DragSourceDragEvent dsde) {
+ }
+ @Override
+ public void dragExit(DragSourceEvent dse) {
+ }
+ @Override
+ public void dragOver(DragSourceDragEvent dsde) {
+ }
+ @Override
+ public void dropActionChanged(DragSourceDragEvent dsde) {
+ }
+ }
+
+ ExpandListener groupExpandListener = new ExpandListener() {
+ @Override
+ public void itemCollapsed(ExpandEvent e) {
+ final PGroup group = (PGroup) e.widget;
+ group.setData(KEY_USER_EXPANDED, Boolean.FALSE);
+ storeGroupExpandedState(group, false);
+ //System.out.println("item collapsed: " + group + ", " + sc.getClientArea());
+ refreshScrolledComposite();
+ }
+ @Override
+ public void itemExpanded(ExpandEvent e) {
+ final PGroup group = (PGroup) e.widget;
+ group.setData(KEY_USER_EXPANDED, Boolean.TRUE);
+ storeGroupExpandedState(group, true);
+ //System.out.println("item expanded: " + group + ", " + sc.getClientArea());
+ ThreadUtils.asyncExec(swtThread, () -> {
+ GalleryViewer viewer = initializeGroup(group);
+ if (viewer == null)
+ return;
+ ThreadUtils.asyncExec(swtThread, () -> {
+ if (viewer.getControl().isDisposed())
+ return;
+ viewer.refresh();
+ refreshScrolledComposite();
+ });
+ });
+ }
+ };
+
+ public boolean isDefaultExpanded() {
+ return defaultExpanded;
+ }
+
+ public void setDefaultExpanded(boolean defaultExpanded) {
+ this.defaultExpanded = defaultExpanded;
+ }
+
+ Runnable disposer(final Widget w) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ if (w.isDisposed())
+ return;
+ w.dispose();
+ }
+ };
+ }
+
+ /**
+ * Invoke from SWT thread only.
+ *
+ * @param targetState
+ */
+ public void setAllExpandedStates(boolean targetState) {
+ setDefaultExpanded(targetState);
+ Control[] grps = c.getChildren();
+ boolean changed = false;
+ for (Control control : grps)
+ changed |= setExpandedState((PGroup) control, targetState, false);
+ if (changed)
+ refreshScrolledComposite();
+ }
+
+ /**
+ * Invoke from SWT thread only.
+ *
+ * @param grp
+ * @param targetState
+ * @return
+ */
+ boolean setExpandedState(PGroup grp, boolean targetState, boolean force) {
+ if (grp.isDisposed())
+ return false;
+
+ storeGroupExpandedState(grp, targetState);
+ grp.setData(KEY_USER_EXPANDED, Boolean.valueOf(targetState));
+ if ((force || grp.getExpanded() != targetState) && grp.getVisible()) {
+ final GalleryViewer viewer = initializeGroup(grp);
+ setGroupExpandedWithoutNotification(grp, targetState);
+ ThreadUtils.asyncExec(swtThread, () -> {
+ if (!grp.isDisposed()) {
+ if (viewer != null)
+ viewer.refresh();
+ refreshScrolledComposite();
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ class ImageLoader implements Runnable {
+
+ private final ImageProxy imageProxy;
+ private final ISymbolItem item;
+
+ public ImageLoader(ImageProxy imageProxy, ISymbolItem item) {
+ this.imageProxy = imageProxy;
+ this.item = item;
+ }
+
+ @Override
+ public void run() {
+ // SVG images using the SVGUniverse in SVGCache must use
+ // AWT thread for all operations.
+ ThreadUtils.asyncExec(AWTThread.getThreadAccess(), () -> runBlocking());
+ }
+
+ private void runBlocking() {
+ try {
+ ISymbolGroup group = item.getGroup();
+ if (group == null)
+ throw new ProvisionException("No ISymbolGroup available for ISymbolItem " + item);
+
+ GalleryViewer viewer = groupViewers.get(group);
+ if (viewer == null) {
+ // This is normal if this composite has been disposed while these are being ran.
+ //throw new ProvisionException("No GalleryViewer available ISymbolGroup " + group);
+ imageProxy.setSource(DefaultImages.UNKNOWN2.get());
+ return;
+ }
+
+ IHintContext hints = viewer.getDiagram();
+ if (hints == null)
+ throw new ProvisionException("No diagram available for GalleryViewer of group " + group);
+
+ hints.setHint(ISymbolItem.KEY_ELEMENT_CLASS_LISTENER, new ElementClassListener(imageCache, disposed, item));
+ final ElementClass ec = item.getElementClass(hints);
+
+ // Without this the symbol library will at times
+ // not update the final graphics for the symbol.
+ // It will keep displaying the hourglass pending icon instead.
+ symbolUpdate(disposed, imageProxy, ec);
+ } catch (ProvisionException e) {
+ ExceptionUtils.logWarning("Failed to provide element class for symbol item " + item, e);
+ imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
+ } catch (Exception e) {
+ ExceptionUtils.logError(e);
+ imageProxy.setSource(DefaultImages.ERROR_DECORATOR.get());
+ } finally {
+ }
+ }
+ }
+
+ static class ElementClassListener implements org.simantics.db.procedure.Listener<ElementClass> {
+ private Map<ISymbolItem, SoftReference<ImageProxy>> imageCache;
+ private final AtomicBoolean disposed;
+ private final ISymbolItem item;
+
+ public ElementClassListener(Map<ISymbolItem, SoftReference<ImageProxy>> imageCache, AtomicBoolean disposed, ISymbolItem item) {
+ this.imageCache = imageCache;
+ this.disposed = disposed;
+ this.item = item;
+ }
+
+ @Override
+ public void execute(final ElementClass ec) {
+ //System.out.println("SYMBOL CHANGED: " + item + " - disposed=" + disposed + " - " + ec);
+
+ final ImageProxy[] imageProxy = { null };
+ SoftReference<ImageProxy> proxyRef = imageCache.get(item);
+ if (proxyRef != null)
+ imageProxy[0] = proxyRef.get();
+ if (imageProxy[0] != null)
+ scheduleSymbolUpdate(disposed, imageProxy[0], ec);
+ }
+
+ @Override
+ public void exception(Throwable t) {
+ Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error in ElementClass request.", t));
+ }
+
+ @Override
+ public boolean isDisposed() {
+ //System.out.println("ElementClassListener.isDisposed " + item + " - " + disposed.get());
+ return disposed.get();
+ }
+ }
+
+ public FilterArea getFilterArea() {
+ return filter;
+ }
+
+ public static void scheduleSymbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {
+ if (disposed.get())
+ return;
+ ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() {
+ @Override
+ public void run() {
+ if (disposed.get())
+ return;
+ symbolUpdate(disposed, imageProxy, ec);
+ }
+ });
+ }
+
+ public static void symbolUpdate(final AtomicBoolean disposed, final ImageProxy imageProxy, final ElementClass ec) {
+ StaticSymbol ss = ec.getSingleItem(StaticSymbol.class);
+ Image source = ss == null ? DefaultImages.UNKNOWN2.get() : ss.getImage();
+ imageProxy.setSource(source);
+ }
+
+ Runnable filterActivator = new Runnable() {
+ @Override
+ public void run() {
+ filter.focus();
+ }
+ };
+ Listener filterActivationListener = new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ //System.out.println("event: " + event);
+ filterActivator.run();
+ }
+ };
+
+ ISymbolGroupListener groupListener = new ISymbolGroupListener() {
+ @Override
+ public void itemsChanged(ISymbolGroup group) {
+ //System.out.println("symbol group changed: " + group);
+ GalleryViewer viewer = groupViewers.get(group);
+ if (viewer != null) {
+ ISymbolItem[] input = group.getItems();
+ viewer.setInput(input);
+ }
+ }
+ };
+
+ IEventHandler externalEventHandler = new IEventHandler() {
+ @Override
+ public int getEventMask() {
+ return EventTypes.AnyMask;
+ }
+ @Override
+ public boolean handleEvent(org.simantics.scenegraph.g2d.events.Event e) {
+ IEventHandler handler = SymbolLibraryComposite.this.eventHandler;
+ return handler != null && EventTypes.passes(handler, e) ? handler.handleEvent(e) : false;
+ }
+ };
+
+ protected volatile IEventHandler eventHandler;
+
+ /**
+ * @param eventHandler
+ */
+ public void setEventHandler(IEventHandler eventHandler) {
+ this.eventHandler = eventHandler;
+ }
+
+ protected void storeGroupExpandedState(PGroup group, boolean expanded) {
+ ISymbolGroup symbolGroup = (ISymbolGroup) group.getData(SymbolLibraryKeys.KEY_GROUP);
+ //System.out.println("setGroupExpandedWithoutNotification(" + group + ", " + expanded + ", " + symbolGroup + ")");
+ if (symbolGroup != null) {
+ Object key = symbolGroupToKey(symbolGroup);
+ expandedGroups.put(key, expanded ? Boolean.TRUE : Boolean.FALSE);
+ }
+ }
+
+ private static Object symbolGroupToKey(ISymbolGroup symbolGroup) {
+ if (symbolGroup instanceof IIdentifiedObject)
+ return ((IIdentifiedObject) symbolGroup).getId();
+ return new Tuple2(symbolGroup.getName(), symbolGroup.getDescription());
+ }
+
+}