Commit | Line | Data |
---|---|---|
735b1ca2 AM |
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 | } |