--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2011 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.event.view;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.SelectionAdapter;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Text;\r
+import org.eclipse.swt.widgets.Tree;\r
+import org.eclipse.swt.widgets.TreeColumn;\r
+import org.eclipse.ui.IMemento;\r
+import org.eclipse.ui.IWorkbenchSite;\r
+import org.simantics.Simantics;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.Column;\r
+import org.simantics.browsing.ui.Column.Align;\r
+import org.simantics.browsing.ui.GraphExplorer;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
+import org.simantics.browsing.ui.NodeQueryManager;\r
+import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
+import org.simantics.browsing.ui.common.EvaluatorData;\r
+import org.simantics.browsing.ui.common.EvaluatorData.Evaluator;\r
+import org.simantics.browsing.ui.common.comparators.ImmutableLexicalComparable;\r
+import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
+import org.simantics.browsing.ui.content.ComparableContext;\r
+import org.simantics.browsing.ui.content.ComparableContextFactory;\r
+import org.simantics.browsing.ui.content.Labeler;\r
+import org.simantics.browsing.ui.swt.widgets.GraphExplorerComposite;\r
+import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport;\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.procedure.adapter.DisposableListener;\r
+import org.simantics.db.common.request.ObjectsWithType;\r
+import org.simantics.db.common.request.UniqueRead;\r
+import org.simantics.db.common.uri.UnescapedChildMapOfResource;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.exception.BindingException;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.DoesNotContainValueException;\r
+import org.simantics.db.exception.NoSingleResultException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.management.ISessionContext;\r
+import org.simantics.db.service.QueryControl;\r
+import org.simantics.event.ontology.EventResource;\r
+import org.simantics.event.util.EventUtils;\r
+import org.simantics.operation.Layer0X;\r
+import org.simantics.simulation.ontology.SimulationResource;\r
+import org.simantics.ui.workbench.IPropertyPage;\r
+import org.simantics.utils.datastructures.ArrayMap;\r
+import org.simantics.utils.strings.AlphanumComparator;\r
+import org.simantics.utils.ui.workbench.StringMemento;\r
+import org.simantics.views.swt.SimanticsView;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class EventView extends SimanticsView {\r
+\r
+ private static final String EVENTS_TABLE_MEMENTO_ID = "events";\r
+ private static final String BROWSE_CONTEXT = "http://www.simantics.org/Event-1.2/View/EventBrowseContext";\r
+ private static final String CONTEXT_MENU_ID = "org.simantics.event.view.popup";\r
+\r
+ private GraphExplorerComposite eventExplorer;\r
+ private TableComparatorFactory comparator;\r
+ \r
+\r
+ @Override\r
+ protected Set<String> getBrowseContexts() {\r
+ return Collections.singleton(BROWSE_CONTEXT);\r
+ }\r
+\r
+ @Override\r
+ protected String getContextMenuId() {\r
+ return CONTEXT_MENU_ID;\r
+ }\r
+\r
+ @Override\r
+ protected void createControls(Composite body, IWorkbenchSite site, ISessionContext context, WidgetSupport support) {\r
+ \r
+ Column[] COLUMNS = new Column[] {\r
+ //new Column(ColumnKeys.SINGLE, "S", Align.LEFT, 40, "S", false),\r
+ new Column(Constants.COLUMN_EVENT_INDEX, "#", Align.LEFT, 70, "Event Index", false),\r
+ new Column(Constants.COLUMN_TIMESTAMP, "Time Stamp", Align.LEFT, 100, "Time Stamp", false),\r
+ new Column(Constants.COLUMN_MILESTONE, "M", Align.LEFT, 24, "Is Milestone?", false),\r
+ new Column(Constants.COLUMN_EVENT_TYPE, "Type", Align.LEFT, 110, "Event Type", false),\r
+ new Column(Constants.COLUMN_RETURNED, "R", Align.LEFT, 24, "Has Event Returned?", false),\r
+ new Column(Constants.COLUMN_TAG_NAME, "Tag", Align.LEFT, 120, "Tag Name", true, 1),\r
+ new Column(Constants.COLUMN_MESSAGE, "Message", Align.LEFT, 100, "Message", true, 2),\r
+ new Column(Constants.COLUMN_RETURN_TIME, "Return Time", Align.LEFT, 100, "Return Event Time Stamp", false),\r
+ };\r
+\r
+ final Text headerText = new Text(body, SWT.NONE);\r
+\r
+ final DisposableListener<String> headerTextListener = new DisposableListener<String>() {\r
+\r
+ @Override\r
+ public void execute(final String result) {\r
+ Display d = headerText.getDisplay();\r
+ if(d.isDisposed()) return;\r
+ d.asyncExec(new Runnable() {\r
+\r
+ @Override\r
+ public void run() {\r
+ if(headerText.isDisposed()) return;\r
+ headerText.setText(result);\r
+ headerText.getParent().layout();\r
+ }\r
+ \r
+ });\r
+ }\r
+\r
+ @Override\r
+ public void exception(Throwable t) {\r
+ Logger.defaultLogError(t);\r
+ }\r
+ \r
+ };\r
+ \r
+ headerText.addDisposeListener(new DisposeListener() {\r
+ \r
+ @Override\r
+ public void widgetDisposed(DisposeEvent e) {\r
+ headerTextListener.dispose();\r
+ }\r
+ \r
+ });\r
+ \r
+ headerText.setText("");\r
+ headerText.setBackground(body.getBackground());\r
+ GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, false).span(2, 1).applyTo(headerText);\r
+\r
+ Simantics.getSession().asyncRequest(new UniqueRead<String>() {\r
+\r
+ public String getChildrenImpl(ReadGraph graph, Resource project, List<Resource> eventLogs) throws DatabaseException {\r
+ \r
+ int discardedEvents = 0;\r
+ \r
+ for(Resource log : eventLogs) {\r
+ \r
+ Map<String,Resource> slices = graph.syncRequest(new UnescapedChildMapOfResource(log));\r
+ if(slices.isEmpty()) continue; \r
+ ArrayList<String> keys = new ArrayList<String>(slices.keySet());\r
+ Collections.sort(keys, AlphanumComparator.COMPARATOR);\r
+ Integer base = Integer.parseInt(keys.get(0));\r
+ discardedEvents += EventUtils.SLICE_SIZE * base;\r
+ \r
+ }\r
+\r
+ if(discardedEvents > 0)\r
+ return discardedEvents + " events in chronological order have been automatically discarded from event log";\r
+ else \r
+ return "";\r
+ \r
+ }\r
+ \r
+ @Override\r
+ public String perform(ReadGraph graph) throws DatabaseException {\r
+\r
+ List<Resource> eventLogs = EventView.getEventLogs(graph);\r
+ QueryControl qc = graph.getService(QueryControl.class);\r
+ return getChildrenImpl(qc.getIndependentGraph(graph), Simantics.getProjectResource(), eventLogs);\r
+\r
+ }\r
+ \r
+ }, headerTextListener);\r
+\r
+ eventExplorer = new GraphExplorerComposite(\r
+ ArrayMap.keys("displaySelectors", "displayFilter", "maxChildren").values(false, false, 5000),\r
+ site, body, support, SWT.FULL_SELECTION | SWT.MULTI) {\r
+ @Override\r
+ protected void initializeExplorerWithEvaluator(GraphExplorer explorer, ISessionContext context,\r
+ EvaluatorData data) {\r
+ Evaluator eval = data.newEvaluator();\r
+ IMemento m = memento;\r
+ if (comparator != null) {\r
+ m = new StringMemento();\r
+ comparator.saveState(EVENTS_TABLE_MEMENTO_ID, m);\r
+ }\r
+ comparator = new TableComparatorFactory(explorer);\r
+ if (m != null)\r
+ comparator.restoreState(EVENTS_TABLE_MEMENTO_ID, m);\r
+ eval.addComparator(comparator, 2.0);\r
+ data.addEvaluator(Object.class, eval);\r
+ }\r
+ };\r
+ eventExplorer.setBrowseContexts(getBrowseContexts());\r
+ eventExplorer.setContextMenuId(getContextMenuId());\r
+ eventExplorer.setColumns(COLUMNS);\r
+ eventExplorer.finish();\r
+ GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(eventExplorer);\r
+\r
+ attachHeaderListeners(eventExplorer);\r
+ }\r
+\r
+ public static List<Resource> getEventLogs(ReadGraph graph) throws NoSingleResultException, DoesNotContainValueException, BindingException, ServiceException, DatabaseException {\r
+ Layer0X L0X = Layer0X.getInstance(graph);\r
+ SimulationResource SIMU = SimulationResource.getInstance(graph);\r
+ EventResource EVENT = EventResource.getInstance(graph);\r
+\r
+ Resource project = Simantics.getProjectResource();\r
+\r
+ List<Resource> eventLogs = new ArrayList<Resource>();\r
+ for (Resource activeModel : graph.syncRequest(new ObjectsWithType(project, L0X.Activates, SIMU.Model))) {\r
+ for (Resource eventLog : graph.syncRequest(new ObjectsWithType(activeModel, EVENT.HasEventLog, EVENT.EventLog))) {\r
+ if (!graph.hasStatement(eventLog, EVENT.Hidden)) {\r
+ eventLogs.add(eventLog);\r
+// graph.getRelatedValue(eventLog, EVENT.HasModificationCounter, Bindings.INTEGER);\r
+ }\r
+ }\r
+ }\r
+ return eventLogs;\r
+ }\r
+\r
+ @Override\r
+ public void saveState(IMemento memento) {\r
+ super.saveState(memento);\r
+ if (comparator != null)\r
+ comparator.saveState(EVENTS_TABLE_MEMENTO_ID, memento);\r
+ }\r
+\r
+ private void attachHeaderListeners(GraphExplorerComposite explorer) {\r
+ final Tree tree = explorer.getExplorerControl();\r
+\r
+ String sortColumn = null;\r
+ if (comparator != null) {\r
+ sortColumn = comparator.getColumn();\r
+ }\r
+\r
+ for (final TreeColumn column : tree.getColumns()) {\r
+ column.addSelectionListener(new SelectionAdapter() {\r
+ @Override \r
+ public void widgetSelected(SelectionEvent e) {\r
+ Column c = (Column) column.getData();\r
+ String key = transformColumn( c.getKey() );\r
+\r
+ comparator.setColumn(key);\r
+ comparator.toggleAscending(key);\r
+ comparator.refresh();\r
+\r
+ // Visualizes the selected sorting in the Tree column header\r
+ tree.setSortColumn(column);\r
+ tree.setSortDirection(comparator.isAscending(key) ? SWT.DOWN : SWT.UP);\r
+ }\r
+ });\r
+\r
+ Column c = (Column) column.getData();\r
+ String key = transformColumn( c.getKey() );\r
+ if (key.equals(sortColumn)) {\r
+ tree.setSortColumn(column);\r
+ tree.setSortDirection(comparator.isAscending(key) ? SWT.DOWN : SWT.UP);\r
+ }\r
+ }\r
+ }\r
+\r
+ private Class<?> getColumnType(String key) {\r
+ if (Constants.COLUMN_EVENT_INDEX.equals(key))\r
+ return Long.class;\r
+ if (Constants.COLUMN_TIMESTAMP_NUMERIC.equals(key)\r
+ || Constants.COLUMN_RETURN_TIME_NUMERIC.equals(key))\r
+ return Double.class;\r
+ return String.class;\r
+ }\r
+\r
+ private String transformColumn(String key) {\r
+ // HACK: for proper numeric timestamp sorting since\r
+ // formatted time values don't sort all that well.\r
+ if (Constants.COLUMN_TIMESTAMP.equals(key))\r
+ key = Constants.COLUMN_TIMESTAMP_NUMERIC;\r
+ if (Constants.COLUMN_RETURN_TIME.equals(key))\r
+ key = Constants.COLUMN_RETURN_TIME_NUMERIC;\r
+ return key;\r
+ }\r
+\r
+ @Override\r
+ public void setFocus() {\r
+ eventExplorer.setFocus();\r
+ }\r
+\r
+ @Override\r
+ protected IPropertyPage getPropertyPage() {\r
+ return null;\r
+ }\r
+\r
+ public class TableComparatorFactory implements ComparableContextFactory {\r
+\r
+ private static final String SORTING = "sorting";\r
+ private final static String SORT_COL = "sortCol";\r
+ private final static String COLS = "cols";\r
+ private final static String COL_ASCEND = "ascend";\r
+\r
+ UpdateTrigger updateTrigger = new UpdateTrigger();\r
+\r
+ /** Current sort column */\r
+ private String column = null;\r
+ /** Ascending state of each column */\r
+ private Map<String, Boolean> ascendMap = new HashMap<String, Boolean>();\r
+\r
+ public TableComparatorFactory(GraphExplorer ge) {\r
+ ge.setPrimitiveProcessor(updateTrigger);\r
+ }\r
+\r
+ public void saveState(String id, IMemento memento) {\r
+ if (id == null)\r
+ throw new NullPointerException("null id");\r
+ IMemento m = null;\r
+ for (IMemento child : memento.getChildren(SORTING)) {\r
+ if (id.equals(child.getID()))\r
+ m = child;\r
+ }\r
+ if (m == null)\r
+ m = memento.createChild(SORTING, id);\r
+ if (getColumn() != null)\r
+ m.putString(SORT_COL, getColumn());\r
+\r
+ columns:\r
+ for (String columnKey : ascendMap.keySet()) {\r
+ for (IMemento col : m.getChildren(COLS)) {\r
+ if (columnKey.equals(col.getID())) {\r
+ col.putBoolean(COL_ASCEND, isAscending(columnKey));\r
+ continue columns;\r
+ }\r
+ }\r
+ IMemento col = m.createChild(COLS, columnKey);\r
+ col.putBoolean(COL_ASCEND, isAscending(columnKey));\r
+ }\r
+ }\r
+\r
+ public void restoreState(String id, IMemento memento) {\r
+ if (id == null)\r
+ throw new NullPointerException("null id");\r
+ if (!hasState(id, memento))\r
+ return;\r
+ for (IMemento m : memento.getChildren(SORTING)) {\r
+ if (!id.equals(m.getID()))\r
+ continue;\r
+ for (IMemento column : m.getChildren(COLS)) {\r
+ setAscending( column.getID(), column.getBoolean(COL_ASCEND) );\r
+ }\r
+ setColumn( m.getString(SORT_COL) );\r
+ break;\r
+ }\r
+ }\r
+\r
+ public boolean hasState(String id, IMemento memento) {\r
+ for (IMemento m : memento.getChildren(SORTING))\r
+ if (id.equals(m.getID()))\r
+ return true;\r
+ return false;\r
+ }\r
+\r
+ public void setAscending(String column, Boolean ascending) {\r
+ if (ascending == null)\r
+ ascendMap.remove(column);\r
+ else\r
+ ascendMap.put(column, ascending);\r
+ }\r
+\r
+ public boolean isAscending(String column) {\r
+ return !Boolean.FALSE.equals(ascendMap.get(column));\r
+ }\r
+\r
+ public void setColumn(String column) {\r
+ this.column = column;\r
+ }\r
+\r
+ public void toggleAscending(String column) {\r
+ Boolean ascending = ascendMap.get(column);\r
+ if (ascending == null) {\r
+ ascendMap.put(column, Boolean.TRUE);\r
+ } else {\r
+ ascendMap.put(column, !ascending);\r
+ }\r
+ }\r
+\r
+ public void refresh() {\r
+ updateTrigger.refresh();\r
+ }\r
+\r
+ public String getColumn() {\r
+ return column;\r
+ }\r
+\r
+ @Override\r
+ public ComparableContext[] create(NodeQueryManager manager, NodeContext parent, NodeContext[] children) {\r
+ // To make this query refreshable.\r
+ manager.query(parent, UpdateTrigger.KEY);\r
+\r
+ // Don't sort if no sort column is defined\r
+ if (column == null)\r
+ return null;\r
+\r
+ final boolean _ascending = isAscending(column);\r
+ final Class<?> _clazz = getColumnType(column);\r
+\r
+ ComparableContext[] result = new ComparableContext[children.length];\r
+ for (int i = 0; i < children.length; i++) {\r
+ NodeContext child = children[i];\r
+ Labeler labeler = manager.query(child, BuiltinKeys.SELECTED_LABELER);\r
+\r
+ int category = (labeler != null) ? labeler.getCategory() : 0;\r
+ String label = (labeler != null && column != null) ? labeler.getLabels().get(column) : "";\r
+ if (label == null)\r
+ label = "";\r
+\r
+ result[i] = new ImmutableLexicalComparable(category, label, child) {\r
+ final boolean ascending = _ascending;\r
+ final Class<?> clazz = _clazz;\r
+\r
+ @SuppressWarnings("unchecked")\r
+ @Override\r
+ public int compareTo(ComparableContext arg0) {\r
+ ImmutableLexicalComparable other = (ImmutableLexicalComparable) arg0;\r
+\r
+ int catDelta = getCategory() - other.getCategory();\r
+ if (!ascending)\r
+ catDelta = -catDelta;\r
+ if (catDelta != 0)\r
+ return catDelta;\r
+\r
+ String label1 = getLabel();\r
+ String label2 = other.getLabel();\r
+\r
+ @SuppressWarnings("rawtypes")\r
+ Comparator comparator = AlphanumComparator.CASE_INSENSITIVE_COMPARATOR;\r
+ if (clazz == Double.class) {\r
+ comparator = DoubleStringComparator.INSTANCE;\r
+ } else if (clazz == Long.class) {\r
+ comparator = LongStringComparator.INSTANCE;\r
+ }\r
+\r
+ return ascending ? comparator.compare(label1, label2)\r
+ : comparator.compare(label2, label1);\r
+ }\r
+ };\r
+ }\r
+\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "Event Table Sort";\r
+ }\r
+\r
+ }\r
+\r
+ private static class LongStringComparator implements Comparator<String> {\r
+\r
+ private static final LongStringComparator INSTANCE = new LongStringComparator();\r
+\r
+ @Override\r
+ public int compare(String s1, String s2) {\r
+ long n1 = parseLong(s1);\r
+ long n2 = parseLong(s2);\r
+ return compare(n1, n2);\r
+ }\r
+\r
+ private long parseLong(String s) {\r
+ try {\r
+ return Long.parseLong(s);\r
+ } catch (NumberFormatException e1) {\r
+ return Long.MIN_VALUE;\r
+ }\r
+ }\r
+\r
+ public static int compare(long x, long y) {\r
+ return (x < y) ? -1 : ((x == y) ? 0 : 1);\r
+ }\r
+ }\r
+\r
+ private static class DoubleStringComparator implements Comparator<String> {\r
+\r
+ private static final DoubleStringComparator INSTANCE = new DoubleStringComparator();\r
+\r
+ @Override\r
+ public int compare(String s1, String s2) {\r
+ double n1 = parseDouble(s1);\r
+ double n2 = parseDouble(s2);\r
+ return Double.compare(n1, n2);\r
+ }\r
+\r
+ private double parseDouble(String s) {\r
+ try {\r
+ return Double.parseDouble(s);\r
+ } catch (NumberFormatException e2) {\r
+ return Double.NaN;\r
+ }\r
+ }\r
+ }\r
+\r
+ public static class UpdateTrigger extends AbstractPrimitiveQueryProcessor<Double> implements ProcessorLifecycle {\r
+\r
+ private Double random = Math.random();\r
+ private Map<NodeContext, PrimitiveQueryUpdater> contexts = new THashMap<NodeContext, PrimitiveQueryUpdater>();\r
+\r
+ public static final PrimitiveQueryKey<Double> KEY = new PrimitiveQueryKey<Double>() {\r
+ @Override\r
+ public String toString() {\r
+ return "TRIGGER";\r
+ }\r
+ };\r
+\r
+ @Override\r
+ public Object getIdentifier() {\r
+ return KEY;\r
+ }\r
+\r
+ public void refresh() {\r
+ random = Math.random();\r
+ for (Map.Entry<NodeContext, PrimitiveQueryUpdater> entry : contexts.entrySet()) {\r
+ entry.getValue().scheduleReplace(entry.getKey(), KEY, random);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Double query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Double> key) {\r
+ this.contexts.put(context, updater);\r
+ return random;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "UpdateTrigger";\r
+ }\r
+\r
+ @Override\r
+ public void attached(GraphExplorer explorer) {\r
+ }\r
+\r
+ @Override\r
+ public void detached(GraphExplorer explorer) {\r
+ clear();\r
+ }\r
+\r
+ @Override\r
+ public void clear() {\r
+ contexts.clear();\r
+ }\r
+\r
+ }\r
+\r
+}\r