]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.event/src/org/simantics/event/view/EventView.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.event / src / org / simantics / event / view / EventView.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.event.view;\r
13 \r
14 import gnu.trove.map.hash.THashMap;\r
15 \r
16 import java.util.ArrayList;\r
17 import java.util.Collections;\r
18 import java.util.Comparator;\r
19 import java.util.HashMap;\r
20 import java.util.List;\r
21 import java.util.Map;\r
22 import java.util.Set;\r
23 \r
24 import org.eclipse.jface.layout.GridDataFactory;\r
25 import org.eclipse.swt.SWT;\r
26 import org.eclipse.swt.events.DisposeEvent;\r
27 import org.eclipse.swt.events.DisposeListener;\r
28 import org.eclipse.swt.events.SelectionAdapter;\r
29 import org.eclipse.swt.events.SelectionEvent;\r
30 import org.eclipse.swt.widgets.Composite;\r
31 import org.eclipse.swt.widgets.Display;\r
32 import org.eclipse.swt.widgets.Text;\r
33 import org.eclipse.swt.widgets.Tree;\r
34 import org.eclipse.swt.widgets.TreeColumn;\r
35 import org.eclipse.ui.IMemento;\r
36 import org.eclipse.ui.IWorkbenchSite;\r
37 import org.simantics.Simantics;\r
38 import org.simantics.browsing.ui.BuiltinKeys;\r
39 import org.simantics.browsing.ui.Column;\r
40 import org.simantics.browsing.ui.Column.Align;\r
41 import org.simantics.browsing.ui.GraphExplorer;\r
42 import org.simantics.browsing.ui.NodeContext;\r
43 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
44 import org.simantics.browsing.ui.NodeQueryManager;\r
45 import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
46 import org.simantics.browsing.ui.common.EvaluatorData;\r
47 import org.simantics.browsing.ui.common.EvaluatorData.Evaluator;\r
48 import org.simantics.browsing.ui.common.comparators.ImmutableLexicalComparable;\r
49 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
50 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
51 import org.simantics.browsing.ui.content.ComparableContext;\r
52 import org.simantics.browsing.ui.content.ComparableContextFactory;\r
53 import org.simantics.browsing.ui.content.Labeler;\r
54 import org.simantics.browsing.ui.swt.widgets.GraphExplorerComposite;\r
55 import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport;\r
56 import org.simantics.databoard.Bindings;\r
57 import org.simantics.db.ReadGraph;\r
58 import org.simantics.db.Resource;\r
59 import org.simantics.db.common.procedure.adapter.DisposableListener;\r
60 import org.simantics.db.common.request.ObjectsWithType;\r
61 import org.simantics.db.common.request.UniqueRead;\r
62 import org.simantics.db.common.uri.UnescapedChildMapOfResource;\r
63 import org.simantics.db.common.utils.Logger;\r
64 import org.simantics.db.exception.BindingException;\r
65 import org.simantics.db.exception.DatabaseException;\r
66 import org.simantics.db.exception.DoesNotContainValueException;\r
67 import org.simantics.db.exception.NoSingleResultException;\r
68 import org.simantics.db.exception.ServiceException;\r
69 import org.simantics.db.management.ISessionContext;\r
70 import org.simantics.db.service.QueryControl;\r
71 import org.simantics.event.ontology.EventResource;\r
72 import org.simantics.event.util.EventUtils;\r
73 import org.simantics.operation.Layer0X;\r
74 import org.simantics.simulation.ontology.SimulationResource;\r
75 import org.simantics.ui.workbench.IPropertyPage;\r
76 import org.simantics.utils.datastructures.ArrayMap;\r
77 import org.simantics.utils.strings.AlphanumComparator;\r
78 import org.simantics.utils.ui.workbench.StringMemento;\r
79 import org.simantics.views.swt.SimanticsView;\r
80 \r
81 /**\r
82  * @author Tuukka Lehtonen\r
83  */\r
84 public class EventView extends SimanticsView {\r
85 \r
86     private static final String EVENTS_TABLE_MEMENTO_ID = "events";\r
87     private static final String  BROWSE_CONTEXT  = "http://www.simantics.org/Event-1.2/View/EventBrowseContext";\r
88     private static final String  CONTEXT_MENU_ID = "org.simantics.event.view.popup";\r
89 \r
90     private GraphExplorerComposite eventExplorer;\r
91     private TableComparatorFactory comparator;\r
92     \r
93 \r
94     @Override\r
95     protected Set<String> getBrowseContexts() {\r
96         return Collections.singleton(BROWSE_CONTEXT);\r
97     }\r
98 \r
99     @Override\r
100     protected String getContextMenuId() {\r
101         return CONTEXT_MENU_ID;\r
102     }\r
103 \r
104     @Override\r
105     protected void createControls(Composite body, IWorkbenchSite site, ISessionContext context, WidgetSupport support) {\r
106         \r
107         Column[] COLUMNS = new Column[] {\r
108                 //new Column(ColumnKeys.SINGLE, "S", Align.LEFT, 40, "S", false),\r
109                 new Column(Constants.COLUMN_EVENT_INDEX, "#", Align.LEFT, 70, "Event Index", false),\r
110                 new Column(Constants.COLUMN_TIMESTAMP, "Time Stamp", Align.LEFT, 100, "Time Stamp", false),\r
111                 new Column(Constants.COLUMN_MILESTONE, "M", Align.LEFT, 24, "Is Milestone?", false),\r
112                 new Column(Constants.COLUMN_EVENT_TYPE, "Type", Align.LEFT, 110, "Event Type", false),\r
113                 new Column(Constants.COLUMN_RETURNED, "R", Align.LEFT, 24, "Has Event Returned?", false),\r
114                 new Column(Constants.COLUMN_TAG_NAME, "Tag", Align.LEFT, 120, "Tag Name", true, 1),\r
115                 new Column(Constants.COLUMN_MESSAGE, "Message", Align.LEFT, 100, "Message", true, 2),\r
116                 new Column(Constants.COLUMN_RETURN_TIME, "Return Time", Align.LEFT, 100, "Return Event Time Stamp", false),\r
117         };\r
118 \r
119         final Text headerText = new Text(body, SWT.NONE);\r
120 \r
121         final DisposableListener<String> headerTextListener = new DisposableListener<String>() {\r
122 \r
123                         @Override\r
124                         public void execute(final String result) {\r
125                                 Display d = headerText.getDisplay();\r
126                                 if(d.isDisposed()) return;\r
127                                 d.asyncExec(new Runnable() {\r
128 \r
129                                         @Override\r
130                                         public void run() {\r
131                                                 if(headerText.isDisposed()) return;\r
132                                                 headerText.setText(result);\r
133                                                 headerText.getParent().layout();\r
134                                         }\r
135                                         \r
136                                 });\r
137                         }\r
138 \r
139                         @Override\r
140                         public void exception(Throwable t) {\r
141                                 Logger.defaultLogError(t);\r
142                         }\r
143                         \r
144                 };\r
145         \r
146         headerText.addDisposeListener(new DisposeListener() {\r
147                         \r
148                         @Override\r
149                         public void widgetDisposed(DisposeEvent e) {\r
150                                 headerTextListener.dispose();\r
151                         }\r
152                         \r
153                 });\r
154         \r
155         headerText.setText("");\r
156         headerText.setBackground(body.getBackground());\r
157         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).grab(true, false).span(2, 1).applyTo(headerText);\r
158 \r
159         Simantics.getSession().asyncRequest(new UniqueRead<String>() {\r
160 \r
161                    public String getChildrenImpl(ReadGraph graph, Resource project, List<Resource> eventLogs) throws DatabaseException {\r
162                            \r
163                            int discardedEvents = 0;\r
164                            \r
165                            for(Resource log : eventLogs) {\r
166                                    \r
167                                    Map<String,Resource> slices = graph.syncRequest(new UnescapedChildMapOfResource(log));\r
168                                    if(slices.isEmpty()) continue;                                          \r
169                                    ArrayList<String> keys = new ArrayList<String>(slices.keySet());\r
170                                 Collections.sort(keys, AlphanumComparator.COMPARATOR);\r
171                                 Integer base = Integer.parseInt(keys.get(0));\r
172                                 discardedEvents += EventUtils.SLICE_SIZE * base;\r
173                                 \r
174                            }\r
175 \r
176                            if(discardedEvents > 0)\r
177                                    return discardedEvents + " events in chronological order have been automatically discarded from event log";\r
178                            else \r
179                                    return "";\r
180                         \r
181                     }\r
182                 \r
183                    @Override\r
184                    public String perform(ReadGraph graph) throws DatabaseException {\r
185 \r
186                        List<Resource> eventLogs = EventView.getEventLogs(graph);\r
187                            QueryControl qc = graph.getService(QueryControl.class);\r
188                            return getChildrenImpl(qc.getIndependentGraph(graph), Simantics.getProjectResource(), eventLogs);\r
189 \r
190                    }\r
191                 \r
192         }, headerTextListener);\r
193 \r
194         eventExplorer = new GraphExplorerComposite(\r
195                 ArrayMap.keys("displaySelectors", "displayFilter", "maxChildren").values(false, false, 5000),\r
196                 site, body, support, SWT.FULL_SELECTION | SWT.MULTI) {\r
197             @Override\r
198             protected void initializeExplorerWithEvaluator(GraphExplorer explorer, ISessionContext context,\r
199                     EvaluatorData data) {\r
200                 Evaluator eval = data.newEvaluator();\r
201                 IMemento m = memento;\r
202                 if (comparator != null) {\r
203                     m = new StringMemento();\r
204                     comparator.saveState(EVENTS_TABLE_MEMENTO_ID, m);\r
205                 }\r
206                 comparator = new TableComparatorFactory(explorer);\r
207                 if (m != null)\r
208                     comparator.restoreState(EVENTS_TABLE_MEMENTO_ID, m);\r
209                 eval.addComparator(comparator, 2.0);\r
210                 data.addEvaluator(Object.class, eval);\r
211             }\r
212         };\r
213         eventExplorer.setBrowseContexts(getBrowseContexts());\r
214         eventExplorer.setContextMenuId(getContextMenuId());\r
215         eventExplorer.setColumns(COLUMNS);\r
216         eventExplorer.finish();\r
217         GridDataFactory.fillDefaults().grab(true, true).span(2, 1).applyTo(eventExplorer);\r
218 \r
219         attachHeaderListeners(eventExplorer);\r
220     }\r
221 \r
222     public static List<Resource> getEventLogs(ReadGraph graph) throws NoSingleResultException, DoesNotContainValueException, BindingException, ServiceException, DatabaseException {\r
223         Layer0X L0X = Layer0X.getInstance(graph);\r
224         SimulationResource SIMU = SimulationResource.getInstance(graph);\r
225         EventResource EVENT = EventResource.getInstance(graph);\r
226 \r
227         Resource project = Simantics.getProjectResource();\r
228 \r
229         List<Resource> eventLogs = new ArrayList<Resource>();\r
230         for (Resource activeModel : graph.syncRequest(new ObjectsWithType(project, L0X.Activates, SIMU.Model))) {\r
231             for (Resource eventLog : graph.syncRequest(new ObjectsWithType(activeModel, EVENT.HasEventLog, EVENT.EventLog))) {\r
232                 if (!graph.hasStatement(eventLog, EVENT.Hidden)) {\r
233                     eventLogs.add(eventLog);\r
234 //                    graph.getRelatedValue(eventLog, EVENT.HasModificationCounter, Bindings.INTEGER);\r
235                 }\r
236             }\r
237         }\r
238         return eventLogs;\r
239     }\r
240 \r
241     @Override\r
242     public void saveState(IMemento memento) {\r
243         super.saveState(memento);\r
244         if (comparator != null)\r
245             comparator.saveState(EVENTS_TABLE_MEMENTO_ID, memento);\r
246     }\r
247 \r
248     private void attachHeaderListeners(GraphExplorerComposite explorer) {\r
249         final Tree tree = explorer.getExplorerControl();\r
250 \r
251         String sortColumn = null;\r
252         if (comparator != null) {\r
253             sortColumn = comparator.getColumn();\r
254         }\r
255 \r
256         for (final TreeColumn column : tree.getColumns()) {\r
257             column.addSelectionListener(new SelectionAdapter() {\r
258                 @Override   \r
259                 public void widgetSelected(SelectionEvent e) {\r
260                     Column c = (Column) column.getData();\r
261                     String key = transformColumn( c.getKey() );\r
262 \r
263                     comparator.setColumn(key);\r
264                     comparator.toggleAscending(key);\r
265                     comparator.refresh();\r
266 \r
267                     // Visualizes the selected sorting in the Tree column header\r
268                     tree.setSortColumn(column);\r
269                     tree.setSortDirection(comparator.isAscending(key) ? SWT.DOWN : SWT.UP);\r
270                 }\r
271             });\r
272 \r
273             Column c = (Column) column.getData();\r
274             String key = transformColumn( c.getKey() );\r
275             if (key.equals(sortColumn)) {\r
276                 tree.setSortColumn(column);\r
277                 tree.setSortDirection(comparator.isAscending(key) ? SWT.DOWN : SWT.UP);\r
278             }\r
279         }\r
280     }\r
281 \r
282     private Class<?> getColumnType(String key) {\r
283         if (Constants.COLUMN_EVENT_INDEX.equals(key))\r
284             return Long.class;\r
285         if (Constants.COLUMN_TIMESTAMP_NUMERIC.equals(key)\r
286                 || Constants.COLUMN_RETURN_TIME_NUMERIC.equals(key))\r
287             return Double.class;\r
288         return String.class;\r
289     }\r
290 \r
291     private String transformColumn(String key) {\r
292         // HACK: for proper numeric timestamp sorting since\r
293         // formatted time values don't sort all that well.\r
294         if (Constants.COLUMN_TIMESTAMP.equals(key))\r
295             key = Constants.COLUMN_TIMESTAMP_NUMERIC;\r
296         if (Constants.COLUMN_RETURN_TIME.equals(key))\r
297             key = Constants.COLUMN_RETURN_TIME_NUMERIC;\r
298         return key;\r
299     }\r
300 \r
301     @Override\r
302     public void setFocus() {\r
303         eventExplorer.setFocus();\r
304     }\r
305 \r
306     @Override\r
307     protected IPropertyPage getPropertyPage() {\r
308         return null;\r
309     }\r
310 \r
311     public class TableComparatorFactory implements ComparableContextFactory {\r
312 \r
313         private static final String  SORTING       = "sorting";\r
314         private final static String  SORT_COL      = "sortCol";\r
315         private final static String  COLS          = "cols";\r
316         private final static String  COL_ASCEND    = "ascend";\r
317 \r
318         UpdateTrigger updateTrigger = new UpdateTrigger();\r
319 \r
320         /** Current sort column */\r
321         private String column = null;\r
322         /** Ascending state of each column */\r
323         private Map<String, Boolean> ascendMap = new HashMap<String, Boolean>();\r
324 \r
325         public TableComparatorFactory(GraphExplorer ge) {\r
326             ge.setPrimitiveProcessor(updateTrigger);\r
327         }\r
328 \r
329         public void saveState(String id, IMemento memento) {\r
330             if (id == null)\r
331                 throw new NullPointerException("null id");\r
332             IMemento m = null;\r
333             for (IMemento child : memento.getChildren(SORTING)) {\r
334                 if (id.equals(child.getID()))\r
335                     m = child;\r
336             }\r
337             if (m == null)\r
338                 m = memento.createChild(SORTING, id);\r
339             if (getColumn() != null)\r
340                 m.putString(SORT_COL, getColumn());\r
341 \r
342             columns:\r
343                 for (String columnKey : ascendMap.keySet()) {\r
344                     for (IMemento col : m.getChildren(COLS)) {\r
345                         if (columnKey.equals(col.getID())) {\r
346                             col.putBoolean(COL_ASCEND, isAscending(columnKey));\r
347                             continue columns;\r
348                         }\r
349                     }\r
350                     IMemento col = m.createChild(COLS, columnKey);\r
351                     col.putBoolean(COL_ASCEND, isAscending(columnKey));\r
352                 }\r
353         }\r
354 \r
355         public void restoreState(String id, IMemento memento) {\r
356             if (id == null)\r
357                 throw new NullPointerException("null id");\r
358             if (!hasState(id, memento))\r
359                 return;\r
360             for (IMemento m : memento.getChildren(SORTING)) {\r
361                 if (!id.equals(m.getID()))\r
362                     continue;\r
363                 for (IMemento column : m.getChildren(COLS)) {\r
364                     setAscending( column.getID(), column.getBoolean(COL_ASCEND) );\r
365                 }\r
366                 setColumn( m.getString(SORT_COL) );\r
367                 break;\r
368             }\r
369         }\r
370 \r
371         public boolean hasState(String id, IMemento memento) {\r
372             for (IMemento m : memento.getChildren(SORTING))\r
373                 if (id.equals(m.getID()))\r
374                     return true;\r
375             return false;\r
376         }\r
377 \r
378         public void setAscending(String column, Boolean ascending) {\r
379             if (ascending == null)\r
380                 ascendMap.remove(column);\r
381             else\r
382                 ascendMap.put(column, ascending);\r
383         }\r
384 \r
385         public boolean isAscending(String column) {\r
386             return !Boolean.FALSE.equals(ascendMap.get(column));\r
387         }\r
388 \r
389         public void setColumn(String column) {\r
390             this.column = column;\r
391         }\r
392 \r
393         public void toggleAscending(String column) {\r
394             Boolean ascending = ascendMap.get(column);\r
395             if (ascending == null) {\r
396                 ascendMap.put(column, Boolean.TRUE);\r
397             } else {\r
398                 ascendMap.put(column, !ascending);\r
399             }\r
400         }\r
401 \r
402         public void refresh() {\r
403             updateTrigger.refresh();\r
404         }\r
405 \r
406         public String getColumn() {\r
407             return column;\r
408         }\r
409 \r
410         @Override\r
411         public ComparableContext[] create(NodeQueryManager manager, NodeContext parent, NodeContext[] children) {\r
412             // To make this query refreshable.\r
413             manager.query(parent, UpdateTrigger.KEY);\r
414 \r
415             // Don't sort if no sort column is defined\r
416             if (column == null)\r
417                 return null;\r
418 \r
419             final boolean _ascending = isAscending(column);\r
420             final Class<?> _clazz = getColumnType(column);\r
421 \r
422             ComparableContext[] result = new ComparableContext[children.length];\r
423             for (int i = 0; i < children.length; i++) {\r
424                 NodeContext child = children[i];\r
425                 Labeler labeler = manager.query(child, BuiltinKeys.SELECTED_LABELER);\r
426 \r
427                 int category = (labeler != null) ? labeler.getCategory() : 0;\r
428                 String label = (labeler != null && column != null) ? labeler.getLabels().get(column) : "";\r
429                 if (label == null)\r
430                     label = "";\r
431 \r
432                 result[i] = new ImmutableLexicalComparable(category, label, child) {\r
433                     final boolean ascending = _ascending;\r
434                     final Class<?> clazz = _clazz;\r
435 \r
436                     @SuppressWarnings("unchecked")\r
437                     @Override\r
438                     public int compareTo(ComparableContext arg0) {\r
439                         ImmutableLexicalComparable other = (ImmutableLexicalComparable) arg0;\r
440 \r
441                         int catDelta = getCategory() - other.getCategory();\r
442                         if (!ascending)\r
443                             catDelta = -catDelta;\r
444                         if (catDelta != 0)\r
445                             return catDelta;\r
446 \r
447                         String label1 = getLabel();\r
448                         String label2 = other.getLabel();\r
449 \r
450                         @SuppressWarnings("rawtypes")\r
451                         Comparator comparator = AlphanumComparator.CASE_INSENSITIVE_COMPARATOR;\r
452                         if (clazz == Double.class) {\r
453                             comparator = DoubleStringComparator.INSTANCE;\r
454                         } else if (clazz == Long.class) {\r
455                             comparator = LongStringComparator.INSTANCE;\r
456                         }\r
457 \r
458                         return ascending ? comparator.compare(label1, label2)\r
459                                 : comparator.compare(label2, label1);\r
460                     }\r
461                 };\r
462             }\r
463 \r
464             return result;\r
465         }\r
466 \r
467         @Override\r
468         public String toString() {\r
469             return "Event Table Sort";\r
470         }\r
471 \r
472     }\r
473 \r
474     private static class LongStringComparator implements Comparator<String> {\r
475 \r
476         private static final LongStringComparator INSTANCE = new LongStringComparator();\r
477 \r
478         @Override\r
479         public int compare(String s1, String s2) {\r
480             long n1 = parseLong(s1);\r
481             long n2 = parseLong(s2);\r
482             return compare(n1, n2);\r
483         }\r
484 \r
485         private long parseLong(String s) {\r
486             try {\r
487                 return Long.parseLong(s);\r
488             } catch (NumberFormatException e1) {\r
489                 return Long.MIN_VALUE;\r
490             }\r
491         }\r
492 \r
493         public static int compare(long x, long y) {\r
494             return (x < y) ? -1 : ((x == y) ? 0 : 1);\r
495         }\r
496     }\r
497 \r
498     private static class DoubleStringComparator implements Comparator<String> {\r
499 \r
500         private static final DoubleStringComparator INSTANCE = new DoubleStringComparator();\r
501 \r
502         @Override\r
503         public int compare(String s1, String s2) {\r
504             double n1 = parseDouble(s1);\r
505             double n2 = parseDouble(s2);\r
506             return Double.compare(n1, n2);\r
507         }\r
508 \r
509         private double parseDouble(String s) {\r
510             try {\r
511                 return Double.parseDouble(s);\r
512             } catch (NumberFormatException e2) {\r
513                 return Double.NaN;\r
514             }\r
515         }\r
516     }\r
517 \r
518     public static class UpdateTrigger extends AbstractPrimitiveQueryProcessor<Double> implements ProcessorLifecycle {\r
519 \r
520         private Double                                  random   = Math.random();\r
521         private Map<NodeContext, PrimitiveQueryUpdater> contexts = new THashMap<NodeContext, PrimitiveQueryUpdater>();\r
522 \r
523         public static final PrimitiveQueryKey<Double>   KEY      = new PrimitiveQueryKey<Double>() {\r
524             @Override\r
525             public String toString() {\r
526                 return "TRIGGER";\r
527             }\r
528         };\r
529 \r
530         @Override\r
531         public Object getIdentifier() {\r
532             return KEY;\r
533         }\r
534 \r
535         public void refresh() {\r
536             random = Math.random();\r
537             for (Map.Entry<NodeContext, PrimitiveQueryUpdater> entry : contexts.entrySet()) {\r
538                 entry.getValue().scheduleReplace(entry.getKey(), KEY, random);\r
539             }\r
540         }\r
541 \r
542         @Override\r
543         public Double query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Double> key) {\r
544             this.contexts.put(context, updater);\r
545             return random;\r
546         }\r
547 \r
548         @Override\r
549         public String toString() {\r
550             return "UpdateTrigger";\r
551         }\r
552 \r
553         @Override\r
554         public void attached(GraphExplorer explorer) {\r
555         }\r
556 \r
557         @Override\r
558         public void detached(GraphExplorer explorer) {\r
559             clear();\r
560         }\r
561 \r
562         @Override\r
563         public void clear() {\r
564             contexts.clear();\r
565         }\r
566 \r
567     }\r
568 \r
569 }\r