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