1 /*******************************************************************************
2 * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
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 *******************************************************************************/
10 package org
.eclipse
.tracecompass
.internal
.provisional
.tmf
.ui
.views
.timegraph2
.swtjfx
;
12 import static org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
.checkNotNull
;
14 import java
.util
.ArrayList
;
15 import java
.util
.Collection
;
16 import java
.util
.List
;
18 import java
.util
.stream
.Collectors
;
19 import java
.util
.stream
.DoubleStream
;
20 import java
.util
.stream
.IntStream
;
21 import java
.util
.stream
.Stream
;
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
;
40 import com
.google
.common
.annotations
.VisibleForTesting
;
41 import com
.google
.common
.collect
.Lists
;
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
;
64 * Viewer for the {@link SwtJfxTimeGraphView}, encapsulating all the view's
67 * Its contents consist of:
69 * TODO update this to its final form
71 * SashForm fBaseControl (parent is passed from the view)
74 * | + TreeView (?), contains the list of threads
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
83 * Both ScrolledPanes's vertical scrollbars are bound together, so that they
86 * @author Alexandre Montplaisir
88 public class SwtJfxTimeGraphViewer
extends TimeGraphModelViewer
{
90 private static final double MAX_CANVAS_WIDTH
= 2000.0;
91 private static final double MAX_CANVAS_HEIGHT
= 2000.0;
93 // ------------------------------------------------------------------------
95 // (Could eventually be moved to separate .css file?)
96 // ------------------------------------------------------------------------
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$
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));
105 private static final int LABEL_SIDE_MARGIN
= 10;
107 // ------------------------------------------------------------------------
109 // ------------------------------------------------------------------------
111 private final SelectionContext fSelectionCtx
= new SelectionContext();
112 private final ScrollingContext fScrollingCtx
= new ScrollingContext();
114 private final LatestJobExecutor fJobExecutor
= new LatestJobExecutor();
116 private final SashForm fBaseControl
;
118 private final FXCanvas fTreeFXCanvas
;
119 private final FXCanvas fTimeGraphFXCanvas
;
121 private final Pane fTreePane
;
122 private final ScrollPane fTreeScrollPane
;
123 private final Pane fTimeGraphPane
;
124 private final ScrollPane fTimeGraphScrollPane
;
127 * Children of the time graph pane are split into groups, so we can easily
128 * redraw or add only some of them.
130 private final Group fTimeGraphStatesLayer
;
131 private final Group fTimeGraphSelectionLayer
;
132 // TODO Layers for markers, arrows
134 private final Rectangle fSelectionRect
;
135 private final Rectangle fOngoingSelectionRect
;
138 * Height of individual entries (text + states), including padding.
140 * TODO Make this configurable (vertical zoom feature)
142 private static final double ENTRY_HEIGHT
= 20;
145 /** Current zoom level */
146 private double fNanosPerPixel
= 1.0;
153 * Parent SWT composite
155 public SwtJfxTimeGraphViewer(Composite parent
, ITimeGraphModelRenderProvider provider
) {
158 // TODO Convert this sash to JavaFX too?
159 fBaseControl
= new SashForm(parent
, SWT
.NONE
);
161 fTreeFXCanvas
= new FXCanvas(fBaseControl
, SWT
.NONE
);
162 fTimeGraphFXCanvas
= new FXCanvas(fBaseControl
, SWT
.NONE
);
164 // TODO Base on time-alignment
165 fBaseControl
.setWeights(new int[] { 15, 85 });
167 // --------------------------------------------------------------------
168 // Prepare the tree part's scene graph
169 // --------------------------------------------------------------------
171 fTreePane
= new Pane();
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
);
178 // --------------------------------------------------------------------
179 // Prepare the time graph's part scene graph
180 // --------------------------------------------------------------------
182 fSelectionRect
= new Rectangle();
183 fOngoingSelectionRect
= new Rectangle();
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
);
192 fTimeGraphStatesLayer
= new Group();
193 fTimeGraphSelectionLayer
= new Group(fSelectionRect
, fOngoingSelectionRect
);
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
);
202 * We control the width of the time graph pane programatically, so
203 * ensure that calls to setPrefWidth set the actual width right away.
205 fTimeGraphPane
.minWidthProperty().bind(fTimeGraphPane
.prefWidthProperty());
206 fTimeGraphPane
.maxWidthProperty().bind(fTimeGraphPane
.prefWidthProperty());
209 * Ensure the time graph pane is always exactly the same vertical size
210 * as the tree pane, so they remain aligned.
212 fTimeGraphPane
.minHeightProperty().bind(fTreePane
.heightProperty());
213 fTimeGraphPane
.prefHeightProperty().bind(fTreePane
.heightProperty());
214 fTimeGraphPane
.maxHeightProperty().bind(fTreePane
.heightProperty());
216 fTimeGraphScrollPane
= new ScrollPane(fTimeGraphPane
);
217 fTimeGraphScrollPane
.setVbarPolicy(ScrollBarPolicy
.ALWAYS
);
218 fTimeGraphScrollPane
.setHbarPolicy(ScrollBarPolicy
.ALWAYS
);
220 // fTimeGraphScrollPane.viewportBoundsProperty().addListener(fScrollingCtx.fHScrollChangeListener);
221 fTimeGraphScrollPane
.setOnMouseEntered(fScrollingCtx
.fMouseEnteredEventHandler
);
222 fTimeGraphScrollPane
.setOnMouseExited(fScrollingCtx
.fMouseExitedEventHandler
);
223 fTimeGraphScrollPane
.hvalueProperty().addListener(fScrollingCtx
.fHScrollChangeListener
);
225 /* Synchronize the two scrollpanes' vertical scroll bars together */
226 fTreeScrollPane
.vvalueProperty().bindBidirectional(fTimeGraphScrollPane
.vvalueProperty());
228 // --------------------------------------------------------------------
229 // Hook the parts into the SWT window
230 // --------------------------------------------------------------------
232 fTreeFXCanvas
.setScene(new Scene(fTreeScrollPane
));
233 fTimeGraphFXCanvas
.setScene(new Scene(fTimeGraphScrollPane
));
236 * Initially populate the viewer with the context of the current trace.
238 ITmfTrace trace
= TmfTraceManager
.getInstance().getActiveTrace();
239 getSignalingContext().initializeForTrace(trace
);
242 // ------------------------------------------------------------------------
244 // ------------------------------------------------------------------------
247 protected Pane
getTimeGraphPane() {
248 return fTimeGraphPane
;
252 protected ScrollPane
getTimeGraphScrollPane() {
253 return fTimeGraphScrollPane
;
256 // ------------------------------------------------------------------------
258 // ------------------------------------------------------------------------
261 protected void seekVisibleRangeImpl(long visibleWindowStartTime
, long visibleWindowEndTime
) {
262 final long fullTimeGraphStart
= getFullTimeGraphStartTime();
263 final long fullTimeGraphEnd
= getFullTimeGraphEndTime();
265 /* Update the zoom level */
266 long windowTimeRange
= visibleWindowEndTime
- visibleWindowStartTime
;
267 double timeGraphWidth
= fTimeGraphScrollPane
.getWidth();
268 fNanosPerPixel
= windowTimeRange
/ timeGraphWidth
;
270 double timeGraphAreaWidth
= timestampToPaneXPos(fullTimeGraphEnd
) - timestampToPaneXPos(fullTimeGraphStart
);
271 if (timeGraphAreaWidth
< 1.0) {
277 if (visibleWindowStartTime
== fullTimeGraphStart
) {
278 newValue
= fTimeGraphScrollPane
.getHmin();
279 } else if (visibleWindowEndTime
== fullTimeGraphEnd
) {
280 newValue
= fTimeGraphScrollPane
.getHmax();
282 // FIXME Not aligned perfectly yet, see how the scrolling
284 long targetTs
= (visibleWindowStartTime
+ visibleWindowEndTime
) / 2;
285 double xPos
= timestampToPaneXPos(targetTs
);
286 newValue
= xPos
/ timeGraphAreaWidth
;
289 fTimeGraphPane
.setPrefWidth(timeGraphAreaWidth
);
290 fTimeGraphScrollPane
.setHvalue(newValue
);
294 protected void paintAreaImpl(ITmfTrace trace
, long windowStartTime
, long windowEndTime
) {
295 final long fullTimeGraphStart
= getFullTimeGraphStartTime();
296 final long fullTimeGraphEnd
= getFullTimeGraphEndTime();
299 * Get the current target width of the viewer, so we know at which
300 * resolution we must do state system queries.
302 * Yes! We can query the size of visible components outside of the UI
303 * thread! Praise the JavaFX!
305 long treePaneWidth
= Math
.round(fTreeScrollPane
.getWidth());
307 long windowTimeRange
= windowEndTime
- windowStartTime
;
309 Job job
= new Job("Time Graph Update") {
311 protected IStatus
run(@Nullable IProgressMonitor monitor
) {
312 IProgressMonitor mon
= checkNotNull(monitor
);
314 /* Apply the configuration options to the render provider */
315 ITimeGraphModelRenderProvider renderProvider
= getModelRenderProvider();
316 renderProvider
.setConfiguredTimeRange(windowStartTime
, windowEndTime
);
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
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
);
328 List
<TimeGraphModelRender
> renders
= new ArrayList
<>();
329 long renderStart
= renderingStartTime
- renderTimeRange
;
330 // TODO Find a way to streamize/parallelize this loop?
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));
338 System
.out
.printf("requesting render from %,d to %,d, resolution=%d%n",
339 renderStart
, renderEnd
, resolution
);
341 TimeGraphModelRender render
= renderProvider
.getRender(trace
,
342 renderStart
, renderEnd
, resolution
, monitor
);
344 } while ((renderStart
+ renderTimeRange
) < renderingEndTime
);
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
;
352 if (renders
.isEmpty()) {
353 /* Nothing to show yet, keep the view empty */
354 return Status
.OK_STATUS
;
357 /* Prepare the time graph part */
358 Node timeGraphContents
= prepareTimeGraphContents(renders
);
360 /* Prepare the tree part */
361 Node treeContents
= prepareTreeContents(renders
.get(0).getTreeRender(), treePaneWidth
);
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
;
369 /* Update the view! */
370 Display
.getDefault().syncExec( () -> {
371 fTreePane
.getChildren().clear();
372 fTreePane
.getChildren().add(treeContents
);
374 fTimeGraphStatesLayer
.getChildren().clear();
375 fTimeGraphStatesLayer
.getChildren().add(timeGraphContents
);
378 return Status
.OK_STATUS
;
382 fJobExecutor
.schedule(job
);
386 protected void drawSelectionImpl(long selectionStartTime
, long selectionEndTime
) {
387 double xStart
= timestampToPaneXPos(selectionStartTime
);
388 double xEnd
= timestampToPaneXPos(selectionEndTime
);
389 double xWidth
= xEnd
- xStart
;
391 fSelectionRect
.setX(xStart
);
392 fSelectionRect
.setY(0);
393 fSelectionRect
.setWidth(xWidth
);
394 fSelectionRect
.setHeight(fTimeGraphPane
.getHeight());
396 fSelectionRect
.setVisible(true);
399 // ------------------------------------------------------------------------
400 // Methods related to the Tree area
401 // ------------------------------------------------------------------------
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()))
409 label
.setPrefHeight(ENTRY_HEIGHT
);
410 label
.setPadding(new Insets(0, LABEL_SIDE_MARGIN
, 0, LABEL_SIDE_MARGIN
));
412 * Re-set the solid background for the labels, so we do not
413 * see the background lines through.
415 label
.setStyle(BACKGROUND_STYLE
);
417 .collect(Collectors
.toList());
419 VBox treeElemsBox
= new VBox(); // Change to TreeView eventually ?
420 treeElemsBox
.getChildren().addAll(treeElements
);
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
;
429 Canvas canvas
= new Canvas(paneWidth
, height
);
430 drawBackgroundLines(canvas
, ENTRY_HEIGHT
);
431 canvas
.setCache(true);
432 canvases
.add(canvas
);
434 VBox canvasBox
= new VBox();
435 canvasBox
.getChildren().addAll(canvases
);
437 /* Put the background Canvas and the Tree View into their containers */
438 StackPane stackPane
= new StackPane(canvasBox
, treeElemsBox
);
439 stackPane
.setStyle(BACKGROUND_STYLE
);
443 // ------------------------------------------------------------------------
444 // Methods related to the Time Graph area
445 // ------------------------------------------------------------------------
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());
453 return new Group(canvases
);
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
463 * @return The vertical set of canvases
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
);
474 * Split the full list of intervals into smaller partitions, and draw
475 * one Canvas per partition.
477 List
<Canvas
> canvases
= new ArrayList
<>();
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();
486 Canvas canvas
= new Canvas(canvasWidth
, canvasHeight
);
487 drawBackgroundLines(canvas
, ENTRY_HEIGHT
);
488 drawStates(states
, canvas
.getGraphicsContext2D(), xOffset
);
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
);
495 yOffset
+= canvasHeight
;
500 private void drawStates(List
<List
<TimeGraphStateInterval
>> stateIntervalsToDraw
, GraphicsContext gc
, double xOffset
) {
501 IntStream
.range(0, stateIntervalsToDraw
.size()).forEach(index
-> {
503 * The base (top) of each full-thickness rectangle object we will
504 * draw for this entry
506 final double xBase
= index
* ENTRY_HEIGHT
;
508 List
<TimeGraphStateInterval
> intervals
= stateIntervalsToDraw
.get(index
);
509 for (TimeGraphStateInterval interval
: intervals
) {
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.
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
);
520 double yStart
, yHeight
;
521 switch (interval
.getLineThickness()) {
525 yHeight
= ENTRY_HEIGHT
- 4;
529 yHeight
= ENTRY_HEIGHT
- 8;
533 gc
.setFill(JfxColorFactory
.getColorFromDef(interval
.getColorDefinition()));
534 gc
.fillRect(xStart
, yStart
, xWidth
, yHeight
);
536 } catch (IllegalArgumentException iae
) { // TODO Temp
537 System
.out
.println("out of bounds interval:" + interval
.toString());
541 // TODO Paint the state's name if applicable
547 // ------------------------------------------------------------------------
548 // Mouse event listeners
549 // ------------------------------------------------------------------------
552 * Class encapsulating the time range selection, related drawing and
555 private class SelectionContext
{
557 private boolean fOngoingSelection
;
558 private double fMouseOriginX
;
560 public final EventHandler
<MouseEvent
> fMousePressedEventHandler
= e
-> {
561 if (e
.isShiftDown() ||
563 e
.isSecondaryButtonDown() ||
564 e
.isMiddleButtonDown()) {
565 /* Do other things! */
570 if (fOngoingSelection
) {
574 /* Remove the current selection, if there is one */
575 fSelectionRect
.setVisible(false);
577 fMouseOriginX
= e
.getX();
579 fOngoingSelectionRect
.setX(fMouseOriginX
);
580 fOngoingSelectionRect
.setY(0);
581 fOngoingSelectionRect
.setWidth(0);
582 fOngoingSelectionRect
.setHeight(fTimeGraphPane
.getHeight());
584 fOngoingSelectionRect
.setVisible(true);
588 fOngoingSelection
= true;
591 public final EventHandler
<MouseEvent
> fMouseDraggedEventHandler
= e
-> {
592 double newX
= e
.getX();
593 double offsetX
= newX
- fMouseOriginX
;
596 fOngoingSelectionRect
.setX(fMouseOriginX
);
597 fOngoingSelectionRect
.setWidth(offsetX
);
599 fOngoingSelectionRect
.setX(newX
);
600 fOngoingSelectionRect
.setWidth(-offsetX
);
606 public final EventHandler
<MouseEvent
> fMouseReleasedEventHandler
= e
-> {
607 fOngoingSelectionRect
.setVisible(false);
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
);
618 getSignalingContext().sendTimeRangeSelectionUpdate(tsStart
, tsEnd
);
620 fOngoingSelection
= false;
625 * Class encapsulating the scrolling operations of the time graph pane.
627 * The mouse entered/exited handlers ensure only the scrollpane being
628 * interacted by the user is the one sending the synchronization signals.
630 private class ScrollingContext
{
632 private boolean fUserActionOngoing
= false;
634 private final EventHandler
<MouseEvent
> fMouseEnteredEventHandler
= e
-> {
635 fUserActionOngoing
= true;
638 private final EventHandler
<MouseEvent
> fMouseExitedEventHandler
= e
-> {
639 fUserActionOngoing
= false;
643 * Listener for the horizontal scrollbar changes
645 private final ChangeListener
<Object
> fHScrollChangeListener
= (observable
, oldValue
, newValue
) -> {
646 if (!fUserActionOngoing
) {
647 System
.out
.println("Listener triggered but inactive");
651 System
.out
.println("Change listener triggered, oldval=" + oldValue
.toString() + ", newval=" + newValue
.toString());
654 * Determine the X position represented by the left edge of the pane
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
);
664 * Convert the positions of the left and right edges to timestamps,
665 * and send a window range update signal
667 long tsStart
= paneXPosToTimestamp(hoffset
);
668 long tsEnd
= paneXPosToTimestamp(hoffset
+ viewportWidth
);
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
);
673 getSignalingContext().sendVisibleWindowRangeUpdate(tsStart
, tsEnd
);
677 // ------------------------------------------------------------------------
679 // ------------------------------------------------------------------------
681 private static void drawBackgroundLines(Canvas canvas
, double entryHeight
) {
682 double width
= canvas
.getWidth();
683 int nbLines
= (int) (canvas
.getHeight() / entryHeight
);
686 GraphicsContext gc
= canvas
.getGraphicsContext2D();
689 gc
.setStroke(BACKGROUD_LINES_COLOR
);
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
);
699 private double timestampToPaneXPos(long timestamp
) {
700 return timestampToPaneXPos(timestamp
, getFullTimeGraphStartTime(), getFullTimeGraphEndTime(), fNanosPerPixel
);
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$
708 if (timestamp
> end
) {
709 throw new IllegalArgumentException(timestamp
+ " is greater than trace end time " + end
); //$NON-NLS-1$
712 double traceTimeRange
= end
- start
;
713 double timeStampRatio
= (timestamp
- start
) / traceTimeRange
;
715 long fullTraceWidthInPixels
= (long) (traceTimeRange
/ nanosPerPixel
);
716 double xPos
= fullTraceWidthInPixels
* timeStampRatio
;
717 return Math
.round(xPos
);
720 private long paneXPosToTimestamp(double x
) {
721 return paneXPosToTimestamp(x
, fTimeGraphPane
.getWidth(), getFullTimeGraphStartTime(), fNanosPerPixel
);
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
);
730 long ts
= Math
.round(x
* nanosPerPixel
);
731 return ts
+ startTimestamp
;