Import views plugins
[deliverable/tracecompass.git] / tmf / org.lttng.scope.tmf2.views.ui / src / org / lttng / scope / tmf2 / views / ui / timeline / widgets / timegraph / toolbar / nav / NavigationModeFollowStateChanges.java
1 /*
2 * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
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.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
11
12 import static java.util.Objects.requireNonNull;
13
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;
19
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;
23
24 /**
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
27 * at a border.
28 *
29 * @author Alexandre Montplaisir
30 */
31 public class NavigationModeFollowStateChanges extends NavigationMode {
32
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$
35
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();
40
41 /**
42 * Constructor
43 */
44 public NavigationModeFollowStateChanges() {
45 super(requireNonNull(Messages.sfFollowStateChangesNavModeName),
46 BACK_ICON_PATH,
47 FWD_ICON_PATH);
48 }
49
50 @Override
51 public void navigateBackwards(TimeGraphWidget viewer) {
52 navigate(viewer, false);
53 }
54
55
56 @Override
57 public void navigateForwards(TimeGraphWidget viewer) {
58 navigate(viewer, true);
59 }
60
61 private static void navigate(TimeGraphWidget viewer, boolean forward) {
62 StateRectangle state = viewer.getSelectedState();
63 if (state == null) {
64 return;
65 }
66 long stateStartTime = state.getStateInterval().getStartEvent().getTimestamp();
67 long stateEndTime = state.getStateInterval().getEndEvent().getTimestamp();
68
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);
73
74 if (potentialStates.isEmpty()) {
75 /*
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.
78 */
79 long bound = (forward ? stateEndTime : stateStartTime);
80 NavUtils.selectNewTimestamp(viewer, bound);
81 return;
82 }
83
84 /*
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.
88 */
89 List<StateRectangle> intersectingStates = getIntersectingStates(potentialStates, targetTimestamp, forward);
90
91 StateRectangle newState;
92 if (intersectingStates.isEmpty()) {
93 /*
94 * Let's look back into 'potentialStates' (non-intersecting)
95 * and pick the interval with the closest bound.
96 */
97 Optional<StateRectangle> optState = getBestPotentialState(potentialStates, forward);
98 if (!optState.isPresent()) {
99 /* We did our best and didn't find anything. */
100 return;
101 }
102 newState = optState.get();
103
104 } else if (intersectingStates.size() == 1) {
105 /* There is only one match, must be the right one. */
106 newState = intersectingStates.get(0);
107 } else {
108 /*
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.
113 */
114 newState = intersectingStates.stream()
115 .sorted(forward ? LATEST_END_TIME_COMPARATOR : EARLIEST_START_TIME_COMPARATOR)
116 .findFirst().get();
117 }
118
119 viewer.setSelectedState(newState, true);
120 newState.showTooltip(forward);
121 NavUtils.selectNewTimestamp(viewer, targetTimestamp);
122 }
123
124 /**
125 * Compute all the potentially valid states for a navigate
126 * backwards/forwards operation.
127 *
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.
131 */
132 private static List<StateRectangle> getPotentialStates(TimeGraphWidget viewer, long targetTimestamp,
133 TimeGraphTreeElement treeElement, boolean forward) {
134
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));
138
139 if (forward) {
140 /*
141 * Keep only those intersecting, or happening after, the target
142 * timestamp.
143 */
144 potentialStates = potentialStates.filter(rect -> rect.getStateInterval().getEndEvent().getTimestamp() >= targetTimestamp);
145 } else {
146 /*
147 * Keep only those intersecting, or happening before, the target
148 * timestamp.
149 */
150 potentialStates = potentialStates.filter(rect -> rect.getStateInterval().getStartEvent().getTimestamp() <= targetTimestamp);
151 }
152
153 List<StateRectangle> allStates = potentialStates.collect(Collectors.toList());
154 return allStates;
155 }
156
157 /**
158 * From a list of potential states, generate the list of intersecting
159 * states. This means all state intervals that actually cross the target
160 * timestamp.
161 *
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.
165 */
166 private static List<StateRectangle> getIntersectingStates(List<StateRectangle> potentialStates,
167 long targetTimestamp, boolean forward) {
168
169 Stream<StateRectangle> intersectingStates = potentialStates.stream();
170 if (forward) {
171 intersectingStates = intersectingStates.filter(rect -> {
172 long start = rect.getStateInterval().getStartEvent().getTimestamp();
173 return (targetTimestamp >= start);
174 });
175 } else {
176 intersectingStates = intersectingStates.filter(rect -> {
177 long end = rect.getStateInterval().getEndEvent().getTimestamp();
178 return (targetTimestamp <= end);
179 });
180 }
181 return intersectingStates.collect(Collectors.toList());
182 }
183
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)
187 .findFirst();
188 }
189
190 }
This page took 0.033892 seconds and 5 git commands to generate.