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
.toolbar
.nav
;
12 import static java
.util
.Objects
.requireNonNull
;
14 import java
.util
.Comparator
;
15 import java
.util
.List
;
16 import java
.util
.Optional
;
17 import java
.util
.stream
.Collectors
;
18 import java
.util
.stream
.Stream
;
20 import org
.lttng
.scope
.tmf2
.views
.core
.timegraph
.model
.render
.tree
.TimeGraphTreeElement
;
21 import org
.lttng
.scope
.tmf2
.views
.ui
.timeline
.widgets
.timegraph
.StateRectangle
;
22 import org
.lttng
.scope
.tmf2
.views
.ui
.timeline
.widgets
.timegraph
.TimeGraphWidget
;
25 * Navigation mode using state changes. It goes to the end/start of the current
26 * selected state interval, or jumps to the next/previous one if we are already
29 * @author Alexandre Montplaisir
31 public class NavigationModeFollowStateChanges
extends NavigationMode
{
33 private static final String BACK_ICON_PATH
= "/icons/toolbar/nav_statechange_back.gif"; //$NON-NLS-1$
34 private static final String FWD_ICON_PATH
= "/icons/toolbar/nav_statechange_fwd.gif"; //$NON-NLS-1$
36 private static final Comparator
<StateRectangle
> EARLIEST_START_TIME_COMPARATOR
=
37 Comparator
.comparingLong(rect
-> rect
.getStateInterval().getStartEvent().getTimestamp());
38 private static final Comparator
<StateRectangle
> LATEST_END_TIME_COMPARATOR
=
39 Comparator
.<StateRectangle
> comparingLong(rect
-> rect
.getStateInterval().getEndEvent().getTimestamp()).reversed();
44 public NavigationModeFollowStateChanges() {
45 super(requireNonNull(Messages
.sfFollowStateChangesNavModeName
),
51 public void navigateBackwards(TimeGraphWidget viewer
) {
52 navigate(viewer
, false);
57 public void navigateForwards(TimeGraphWidget viewer
) {
58 navigate(viewer
, true);
61 private static void navigate(TimeGraphWidget viewer
, boolean forward
) {
62 StateRectangle state
= viewer
.getSelectedState();
66 long stateStartTime
= state
.getStateInterval().getStartEvent().getTimestamp();
67 long stateEndTime
= state
.getStateInterval().getEndEvent().getTimestamp();
69 /* Aim to go to the start/end of the next/previous interval */
70 long targetTimestamp
= (forward ? stateEndTime
+ 1 : stateStartTime
- 1);
71 TimeGraphTreeElement treeElement
= state
.getStateInterval().getStartEvent().getTreeElement();
72 List
<StateRectangle
> potentialStates
= getPotentialStates(viewer
, targetTimestamp
, treeElement
, forward
);
74 if (potentialStates
.isEmpty()) {
76 * We either reached the end of our model or an edge of the trace.
77 * Go to the end/start of the current state.
79 long bound
= (forward ? stateEndTime
: stateStartTime
);
80 NavUtils
.selectNewTimestamp(viewer
, bound
);
85 * Also compute the intervals that intersect the target timestamp.
86 * We will prefer those, but if there aren't any, we'll pick the
87 * best "potential" state.
89 List
<StateRectangle
> intersectingStates
= getIntersectingStates(potentialStates
, targetTimestamp
, forward
);
91 StateRectangle newState
;
92 if (intersectingStates
.isEmpty()) {
94 * Let's look back into 'potentialStates' (non-intersecting)
95 * and pick the interval with the closest bound.
97 Optional
<StateRectangle
> optState
= getBestPotentialState(potentialStates
, forward
);
98 if (!optState
.isPresent()) {
99 /* We did our best and didn't find anything. */
102 newState
= optState
.get();
104 } else if (intersectingStates
.size() == 1) {
105 /* There is only one match, must be the right one. */
106 newState
= intersectingStates
.get(0);
109 * There is more than one match (overlapping intervals, can
110 * happen sometimes with multi-states). Pick the one with the
111 * earliest start time (for backwards) or latest end time (for
112 * forwards), to ensure we "move out" on the next action.
114 newState
= intersectingStates
.stream()
115 .sorted(forward ? LATEST_END_TIME_COMPARATOR
: EARLIEST_START_TIME_COMPARATOR
)
119 viewer
.setSelectedState(newState
, true);
120 newState
.showTooltip(forward
);
121 NavUtils
.selectNewTimestamp(viewer
, targetTimestamp
);
125 * Compute all the potentially valid states for a navigate
126 * backwards/forwards operation.
128 * This means all the states for the given time graph tree element which
129 * happen before, or after, the time given timestamp for backwards or
130 * forwards operation respectively.
132 private static List
<StateRectangle
> getPotentialStates(TimeGraphWidget viewer
, long targetTimestamp
,
133 TimeGraphTreeElement treeElement
, boolean forward
) {
135 Stream
<StateRectangle
> potentialStates
= viewer
.getRenderedStateRectangles().stream()
136 /* Keep only the intervals of the current tree element */
137 .filter(rect
-> rect
.getStateInterval().getStartEvent().getTreeElement().equals(treeElement
));
141 * Keep only those intersecting, or happening after, the target
144 potentialStates
= potentialStates
.filter(rect
-> rect
.getStateInterval().getEndEvent().getTimestamp() >= targetTimestamp
);
147 * Keep only those intersecting, or happening before, the target
150 potentialStates
= potentialStates
.filter(rect
-> rect
.getStateInterval().getStartEvent().getTimestamp() <= targetTimestamp
);
153 List
<StateRectangle
> allStates
= potentialStates
.collect(Collectors
.toList());
158 * From a list of potential states, generate the list of intersecting
159 * states. This means all state intervals that actually cross the target
162 * Note that we've already verified one of the start/end time for back/forth
163 * navigation when generating the potential states, this method only needs
164 * to check the other bound.
166 private static List
<StateRectangle
> getIntersectingStates(List
<StateRectangle
> potentialStates
,
167 long targetTimestamp
, boolean forward
) {
169 Stream
<StateRectangle
> intersectingStates
= potentialStates
.stream();
171 intersectingStates
= intersectingStates
.filter(rect
-> {
172 long start
= rect
.getStateInterval().getStartEvent().getTimestamp();
173 return (targetTimestamp
>= start
);
176 intersectingStates
= intersectingStates
.filter(rect
-> {
177 long end
= rect
.getStateInterval().getEndEvent().getTimestamp();
178 return (targetTimestamp
<= end
);
181 return intersectingStates
.collect(Collectors
.toList());
184 private static Optional
<StateRectangle
> getBestPotentialState(List
<StateRectangle
> potentialStates
, boolean forward
) {
185 return potentialStates
.stream()
186 .sorted(forward ? EARLIEST_START_TIME_COMPARATOR
: LATEST_END_TIME_COMPARATOR
)