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