--- /dev/null
+package org.simantics.modeling.ui.actions.e4;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+\r
+import javax.annotation.PostConstruct;\r
+import javax.annotation.PreDestroy;\r
+import javax.inject.Inject;\r
+\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.e4.ui.di.UISynchronize;\r
+import org.eclipse.e4.ui.model.application.ui.menu.MToolControl;\r
+import org.eclipse.jface.action.Action;\r
+import org.eclipse.jface.action.ActionContributionItem;\r
+import org.eclipse.jface.action.IContributionItem;\r
+import org.eclipse.jface.action.MenuManager;\r
+import org.eclipse.jface.action.Separator;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.ToolBar;\r
+import org.eclipse.swt.widgets.ToolItem;\r
+import org.simantics.Simantics;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.common.NodeContextBuilder;\r
+import org.simantics.browsing.ui.model.InvalidContribution;\r
+import org.simantics.browsing.ui.model.actions.ActionBrowseContext;\r
+import org.simantics.browsing.ui.model.actions.IActionCategory;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.request.BinaryRead;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.procedure.Listener;\r
+import org.simantics.modeling.ui.Activator;\r
+\r
+/**\r
+ * Used like this in E4 model fragments:\r
+ * \r
+ * <pre>\r
+ * <fragments xsi:type="fragment:StringModelFragment" featurename="trimContributions" parentElementId="org.eclipse.e4.legacy.ide.application">\r
+ * <elements xsi:type="menu:TrimContribution" elementId="modeled.trim.id" parentId="org.eclipse.ui.main.toolbar" positionInParent="after=additions">\r
+ * <children xsi:type="menu:ToolBar" elementId="toolbar.id">\r
+ * <children xsi:type="menu:ToolControl" elementId="toolcontrol.id" contributionURI="bundleclass://org.simantics.modeling.ui/org.simantics.modeling.ui.actions.GlobalModeledToolbarActions">\r
+ * <tags>http://www.simantics.org/Project-1.2/MainToolbarActionContext</tags>\r
+ * </children>\r
+ * </children>\r
+ * </elements>\r
+ * </fragments>\r
+ * </pre>\r
+ * \r
+ * @author Antti Villberg\r
+ * @author Jani Simomaa\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class GlobalModeledToolbarActions {\r
+\r
+ @Inject UISynchronize sync;\r
+\r
+ private List<String> browseContexts;\r
+ private boolean disposed;\r
+ private Composite composite;\r
+\r
+ @PostConstruct\r
+ protected void create(Composite parent, MToolControl control) {\r
+ this.composite = parent;\r
+ browseContexts = new ArrayList<>(control.getTags());\r
+ Simantics.getSession().asyncRequest(\r
+ new GetContributions(Simantics.getProjectResource(), browseContexts),\r
+ new ContributionListener());\r
+ }\r
+\r
+ @PreDestroy\r
+ private void dispose() {\r
+ disposed = true;\r
+ if (composite != null) {\r
+ for (Control c : composite.getChildren())\r
+ c.dispose();\r
+ composite = null;\r
+ }\r
+ }\r
+\r
+ class ContributionListener implements Listener<List<IContributionItem>>, Runnable {\r
+\r
+ AtomicReference<List<IContributionItem>> lastResult = new AtomicReference<>();\r
+\r
+ @Override\r
+ public void execute(List<IContributionItem> result) {\r
+ if (composite != null) {\r
+ lastResult.set(result);\r
+ sync.asyncExec(this);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void run() {\r
+ List<IContributionItem> result = lastResult.getAndSet(null);\r
+ if (result == null || composite == null || composite.isDisposed())\r
+ return;\r
+\r
+ ToolBar tb = (ToolBar) composite.getParent();\r
+ for (ToolItem item : tb.getItems())\r
+ item.dispose();\r
+ for (IContributionItem item : result)\r
+ item.fill(tb, -1);\r
+\r
+ Composite tbp = tb.getParent();\r
+ tbp.layout(true, true);\r
+ }\r
+\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ Activator.getDefault().getLog().log(\r
+ new Status(IStatus.ERROR, Activator.PLUGIN_ID,\r
+ "Global modeled toolbar contribution listener ran into an unexpected exception.",\r
+ t));\r
+ }\r
+\r
+ @Override\r
+ public boolean isDisposed() {\r
+ return disposed;\r
+ }\r
+ }\r
+\r
+ static class GetContributions extends BinaryRead<Resource, Collection<String>, List<IContributionItem>> {\r
+ public GetContributions(Resource from, Collection<String> browseContextNames) {\r
+ super(from, browseContextNames);\r
+ }\r
+ @Override\r
+ public List<IContributionItem> perform(ReadGraph graph) throws DatabaseException {\r
+ return getContributionItems(graph, parameter, parameter2);\r
+ }\r
+ }\r
+\r
+ private static Collection<Resource> getBrowseContextResources(ReadGraph graph, Collection<String> browseContexts) throws DatabaseException {\r
+ List<Resource> result = new ArrayList<Resource>(browseContexts.size());\r
+ for (String name : browseContexts)\r
+ result.add(graph.getResource(name));\r
+ return result;\r
+ }\r
+\r
+ private static List<IContributionItem> getContributionItems(ReadGraph graph, Resource from, Collection<String> browseContextNames) throws DatabaseException {\r
+ Collection<Resource> browseContexts = getBrowseContextResources(graph, browseContextNames);\r
+ NodeContext nodeContext = NodeContextBuilder.buildWithInput(from);\r
+ try {\r
+ ActionBrowseContext defaultContext = ActionBrowseContext.create(graph, browseContexts);\r
+ ActionBrowseContext browseContext = ActionBrowseContext.get(graph, nodeContext, defaultContext);\r
+ Map<IActionCategory, List<Action>> result = browseContext.getActions(graph, nodeContext, Collections.singletonList(nodeContext));\r
+ return toContributionItems(result);\r
+ } catch (InvalidContribution e) {\r
+ Activator.getDefault().getLog().log(\r
+ new Status(IStatus.ERROR, Activator.PLUGIN_ID,\r
+ "Encountered invalid modeled contribution(s) while loading global modeled toolbar contributions.",\r
+ e));\r
+ }\r
+\r
+ return Collections.emptyList();\r
+ }\r
+\r
+ private static List<IContributionItem> toContributionItems(Map<IActionCategory, List<Action>> map) {\r
+ if (map.isEmpty())\r
+ return Collections.emptyList();\r
+\r
+ IActionCategory[] categories = map.keySet().toArray(new IActionCategory[map.size()]);\r
+ Arrays.sort(categories, IActionCategory.ACTION_CATEGORY_COMPARATOR);\r
+\r
+ ArrayList<IContributionItem> items = new ArrayList<>();\r
+ boolean first = true;\r
+ for (IActionCategory category : categories) {\r
+ List<Action> actions = map.get(category);\r
+ Collections.sort(actions, ACTION_COMPARATOR);\r
+\r
+ if (category != null && category.isSubmenu()) {\r
+ MenuManager manager = new MenuManager(category.getLabel());\r
+ for (Action action : actions)\r
+ manager.add(new ActionContributionItem(action));\r
+ items.add(manager);\r
+ }\r
+ else {\r
+ if (first)\r
+ first = false;\r
+ else\r
+ items.add(new Separator(category == null ? "" : category.getLabel()));\r
+ for (Action action : actions)\r
+ items.add(new ActionContributionItem(action));\r
+ }\r
+ }\r
+\r
+ return items;\r
+ }\r
+\r
+ private static final Comparator<Action> ACTION_COMPARATOR = (o1, o2) -> {\r
+ String t1 = o1.getText();\r
+ String t2 = o2.getText();\r
+ return t1 == null ?\r
+ (t2 == null ? 0 : -1)\r
+ : (t2 == null ? 1 : t1.compareTo(t2));\r
+ };\r
+\r
+}\r