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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.event.view;
\r
14 import gnu.trove.map.hash.THashMap;
\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
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
82 * @author Tuukka Lehtonen
\r
84 public class EventView extends SimanticsView {
\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
90 private GraphExplorerComposite eventExplorer;
\r
91 private TableComparatorFactory comparator;
\r
95 protected Set<String> getBrowseContexts() {
\r
96 return Collections.singleton(BROWSE_CONTEXT);
\r
100 protected String getContextMenuId() {
\r
101 return CONTEXT_MENU_ID;
\r
105 protected void createControls(Composite body, IWorkbenchSite site, ISessionContext context, WidgetSupport support) {
\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
119 final Text headerText = new Text(body, SWT.NONE);
\r
121 final DisposableListener<String> headerTextListener = new DisposableListener<String>() {
\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
130 public void run() {
\r
131 if(headerText.isDisposed()) return;
\r
132 headerText.setText(result);
\r
133 headerText.getParent().layout();
\r
140 public void exception(Throwable t) {
\r
141 Logger.defaultLogError(t);
\r
146 headerText.addDisposeListener(new DisposeListener() {
\r
149 public void widgetDisposed(DisposeEvent e) {
\r
150 headerTextListener.dispose();
\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
159 Simantics.getSession().asyncRequest(new UniqueRead<String>() {
\r
161 public String getChildrenImpl(ReadGraph graph, Resource project, List<Resource> eventLogs) throws DatabaseException {
\r
163 int discardedEvents = 0;
\r
165 for(Resource log : eventLogs) {
\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
176 if(discardedEvents > 0)
\r
177 return discardedEvents + " events in chronological order have been automatically discarded from event log";
\r
184 public String perform(ReadGraph graph) throws DatabaseException {
\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
192 }, headerTextListener);
\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
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
206 comparator = new TableComparatorFactory(explorer);
\r
208 comparator.restoreState(EVENTS_TABLE_MEMENTO_ID, m);
\r
209 eval.addComparator(comparator, 2.0);
\r
210 data.addEvaluator(Object.class, eval);
\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
219 attachHeaderListeners(eventExplorer);
\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
227 Resource project = Simantics.getProjectResource();
\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
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
248 private void attachHeaderListeners(GraphExplorerComposite explorer) {
\r
249 final Tree tree = explorer.getExplorerControl();
\r
251 String sortColumn = null;
\r
252 if (comparator != null) {
\r
253 sortColumn = comparator.getColumn();
\r
256 for (final TreeColumn column : tree.getColumns()) {
\r
257 column.addSelectionListener(new SelectionAdapter() {
\r
259 public void widgetSelected(SelectionEvent e) {
\r
260 Column c = (Column) column.getData();
\r
261 String key = transformColumn( c.getKey() );
\r
263 comparator.setColumn(key);
\r
264 comparator.toggleAscending(key);
\r
265 comparator.refresh();
\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
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
282 private Class<?> getColumnType(String key) {
\r
283 if (Constants.COLUMN_EVENT_INDEX.equals(key))
\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
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
302 public void setFocus() {
\r
303 eventExplorer.setFocus();
\r
307 protected IPropertyPage getPropertyPage() {
\r
311 public class TableComparatorFactory implements ComparableContextFactory {
\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
318 UpdateTrigger updateTrigger = new UpdateTrigger();
\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
325 public TableComparatorFactory(GraphExplorer ge) {
\r
326 ge.setPrimitiveProcessor(updateTrigger);
\r
329 public void saveState(String id, IMemento memento) {
\r
331 throw new NullPointerException("null id");
\r
333 for (IMemento child : memento.getChildren(SORTING)) {
\r
334 if (id.equals(child.getID()))
\r
338 m = memento.createChild(SORTING, id);
\r
339 if (getColumn() != null)
\r
340 m.putString(SORT_COL, getColumn());
\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
350 IMemento col = m.createChild(COLS, columnKey);
\r
351 col.putBoolean(COL_ASCEND, isAscending(columnKey));
\r
355 public void restoreState(String id, IMemento memento) {
\r
357 throw new NullPointerException("null id");
\r
358 if (!hasState(id, memento))
\r
360 for (IMemento m : memento.getChildren(SORTING)) {
\r
361 if (!id.equals(m.getID()))
\r
363 for (IMemento column : m.getChildren(COLS)) {
\r
364 setAscending( column.getID(), column.getBoolean(COL_ASCEND) );
\r
366 setColumn( m.getString(SORT_COL) );
\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
378 public void setAscending(String column, Boolean ascending) {
\r
379 if (ascending == null)
\r
380 ascendMap.remove(column);
\r
382 ascendMap.put(column, ascending);
\r
385 public boolean isAscending(String column) {
\r
386 return !Boolean.FALSE.equals(ascendMap.get(column));
\r
389 public void setColumn(String column) {
\r
390 this.column = column;
\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
398 ascendMap.put(column, !ascending);
\r
402 public void refresh() {
\r
403 updateTrigger.refresh();
\r
406 public String getColumn() {
\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
415 // Don't sort if no sort column is defined
\r
416 if (column == null)
\r
419 final boolean _ascending = isAscending(column);
\r
420 final Class<?> _clazz = getColumnType(column);
\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
427 int category = (labeler != null) ? labeler.getCategory() : 0;
\r
428 String label = (labeler != null && column != null) ? labeler.getLabels().get(column) : "";
\r
432 result[i] = new ImmutableLexicalComparable(category, label, child) {
\r
433 final boolean ascending = _ascending;
\r
434 final Class<?> clazz = _clazz;
\r
436 @SuppressWarnings("unchecked")
\r
438 public int compareTo(ComparableContext arg0) {
\r
439 ImmutableLexicalComparable other = (ImmutableLexicalComparable) arg0;
\r
441 int catDelta = getCategory() - other.getCategory();
\r
443 catDelta = -catDelta;
\r
447 String label1 = getLabel();
\r
448 String label2 = other.getLabel();
\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
458 return ascending ? comparator.compare(label1, label2)
\r
459 : comparator.compare(label2, label1);
\r
468 public String toString() {
\r
469 return "Event Table Sort";
\r
474 private static class LongStringComparator implements Comparator<String> {
\r
476 private static final LongStringComparator INSTANCE = new LongStringComparator();
\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
485 private long parseLong(String s) {
\r
487 return Long.parseLong(s);
\r
488 } catch (NumberFormatException e1) {
\r
489 return Long.MIN_VALUE;
\r
493 public static int compare(long x, long y) {
\r
494 return (x < y) ? -1 : ((x == y) ? 0 : 1);
\r
498 private static class DoubleStringComparator implements Comparator<String> {
\r
500 private static final DoubleStringComparator INSTANCE = new DoubleStringComparator();
\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
509 private double parseDouble(String s) {
\r
511 return Double.parseDouble(s);
\r
512 } catch (NumberFormatException e2) {
\r
518 public static class UpdateTrigger extends AbstractPrimitiveQueryProcessor<Double> implements ProcessorLifecycle {
\r
520 private Double random = Math.random();
\r
521 private Map<NodeContext, PrimitiveQueryUpdater> contexts = new THashMap<NodeContext, PrimitiveQueryUpdater>();
\r
523 public static final PrimitiveQueryKey<Double> KEY = new PrimitiveQueryKey<Double>() {
\r
525 public String toString() {
\r
531 public Object getIdentifier() {
\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
543 public Double query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Double> key) {
\r
544 this.contexts.put(context, updater);
\r
549 public String toString() {
\r
550 return "UpdateTrigger";
\r
554 public void attached(GraphExplorer explorer) {
\r
558 public void detached(GraphExplorer explorer) {
\r
563 public void clear() {
\r