2 * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
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
10 package org
.lttng
.scope
.tmf2
.views
.ui
.timeline
.widgets
.timegraph
.layer
;
12 import static java
.util
.Objects
.requireNonNull
;
14 import java
.util
.concurrent
.FutureTask
;
15 import java
.util
.function
.Predicate
;
16 import java
.util
.stream
.Stream
;
18 import org
.eclipse
.jdt
.annotation
.Nullable
;
19 import org
.lttng
.scope
.tmf2
.views
.core
.TimeRange
;
20 import org
.lttng
.scope
.tmf2
.views
.core
.timegraph
.model
.render
.tree
.TimeGraphTreeRender
;
21 import org
.lttng
.scope
.tmf2
.views
.ui
.jfx
.JfxUtils
;
22 import org
.lttng
.scope
.tmf2
.views
.ui
.timeline
.widgets
.timegraph
.TimeGraphWidget
;
23 import org
.lttng
.scope
.tmf2
.views
.ui
.timeline
.widgets
.timegraph
.VerticalPosition
;
25 import javafx
.event
.EventHandler
;
26 import javafx
.scene
.Group
;
27 import javafx
.scene
.input
.MouseButton
;
28 import javafx
.scene
.input
.MouseEvent
;
29 import javafx
.scene
.layout
.Pane
;
30 import javafx
.scene
.paint
.Color
;
31 import javafx
.scene
.shape
.Rectangle
;
32 import javafx
.scene
.shape
.StrokeLineCap
;
35 * Sub-control of the time graph widget to handle the selection layer, which
36 * displays the current and ongoing selections.
38 * It also sends the corresponding selection update whenever a new selection is
41 * @author Alexandre Montplaisir
43 public class TimeGraphSelectionLayer
extends TimeGraphLayer
{
45 /* Style settings. TODO Move to debug options? */
46 private static final double SELECTION_STROKE_WIDTH
= 1;
47 private static final Color SELECTION_STROKE_COLOR
= requireNonNull(Color
.BLUE
);
48 private static final Color SELECTION_FILL_COLOR
= requireNonNull(Color
.LIGHTBLUE
.deriveColor(0, 1.2, 1, 0.4));
51 * These events are to be ignored by the time graph pane, they should
52 * "bubble up" to the scrollpane to be used for panning.
54 private static final Predicate
<MouseEvent
> MOUSE_EVENT_IGNORED
= e
-> {
55 return (e
.getButton() == MouseButton
.SECONDARY
56 || e
.getButton() == MouseButton
.MIDDLE
57 || e
.isControlDown());
60 private final Rectangle fSelectionRect
= new Rectangle();
61 private final Rectangle fOngoingSelectionRect
= new Rectangle();
63 private final SelectionContext fSelectionCtx
= new SelectionContext();
69 * The corresponding time graph widget
71 * The group to which this layer should add its children
73 public TimeGraphSelectionLayer(TimeGraphWidget widget
, Group parentGroup
) {
74 super(widget
, parentGroup
);
76 final Pane timeGraphPane
= getWidget().getTimeGraphPane();
78 fSelectionRect
.setStroke(SELECTION_STROKE_COLOR
);
79 fSelectionRect
.setStrokeWidth(SELECTION_STROKE_WIDTH
);
80 fSelectionRect
.setStrokeLineCap(StrokeLineCap
.ROUND
);
82 Stream
.of(fSelectionRect
, fOngoingSelectionRect
).forEach(rect
-> {
83 rect
.setMouseTransparent(true);
84 rect
.setFill(SELECTION_FILL_COLOR
);
87 * We keep the 'x' property at 0, and we'll use 'layoutX' to set the
88 * start position of the rectangles.
90 * See https://github.com/lttng/lttng-scope/issues/25
92 rect
.xProperty().bind(JfxUtils
.ZERO_PROPERTY
);
93 rect
.yProperty().bind(JfxUtils
.ZERO_PROPERTY
);
94 rect
.heightProperty().bind(timeGraphPane
.heightProperty());
98 * Note, unlike most other controls, we will not add/remove children to
99 * the target group, we will add them once then toggle their 'visible'
102 fSelectionRect
.setVisible(true);
103 fOngoingSelectionRect
.setVisible(false);
104 getParentGroup().getChildren().addAll(fSelectionRect
, fOngoingSelectionRect
);
106 timeGraphPane
.addEventHandler(MouseEvent
.MOUSE_PRESSED
, fSelectionCtx
.fMousePressedEventHandler
);
107 timeGraphPane
.addEventHandler(MouseEvent
.MOUSE_DRAGGED
, fSelectionCtx
.fMouseDraggedEventHandler
);
108 timeGraphPane
.addEventHandler(MouseEvent
.MOUSE_RELEASED
, fSelectionCtx
.fMouseReleasedEventHandler
);
112 * Get the rectangle object representing the current selection range.
114 * @return The current selection rectangle
116 public Rectangle
getSelectionRectangle() {
117 return fSelectionRect
;
121 * Get the rectangle object representing the ongoing selection. It is
122 * displayed while the user holds the mouse down and drags to make a
123 * selection, but before the mouse is released. The "real" selection is only
124 * applied on mouse release.
126 * @return The ongoing selection rectangle
128 public Rectangle
getOngoingSelectionRectangle() {
129 return fOngoingSelectionRect
;
133 public void drawContents(TimeGraphTreeRender treeRender
, TimeRange timeRange
,
134 VerticalPosition vPos
, @Nullable FutureTask
<?
> task
) {
135 drawSelection(timeRange
);
139 public void clear() {
140 /* We don't have to clear anything */
144 * Draw a new "current" selection. For times where the selection is updated
145 * elsewhere in the framework.
148 * The time range of the new selection
150 public void drawSelection(TimeRange timeRange
) {
151 double xStart
= getWidget().timestampToPaneXPos(timeRange
.getStart());
152 double xEnd
= getWidget().timestampToPaneXPos(timeRange
.getEnd());
153 double xWidth
= xEnd
- xStart
;
155 fSelectionRect
.setLayoutX(xStart
);
156 fSelectionRect
.setWidth(xWidth
);
158 fSelectionRect
.setVisible(true);
162 * Class encapsulating the time range selection, related drawing and
165 private class SelectionContext
{
167 private boolean fOngoingSelection
;
168 private double fMouseOriginX
;
170 public final EventHandler
<MouseEvent
> fMousePressedEventHandler
= e
-> {
171 if (MOUSE_EVENT_IGNORED
.test(e
)) {
176 if (fOngoingSelection
) {
180 /* Remove the current selection, if there is one */
181 fSelectionRect
.setVisible(false);
183 fMouseOriginX
= e
.getX();
185 fOngoingSelectionRect
.setLayoutX(fMouseOriginX
);
186 fOngoingSelectionRect
.setWidth(0);
187 fOngoingSelectionRect
.setVisible(true);
189 fOngoingSelection
= true;
192 public final EventHandler
<MouseEvent
> fMouseDraggedEventHandler
= e
-> {
193 if (MOUSE_EVENT_IGNORED
.test(e
)) {
198 double newX
= e
.getX();
199 double offsetX
= newX
- fMouseOriginX
;
202 fOngoingSelectionRect
.setLayoutX(fMouseOriginX
);
203 fOngoingSelectionRect
.setWidth(offsetX
);
205 fOngoingSelectionRect
.setLayoutX(newX
);
206 fOngoingSelectionRect
.setWidth(-offsetX
);
211 public final EventHandler
<MouseEvent
> fMouseReleasedEventHandler
= e
-> {
212 if (MOUSE_EVENT_IGNORED
.test(e
)) {
217 fOngoingSelectionRect
.setVisible(false);
219 /* Send a time range selection signal for the currently selected time range */
220 double startX
= Math
.max(0, fOngoingSelectionRect
.getLayoutX());
221 // FIXME Possible glitch when selecting backwards outside of the window
222 double endX
= Math
.min(getWidget().getTimeGraphPane().getWidth(), startX
+ fOngoingSelectionRect
.getWidth());
223 long tsStart
= getWidget().paneXPosToTimestamp(startX
);
224 long tsEnd
= getWidget().paneXPosToTimestamp(endX
);
226 getWidget().getControl().updateTimeRangeSelection(TimeRange
.of(tsStart
, tsEnd
));
228 fOngoingSelection
= false;