[WIP] CFV Refactor
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / internal / provisional / tmf / ui / views / timegraph2 / swtjfx / SwtJfxTimeGraphViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *******************************************************************************/
9
10 package org.eclipse.tracecompass.internal.provisional.tmf.ui.views.timegraph2.swtjfx;
11
12 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import java.util.stream.DoubleStream;
20 import java.util.stream.IntStream;
21 import java.util.stream.Stream;
22
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.Status;
26 import org.eclipse.core.runtime.jobs.Job;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.custom.SashForm;
30 import org.eclipse.swt.widgets.Composite;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.tracecompass.internal.provisional.tmf.core.views.timegraph2.ITimeGraphModelRenderProvider;
33 import org.eclipse.tracecompass.internal.provisional.tmf.core.views.timegraph2.TimeGraphModelRender;
34 import org.eclipse.tracecompass.internal.provisional.tmf.core.views.timegraph2.TimeGraphStateInterval;
35 import org.eclipse.tracecompass.internal.provisional.tmf.core.views.timegraph2.TimeGraphTreeRender;
36 import org.eclipse.tracecompass.internal.provisional.tmf.ui.views.timegraph2.TimeGraphModelViewer;
37 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
38 import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
39
40 import com.google.common.annotations.VisibleForTesting;
41 import com.google.common.collect.Lists;
42
43 import javafx.beans.value.ChangeListener;
44 import javafx.embed.swt.FXCanvas;
45 import javafx.event.EventHandler;
46 import javafx.geometry.Insets;
47 import javafx.scene.Group;
48 import javafx.scene.Node;
49 import javafx.scene.Scene;
50 import javafx.scene.canvas.Canvas;
51 import javafx.scene.canvas.GraphicsContext;
52 import javafx.scene.control.Label;
53 import javafx.scene.control.ScrollPane;
54 import javafx.scene.control.ScrollPane.ScrollBarPolicy;
55 import javafx.scene.input.MouseEvent;
56 import javafx.scene.layout.Pane;
57 import javafx.scene.layout.StackPane;
58 import javafx.scene.layout.VBox;
59 import javafx.scene.paint.Color;
60 import javafx.scene.shape.Rectangle;
61 import javafx.scene.shape.StrokeLineCap;
62
63 /**
64 * Viewer for the {@link SwtJfxTimeGraphView}, encapsulating all the view's
65 * controls.
66 *
67 * Its contents consist of:
68 *
69 * TODO update this to its final form
70 * <pre>
71 * SashForm fBaseControl (parent is passed from the view)
72 * + FXCanvas
73 * | + ScrollPane
74 * | + TreeView (?), contains the list of threads
75 * + FXCanvas
76 * + ScrollPane, will contain the time graph area
77 * + Pane, gets resized to very large horizontal size to represent the whole trace range
78 * + Canvas, canvas children are tiled on the Pane to show the content of one Render each
79 * + Canvas
80 * + ...
81 * </pre>
82 *
83 * Both ScrolledPanes's vertical scrollbars are bound together, so that they
84 * scroll together.
85 *
86 * @author Alexandre Montplaisir
87 */
88 public class SwtJfxTimeGraphViewer extends TimeGraphModelViewer {
89
90 private static final double MAX_CANVAS_WIDTH = 2000.0;
91 private static final double MAX_CANVAS_HEIGHT = 2000.0;
92
93 // ------------------------------------------------------------------------
94 // Style definitions
95 // (Could eventually be moved to separate .css file?)
96 // ------------------------------------------------------------------------
97
98 private static final Color BACKGROUD_LINES_COLOR = checkNotNull(Color.LIGHTBLUE);
99 private static final String BACKGROUND_STYLE = "-fx-background-color: rgba(255, 255, 255, 255);"; //$NON-NLS-1$
100
101 private static final double SELECTION_STROKE_WIDTH = 1;
102 private static final Color SELECTION_STROKE_COLOR = checkNotNull(Color.BLUE);
103 private static final Color SELECTION_FILL_COLOR = checkNotNull(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.4));
104
105 private static final int LABEL_SIDE_MARGIN = 10;
106
107 // ------------------------------------------------------------------------
108 // Class fields
109 // ------------------------------------------------------------------------
110
111 private final SelectionContext fSelectionCtx = new SelectionContext();
112 private final ScrollingContext fScrollingCtx = new ScrollingContext();
113
114 private final LatestJobExecutor fJobExecutor = new LatestJobExecutor();
115
116 private final SashForm fBaseControl;
117
118 private final FXCanvas fTreeFXCanvas;
119 private final FXCanvas fTimeGraphFXCanvas;
120
121 private final Pane fTreePane;
122 private final ScrollPane fTreeScrollPane;
123 private final Pane fTimeGraphPane;
124 private final ScrollPane fTimeGraphScrollPane;
125
126 /*
127 * Children of the time graph pane are split into groups, so we can easily
128 * redraw or add only some of them.
129 */
130 private final Group fTimeGraphStatesLayer;
131 private final Group fTimeGraphSelectionLayer;
132 // TODO Layers for markers, arrows
133
134 private final Rectangle fSelectionRect;
135 private final Rectangle fOngoingSelectionRect;
136
137 /**
138 * Height of individual entries (text + states), including padding.
139 *
140 * TODO Make this configurable (vertical zoom feature)
141 */
142 private static final double ENTRY_HEIGHT = 20;
143
144
145 /** Current zoom level */
146 private double fNanosPerPixel = 1.0;
147
148
149 /**
150 * Constructor
151 *
152 * @param parent
153 * Parent SWT composite
154 */
155 public SwtJfxTimeGraphViewer(Composite parent, ITimeGraphModelRenderProvider provider) {
156 super(provider);
157
158 // TODO Convert this sash to JavaFX too?
159 fBaseControl = new SashForm(parent, SWT.NONE);
160
161 fTreeFXCanvas = new FXCanvas(fBaseControl, SWT.NONE);
162 fTimeGraphFXCanvas = new FXCanvas(fBaseControl, SWT.NONE);
163
164 // TODO Base on time-alignment
165 fBaseControl.setWeights(new int[] { 15, 85 });
166
167 // --------------------------------------------------------------------
168 // Prepare the tree part's scene graph
169 // --------------------------------------------------------------------
170
171 fTreePane = new Pane();
172
173 fTreeScrollPane = new ScrollPane(fTreePane);
174 /* We only show the time graph's vertical scrollbar */
175 fTreeScrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
176 fTreeScrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
177
178 // --------------------------------------------------------------------
179 // Prepare the time graph's part scene graph
180 // --------------------------------------------------------------------
181
182 fSelectionRect = new Rectangle();
183 fOngoingSelectionRect = new Rectangle();
184
185 Stream.of(fSelectionRect, fOngoingSelectionRect).forEach(rect -> {
186 rect.setStroke(SELECTION_STROKE_COLOR);
187 rect.setStrokeWidth(SELECTION_STROKE_WIDTH);
188 rect.setStrokeLineCap(StrokeLineCap.ROUND);
189 rect.setFill(SELECTION_FILL_COLOR);
190 });
191
192 fTimeGraphStatesLayer = new Group();
193 fTimeGraphSelectionLayer = new Group(fSelectionRect, fOngoingSelectionRect);
194
195 fTimeGraphPane = new Pane(fTimeGraphStatesLayer, fTimeGraphSelectionLayer);
196 fTimeGraphPane.setStyle(BACKGROUND_STYLE);
197 fTimeGraphPane.addEventHandler(MouseEvent.MOUSE_PRESSED, fSelectionCtx.fMousePressedEventHandler);
198 fTimeGraphPane.addEventHandler(MouseEvent.MOUSE_DRAGGED, fSelectionCtx.fMouseDraggedEventHandler);
199 fTimeGraphPane.addEventHandler(MouseEvent.MOUSE_RELEASED, fSelectionCtx.fMouseReleasedEventHandler);
200
201 /*
202 * We control the width of the time graph pane programatically, so
203 * ensure that calls to setPrefWidth set the actual width right away.
204 */
205 fTimeGraphPane.minWidthProperty().bind(fTimeGraphPane.prefWidthProperty());
206 fTimeGraphPane.maxWidthProperty().bind(fTimeGraphPane.prefWidthProperty());
207
208 /*
209 * Ensure the time graph pane is always exactly the same vertical size
210 * as the tree pane, so they remain aligned.
211 */
212 fTimeGraphPane.minHeightProperty().bind(fTreePane.heightProperty());
213 fTimeGraphPane.prefHeightProperty().bind(fTreePane.heightProperty());
214 fTimeGraphPane.maxHeightProperty().bind(fTreePane.heightProperty());
215
216 fTimeGraphScrollPane = new ScrollPane(fTimeGraphPane);
217 fTimeGraphScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
218 fTimeGraphScrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
219
220 // fTimeGraphScrollPane.viewportBoundsProperty().addListener(fScrollingCtx.fHScrollChangeListener);
221 fTimeGraphScrollPane.setOnMouseEntered(fScrollingCtx.fMouseEnteredEventHandler);
222 fTimeGraphScrollPane.setOnMouseExited(fScrollingCtx.fMouseExitedEventHandler);
223 fTimeGraphScrollPane.hvalueProperty().addListener(fScrollingCtx.fHScrollChangeListener);
224
225 /* Synchronize the two scrollpanes' vertical scroll bars together */
226 fTreeScrollPane.vvalueProperty().bindBidirectional(fTimeGraphScrollPane.vvalueProperty());
227
228 // --------------------------------------------------------------------
229 // Hook the parts into the SWT window
230 // --------------------------------------------------------------------
231
232 fTreeFXCanvas.setScene(new Scene(fTreeScrollPane));
233 fTimeGraphFXCanvas.setScene(new Scene(fTimeGraphScrollPane));
234
235 /*
236 * Initially populate the viewer with the context of the current trace.
237 */
238 ITmfTrace trace = TmfTraceManager.getInstance().getActiveTrace();
239 getSignalingContext().initializeForTrace(trace);
240 }
241
242 // ------------------------------------------------------------------------
243 // Test accessors
244 // ------------------------------------------------------------------------
245
246 @VisibleForTesting
247 protected Pane getTimeGraphPane() {
248 return fTimeGraphPane;
249 }
250
251 @VisibleForTesting
252 protected ScrollPane getTimeGraphScrollPane() {
253 return fTimeGraphScrollPane;
254 }
255
256 // ------------------------------------------------------------------------
257 // Operations
258 // ------------------------------------------------------------------------
259
260 @Override
261 protected void seekVisibleRangeImpl(long visibleWindowStartTime, long visibleWindowEndTime) {
262 final long fullTimeGraphStart = getFullTimeGraphStartTime();
263 final long fullTimeGraphEnd = getFullTimeGraphEndTime();
264
265 /* Update the zoom level */
266 long windowTimeRange = visibleWindowEndTime - visibleWindowStartTime;
267 double timeGraphWidth = fTimeGraphScrollPane.getWidth();
268 fNanosPerPixel = windowTimeRange / timeGraphWidth;
269
270 double timeGraphAreaWidth = timestampToPaneXPos(fullTimeGraphEnd) - timestampToPaneXPos(fullTimeGraphStart);
271 if (timeGraphAreaWidth < 1.0) {
272 // FIXME
273 return;
274 }
275
276 double newValue;
277 if (visibleWindowStartTime == fullTimeGraphStart) {
278 newValue = fTimeGraphScrollPane.getHmin();
279 } else if (visibleWindowEndTime == fullTimeGraphEnd) {
280 newValue = fTimeGraphScrollPane.getHmax();
281 } else {
282 // FIXME Not aligned perfectly yet, see how the scrolling
283 // listener does it?
284 long targetTs = (visibleWindowStartTime + visibleWindowEndTime) / 2;
285 double xPos = timestampToPaneXPos(targetTs);
286 newValue = xPos / timeGraphAreaWidth;
287 }
288
289 fTimeGraphPane.setPrefWidth(timeGraphAreaWidth);
290 fTimeGraphScrollPane.setHvalue(newValue);
291 }
292
293 @Override
294 protected void paintAreaImpl(ITmfTrace trace, long windowStartTime, long windowEndTime) {
295 final long fullTimeGraphStart = getFullTimeGraphStartTime();
296 final long fullTimeGraphEnd = getFullTimeGraphEndTime();
297
298 /*
299 * Get the current target width of the viewer, so we know at which
300 * resolution we must do state system queries.
301 *
302 * Yes! We can query the size of visible components outside of the UI
303 * thread! Praise the JavaFX!
304 */
305 long treePaneWidth = Math.round(fTreeScrollPane.getWidth());
306
307 long windowTimeRange = windowEndTime - windowStartTime;
308
309 Job job = new Job("Time Graph Update") {
310 @Override
311 protected IStatus run(@Nullable IProgressMonitor monitor) {
312 IProgressMonitor mon = checkNotNull(monitor);
313
314 /* Apply the configuration options to the render provider */
315 ITimeGraphModelRenderProvider renderProvider = getModelRenderProvider();
316 renderProvider.setConfiguredTimeRange(windowStartTime, windowEndTime);
317
318 /*
319 * Request the needed renders and prepare the corresponding
320 * canvases. We target at most one "window width" before and
321 * after the current window, clamped by the trace's start and
322 * end.
323 */
324 final long renderingStartTime = Math.max(fullTimeGraphStart, windowStartTime - windowTimeRange);
325 final long renderingEndTime = Math.min(fullTimeGraphEnd, windowEndTime + windowTimeRange);
326 final long renderTimeRange = (long) (MAX_CANVAS_WIDTH * fNanosPerPixel);
327
328 List<TimeGraphModelRender> renders = new ArrayList<>();
329 long renderStart = renderingStartTime - renderTimeRange;
330 // TODO Find a way to streamize/parallelize this loop?
331 do {
332 renderStart += renderTimeRange;
333 long renderEnd = Math.min(renderStart + renderTimeRange, renderingEndTime);
334 // FIXME Even with /10 we sometimes get holes in the view. Subpixel rendering?
335 // Needs to be debugged/tested further.
336 long resolution = Math.max(1, Math.round(fNanosPerPixel / 10));
337
338 System.out.printf("requesting render from %,d to %,d, resolution=%d%n",
339 renderStart, renderEnd, resolution);
340
341 TimeGraphModelRender render = renderProvider.getRender(trace,
342 renderStart, renderEnd, resolution, monitor);
343 renders.add(render);
344 } while ((renderStart + renderTimeRange) < renderingEndTime);
345
346 if (mon.isCanceled()) {
347 /* Job was cancelled, no need to update the UI */
348 System.out.println("job was cancelled before it could end");
349 return Status.CANCEL_STATUS;
350 }
351
352 if (renders.isEmpty()) {
353 /* Nothing to show yet, keep the view empty */
354 return Status.OK_STATUS;
355 }
356
357 /* Prepare the time graph part */
358 Node timeGraphContents = prepareTimeGraphContents(renders);
359
360 /* Prepare the tree part */
361 Node treeContents = prepareTreeContents(renders.get(0).getTreeRender(), treePaneWidth);
362
363 if (mon.isCanceled()) {
364 /* Job was cancelled, no need to update the UI */
365 System.out.println("job was cancelled before it could end");
366 return Status.CANCEL_STATUS;
367 }
368
369 /* Update the view! */
370 Display.getDefault().syncExec( () -> {
371 fTreePane.getChildren().clear();
372 fTreePane.getChildren().add(treeContents);
373
374 fTimeGraphStatesLayer.getChildren().clear();
375 fTimeGraphStatesLayer.getChildren().add(timeGraphContents);
376 });
377
378 return Status.OK_STATUS;
379 }
380 };
381
382 fJobExecutor.schedule(job);
383 }
384
385 @Override
386 protected void drawSelectionImpl(long selectionStartTime, long selectionEndTime) {
387 double xStart = timestampToPaneXPos(selectionStartTime);
388 double xEnd = timestampToPaneXPos(selectionEndTime);
389 double xWidth = xEnd - xStart;
390
391 fSelectionRect.setX(xStart);
392 fSelectionRect.setY(0);
393 fSelectionRect.setWidth(xWidth);
394 fSelectionRect.setHeight(fTimeGraphPane.getHeight());
395
396 fSelectionRect.setVisible(true);
397 }
398
399 // ------------------------------------------------------------------------
400 // Methods related to the Tree area
401 // ------------------------------------------------------------------------
402
403 private static Node prepareTreeContents(TimeGraphTreeRender treeRender, double paneWidth) {
404 /* Prepare the tree element objects */
405 List<Label> treeElements = treeRender.getAllTreeElements().stream()
406 // TODO Put as a real tree. TreeView ?
407 .map(elem -> new Label(elem.getName()))
408 .peek(label -> {
409 label.setPrefHeight(ENTRY_HEIGHT);
410 label.setPadding(new Insets(0, LABEL_SIDE_MARGIN, 0, LABEL_SIDE_MARGIN));
411 /*
412 * Re-set the solid background for the labels, so we do not
413 * see the background lines through.
414 */
415 label.setStyle(BACKGROUND_STYLE);
416 })
417 .collect(Collectors.toList());
418
419 VBox treeElemsBox = new VBox(); // Change to TreeView eventually ?
420 treeElemsBox.getChildren().addAll(treeElements);
421
422 /* Prepare the Canvases with the horizontal alignment lines */
423 List<Canvas> canvases = new ArrayList<>();
424 int maxEntriesPerCanvas = (int) (MAX_CANVAS_HEIGHT / ENTRY_HEIGHT);
425 Lists.partition(treeElements, maxEntriesPerCanvas).forEach(subList -> {
426 int nbElements = subList.size();
427 double height = nbElements * ENTRY_HEIGHT;
428
429 Canvas canvas = new Canvas(paneWidth, height);
430 drawBackgroundLines(canvas, ENTRY_HEIGHT);
431 canvas.setCache(true);
432 canvases.add(canvas);
433 });
434 VBox canvasBox = new VBox();
435 canvasBox.getChildren().addAll(canvases);
436
437 /* Put the background Canvas and the Tree View into their containers */
438 StackPane stackPane = new StackPane(canvasBox, treeElemsBox);
439 stackPane.setStyle(BACKGROUND_STYLE);
440 return stackPane;
441 }
442
443 // ------------------------------------------------------------------------
444 // Methods related to the Time Graph area
445 // ------------------------------------------------------------------------
446
447 private Node prepareTimeGraphContents(List<TimeGraphModelRender> renders) {
448 Set<Node> canvases = renders.stream()
449 .parallel() // order doesn't matter here
450 .flatMap(render -> getCanvasesForRender(render).stream())
451 .collect(Collectors.toSet());
452
453 return new Group(canvases);
454 }
455
456 /**
457 * Get the vertically-tiled Canvas's for a single render. They will
458 * be already relocated correctly, so the collection's order does not
459 * matter.
460 *
461 * @param render
462 * The render
463 * @return The vertical set of canvases
464 */
465 private Collection<Canvas> getCanvasesForRender(TimeGraphModelRender render) {
466 List<List<TimeGraphStateInterval>> stateIntervals = render.getStateIntervals();
467 /* The canvas will be put on the Pane at this offset */
468 final double xOffset = timestampToPaneXPos(render.getStartTime());
469 final double xEnd = timestampToPaneXPos(render.getEndTime());
470 final double canvasWidth = xEnd - xOffset;
471 final int maxEntriesPerCanvas = (int) (MAX_CANVAS_HEIGHT / ENTRY_HEIGHT);
472
473 /*
474 * Split the full list of intervals into smaller partitions, and draw
475 * one Canvas per partition.
476 */
477 List<Canvas> canvases = new ArrayList<>();
478 double yOffset = 0;
479 List<List<List<TimeGraphStateInterval>>> partitionedIntervals =
480 Lists.partition(stateIntervals, maxEntriesPerCanvas);
481 for (int i = 0; i < partitionedIntervals.size(); i++) {
482 /* "states" represent the subset of intervals to draw on this Canvas */
483 List<List<TimeGraphStateInterval>> states = partitionedIntervals.get(i);
484 final double canvasHeight = ENTRY_HEIGHT * states.size();
485
486 Canvas canvas = new Canvas(canvasWidth, canvasHeight);
487 drawBackgroundLines(canvas, ENTRY_HEIGHT);
488 drawStates(states, canvas.getGraphicsContext2D(), xOffset);
489
490 // System.out.println("relocating canvas of size + (" + canvasWidth + ", " + canvasHeight + ") to " + xOffset + ", " + yOffset);
491 canvas.relocate(xOffset, yOffset);
492 canvas.setCache(true); // TODO Test?
493 canvases.add(canvas);
494
495 yOffset += canvasHeight;
496 }
497 return canvases;
498 }
499
500 private void drawStates(List<List<TimeGraphStateInterval>> stateIntervalsToDraw, GraphicsContext gc, double xOffset) {
501 IntStream.range(0, stateIntervalsToDraw.size()).forEach(index -> {
502 /*
503 * The base (top) of each full-thickness rectangle object we will
504 * draw for this entry
505 */
506 final double xBase = index * ENTRY_HEIGHT;
507
508 List<TimeGraphStateInterval> intervals = stateIntervalsToDraw.get(index);
509 for (TimeGraphStateInterval interval : intervals) {
510 try {
511 /*
512 * These coordinates are relative to the canvas itself, so
513 * we need to substract the value of the offset of the
514 * canvas relative to the Pane.
515 */
516 final double xStart = timestampToPaneXPos(interval.getStartEvent().getTimestamp()) - xOffset;
517 final double xEnd = timestampToPaneXPos(interval.getEndEvent().getTimestamp()) - xOffset;
518 final double xWidth = Math.max(1.0, xEnd - xStart);
519
520 double yStart, yHeight;
521 switch (interval.getLineThickness()) {
522 case NORMAL:
523 default:
524 yStart = xBase + 4;
525 yHeight = ENTRY_HEIGHT - 4;
526 break;
527 case SMALL:
528 yStart = xBase + 8;
529 yHeight = ENTRY_HEIGHT - 8;
530 break;
531 }
532
533 gc.setFill(JfxColorFactory.getColorFromDef(interval.getColorDefinition()));
534 gc.fillRect(xStart, yStart, xWidth, yHeight);
535
536 } catch (IllegalArgumentException iae) { // TODO Temp
537 System.out.println("out of bounds interval:" + interval.toString());
538 continue;
539 }
540
541 // TODO Paint the state's name if applicable
542 }
543 });
544
545 }
546
547 // ------------------------------------------------------------------------
548 // Mouse event listeners
549 // ------------------------------------------------------------------------
550
551 /**
552 * Class encapsulating the time range selection, related drawing and
553 * listeners.
554 */
555 private class SelectionContext {
556
557 private boolean fOngoingSelection;
558 private double fMouseOriginX;
559
560 public final EventHandler<MouseEvent> fMousePressedEventHandler = e -> {
561 if (e.isShiftDown() ||
562 e.isControlDown() ||
563 e.isSecondaryButtonDown() ||
564 e.isMiddleButtonDown()) {
565 /* Do other things! */
566 // TODO!
567 return;
568 }
569
570 if (fOngoingSelection) {
571 return;
572 }
573
574 /* Remove the current selection, if there is one */
575 fSelectionRect.setVisible(false);
576
577 fMouseOriginX = e.getX();
578
579 fOngoingSelectionRect.setX(fMouseOriginX);
580 fOngoingSelectionRect.setY(0);
581 fOngoingSelectionRect.setWidth(0);
582 fOngoingSelectionRect.setHeight(fTimeGraphPane.getHeight());
583
584 fOngoingSelectionRect.setVisible(true);
585
586 e.consume();
587
588 fOngoingSelection = true;
589 };
590
591 public final EventHandler<MouseEvent> fMouseDraggedEventHandler = e -> {
592 double newX = e.getX();
593 double offsetX = newX - fMouseOriginX;
594
595 if (offsetX > 0) {
596 fOngoingSelectionRect.setX(fMouseOriginX);
597 fOngoingSelectionRect.setWidth(offsetX);
598 } else {
599 fOngoingSelectionRect.setX(newX);
600 fOngoingSelectionRect.setWidth(-offsetX);
601 }
602
603 e.consume();
604 };
605
606 public final EventHandler<MouseEvent> fMouseReleasedEventHandler = e -> {
607 fOngoingSelectionRect.setVisible(false);
608
609 e.consume();
610
611 /* Send a time range selection signal for the currently selected time range */
612 double startX = Math.max(0, fOngoingSelectionRect.getX());
613 // FIXME Possible glitch when selecting backwards outside of the window
614 double endX = Math.min(fTimeGraphPane.getWidth(), startX + fOngoingSelectionRect.getWidth());
615 long tsStart = paneXPosToTimestamp(startX);
616 long tsEnd = paneXPosToTimestamp(endX);
617
618 getSignalingContext().sendTimeRangeSelectionUpdate(tsStart, tsEnd);
619
620 fOngoingSelection = false;
621 };
622 }
623
624 /**
625 * Class encapsulating the scrolling operations of the time graph pane.
626 *
627 * The mouse entered/exited handlers ensure only the scrollpane being
628 * interacted by the user is the one sending the synchronization signals.
629 */
630 private class ScrollingContext {
631
632 private boolean fUserActionOngoing = false;
633
634 private final EventHandler<MouseEvent> fMouseEnteredEventHandler = e -> {
635 fUserActionOngoing = true;
636 };
637
638 private final EventHandler<MouseEvent> fMouseExitedEventHandler = e -> {
639 fUserActionOngoing = false;
640 };
641
642 /**
643 * Listener for the horizontal scrollbar changes
644 */
645 private final ChangeListener<Object> fHScrollChangeListener = (observable, oldValue, newValue) -> {
646 if (!fUserActionOngoing) {
647 System.out.println("Listener triggered but inactive");
648 return;
649 }
650
651 System.out.println("Change listener triggered, oldval=" + oldValue.toString() + ", newval=" + newValue.toString());
652
653 /*
654 * Determine the X position represented by the left edge of the pane
655 */
656 double hmin = fTimeGraphScrollPane.getHmin();
657 double hmax = fTimeGraphScrollPane.getHmax();
658 double hvalue = fTimeGraphScrollPane.getHvalue();
659 double contentWidth = fTimeGraphPane.getLayoutBounds().getWidth();
660 double viewportWidth = fTimeGraphScrollPane.getViewportBounds().getWidth();
661 double hoffset = Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
662
663 /*
664 * Convert the positions of the left and right edges to timestamps,
665 * and send a window range update signal
666 */
667 long tsStart = paneXPosToTimestamp(hoffset);
668 long tsEnd = paneXPosToTimestamp(hoffset + viewportWidth);
669
670 System.out.printf("Offset: %.1f, width: %.1f %n", hoffset, viewportWidth);
671 System.out.printf("Sending visible range update: %,d to %,d%n", tsStart, tsEnd);
672
673 getSignalingContext().sendVisibleWindowRangeUpdate(tsStart, tsEnd);
674 };
675 }
676
677 // ------------------------------------------------------------------------
678 // Common utils
679 // ------------------------------------------------------------------------
680
681 private static void drawBackgroundLines(Canvas canvas, double entryHeight) {
682 double width = canvas.getWidth();
683 int nbLines = (int) (canvas.getHeight() / entryHeight);
684
685
686 GraphicsContext gc = canvas.getGraphicsContext2D();
687 gc.save();
688
689 gc.setStroke(BACKGROUD_LINES_COLOR);
690 gc.setLineWidth(1);
691 /* average+2 gives the best-looking output */
692 DoubleStream.iterate((ENTRY_HEIGHT / 2) + 2, i -> i + entryHeight).limit(nbLines).forEach(yPos -> {
693 gc.strokeLine(0, yPos, width, yPos);
694 });
695
696 gc.restore();
697 }
698
699 private double timestampToPaneXPos(long timestamp) {
700 return timestampToPaneXPos(timestamp, getFullTimeGraphStartTime(), getFullTimeGraphEndTime(), fNanosPerPixel);
701 }
702
703 @VisibleForTesting
704 public static double timestampToPaneXPos(long timestamp, long start, long end, double nanosPerPixel) {
705 if (timestamp < start) {
706 throw new IllegalArgumentException(timestamp + " is smaller than trace start time " + start); //$NON-NLS-1$
707 }
708 if (timestamp > end) {
709 throw new IllegalArgumentException(timestamp + " is greater than trace end time " + end); //$NON-NLS-1$
710 }
711
712 double traceTimeRange = end - start;
713 double timeStampRatio = (timestamp - start) / traceTimeRange;
714
715 long fullTraceWidthInPixels = (long) (traceTimeRange / nanosPerPixel);
716 double xPos = fullTraceWidthInPixels * timeStampRatio;
717 return Math.round(xPos);
718 }
719
720 private long paneXPosToTimestamp(double x) {
721 return paneXPosToTimestamp(x, fTimeGraphPane.getWidth(), getFullTimeGraphStartTime(), fNanosPerPixel);
722 }
723
724 @VisibleForTesting
725 public static long paneXPosToTimestamp(double x, double totalWidth, long startTimestamp, double nanosPerPixel) {
726 if (x < 0.0 || totalWidth < 1.0 || x > totalWidth) {
727 throw new IllegalArgumentException("Invalid position arguments: pos=" + x + ", width=" + totalWidth);
728 }
729
730 long ts = Math.round(x * nanosPerPixel);
731 return ts + startTimestamp;
732 }
733
734 }
This page took 0.081013 seconds and 5 git commands to generate.