tmf: Preserve order of marker categories
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / TimeGraphMarkerAxis.java
1 /*******************************************************************************
2 * Copyright (c) 2016 Ericsson
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 * Contributors:
10 * Patrick Tasse - Initial API and implementation
11 *******************************************************************************/
12
13 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
14
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.events.MouseAdapter;
22 import org.eclipse.swt.events.MouseEvent;
23 import org.eclipse.swt.events.PaintEvent;
24 import org.eclipse.swt.graphics.Color;
25 import org.eclipse.swt.graphics.GC;
26 import org.eclipse.swt.graphics.Point;
27 import org.eclipse.swt.graphics.Rectangle;
28 import org.eclipse.swt.widgets.Composite;
29 import org.eclipse.swt.widgets.Display;
30 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
31
32 import com.google.common.collect.LinkedHashMultimap;
33 import com.google.common.collect.Multimap;
34
35 /**
36 * A control that shows marker labels on a time axis.
37 *
38 * @since 2.0
39 */
40 public class TimeGraphMarkerAxis extends TimeGraphBaseControl {
41
42 private static final int HEIGHT;
43 static {
44 GC gc = new GC(Display.getDefault());
45 HEIGHT = gc.getFontMetrics().getHeight() + 1;
46 gc.dispose();
47 }
48
49 private static final int TOP_MARGIN = 1;
50 private static final int MAX_LABEL_LENGTH = 256;
51 private static final int TEXT_MARGIN = 2;
52 private static final int MAX_GAP = 5;
53 private static final int X_LIMIT = Integer.MAX_VALUE / 256;
54
55 private @NonNull ITimeDataProvider fTimeProvider;
56 private Multimap<String, IMarkerEvent> fMarkers = LinkedHashMultimap.create();
57 private @NonNull List<String> fCategories = Collections.EMPTY_LIST;
58
59 /**
60 * Contructor
61 *
62 * @param parent
63 * The parent composite object
64 * @param colorScheme
65 * The color scheme to use
66 * @param timeProvider
67 * The time data provider
68 */
69 public TimeGraphMarkerAxis(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
70 super(parent, colorScheme, SWT.NO_BACKGROUND | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
71 fTimeProvider = timeProvider;
72 addMouseListener(new MouseAdapter() {
73 @Override
74 public void mouseDown(MouseEvent e) {
75 IMarkerEvent marker = getMarkerForEvent(e);
76 if (marker != null) {
77 fTimeProvider.setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
78 }
79 }
80 });
81 }
82
83 @Override
84 public Point computeSize(int wHint, int hHint, boolean changed) {
85 int height = 0;
86 if (!fMarkers.isEmpty() && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
87 height = TOP_MARGIN + fMarkers.keySet().size() * HEIGHT;
88 }
89 return super.computeSize(wHint, height, changed);
90 }
91
92 /**
93 * Set the time provider
94 *
95 * @param timeProvider
96 * The provider to use
97 */
98 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider) {
99 fTimeProvider = timeProvider;
100 }
101
102 /**
103 * Set the list of marker categories.
104 *
105 * @param categories
106 * The list of marker categories, or null
107 */
108 public void setMarkerCategories(List<String> categories) {
109 if (categories == null) {
110 fCategories = Collections.EMPTY_LIST;
111 } else {
112 fCategories = categories;
113 }
114 }
115
116 /**
117 * Set the markers list.
118 *
119 * @param markers
120 * The markers list
121 */
122 public void setMarkers(List<IMarkerEvent> markers) {
123 Multimap<String, IMarkerEvent> map = LinkedHashMultimap.create();
124 for (IMarkerEvent marker : markers) {
125 map.put(marker.getCategory(), marker);
126 }
127 Display.getDefault().asyncExec(() -> {
128 fMarkers = map;
129 getParent().layout();
130 redraw();
131 });
132 }
133
134 @Override
135 void paint(Rectangle bounds, PaintEvent e) {
136 drawMarkerAxis(bounds, fTimeProvider.getNameSpace(), e.gc);
137 }
138
139 /**
140 * Draw the marker axis
141 *
142 * @param bounds
143 * the bounds of the marker axis
144 * @param nameSpace
145 * the width of the marker name area
146 * @param gc
147 * the GC instance
148 */
149 protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
150 // draw background
151 gc.fillRectangle(bounds);
152
153 Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT);
154 List<String> categories = new ArrayList<>(fCategories);
155 categories.retainAll(fMarkers.keySet());
156 for (String category : categories) {
157 rect.x = bounds.x;
158 rect.width = nameSpace;
159 drawMarkerCategory(category, rect, gc);
160 rect.x = nameSpace;
161 rect.width = bounds.width - nameSpace;
162 drawMarkerLabels(category, rect, gc);
163 rect.y += HEIGHT;
164 }
165 }
166
167 /**
168 * Draw the marker category
169 *
170 * @param category
171 * the category
172 * @param rect
173 * the bounds of the marker name area
174 * @param gc
175 * the GC instance
176 */
177 protected void drawMarkerCategory(String category, Rectangle rect, GC gc) {
178 if (rect.isEmpty()) {
179 return;
180 }
181 // draw marker category
182 gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
183 gc.setClipping(rect);
184 int width = gc.textExtent(category).x + TEXT_MARGIN;
185 gc.drawText(category, Math.max(rect.x, rect.x + rect.width - width), rect.y, true);
186 }
187
188 /**
189 * Draw the marker labels for the specified category
190 *
191 * @param category
192 * the category
193 * @param rect
194 * the bounds of the marker time area
195 * @param gc
196 * the GC instance
197 */
198 protected void drawMarkerLabels(String category, Rectangle rect, GC gc) {
199 if (rect.isEmpty()) {
200 return;
201 }
202 long time0 = fTimeProvider.getTime0();
203 long time1 = fTimeProvider.getTime1();
204 if (time0 == time1) {
205 return;
206 }
207 int timeSpace = fTimeProvider.getTimeSpace();
208 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
209 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
210
211 gc.setClipping(rect);
212 for (IMarkerEvent markerEvent : fMarkers.get(category)) {
213 Color color = getColorScheme().getColor(markerEvent.getColor());
214 gc.setForeground(color);
215 int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime());
216 if (x1 > rect.x + rect.width) {
217 return;
218 }
219 if (markerEvent.getEntry() != null) {
220 continue;
221 }
222 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
223 String label = getTrimmedLabel(markerEvent);
224 if (label != null) {
225 int width = gc.textExtent(label).x + TEXT_MARGIN;
226 if (x1 < rect.x && x1 + width < x2) {
227 int gap = Math.min(rect.x - x1, MAX_GAP);
228 x1 = Math.min(rect.x + gap, x2 - width);
229 if (x1 > rect.x) {
230 int y = rect.y + rect.height / 2;
231 gc.drawLine(rect.x, y, x1, y);
232 }
233 }
234 gc.fillRectangle(x1, rect.y, width, rect.height - 1);
235 Utils.drawText(gc, label, x1 + TEXT_MARGIN, rect.y, true);
236 gc.drawRectangle(x1, rect.y, width, rect.height - 1);
237 if (x2 > x1 + width) {
238 int y = rect.y + rect.height / 2;
239 gc.drawLine(x1 + width, y, x2, y);
240 }
241 } else {
242 int y = rect.y + rect.height / 2;
243 gc.drawLine(x1, y, x2, y);
244 }
245 }
246 }
247
248 private static String getTrimmedLabel(IMarkerEvent marker) {
249 String label = marker.getLabel();
250 if (label == null) {
251 return null;
252 }
253 return label.substring(0, Math.min(label.indexOf(SWT.LF) != -1 ? label.indexOf(SWT.LF) : label.length(), MAX_LABEL_LENGTH));
254 }
255
256 private static int getXForTime(Rectangle rect, long time0, double pixelsPerNanoSec, long time) {
257 int x = rect.x + (int) (Math.floor((time - time0) * pixelsPerNanoSec));
258 return Math.min(Math.max(x, -X_LIMIT), X_LIMIT);
259 }
260
261 private IMarkerEvent getMarkerForEvent(MouseEvent event) {
262 long time0 = fTimeProvider.getTime0();
263 long time1 = fTimeProvider.getTime1();
264 if (time0 == time1) {
265 return null;
266 }
267 int timeSpace = fTimeProvider.getTimeSpace();
268 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
269 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
270
271 int categoryIndex = Math.max((event.y - TOP_MARGIN) / HEIGHT, 0);
272 List<String> categories = new ArrayList<>(fCategories);
273 categories.retainAll(fMarkers.keySet());
274 String category = categories.get(categoryIndex);
275
276 IMarkerEvent marker = null;
277 GC gc = new GC(Display.getDefault());
278 Rectangle rect = getBounds();
279 rect.x += fTimeProvider.getNameSpace();
280 rect.width -= fTimeProvider.getNameSpace();
281
282 for (IMarkerEvent markerEvent : fMarkers.get(category)) {
283 String label = getTrimmedLabel(markerEvent);
284 if (markerEvent.getEntry() == null) {
285 int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime());
286 if (x1 <= event.x) {
287 if (label != null) {
288 int width = gc.textExtent(label).x + TEXT_MARGIN;
289 if (event.x <= x1 + width) {
290 marker = markerEvent;
291 continue;
292 }
293 }
294 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
295 if (event.x <= x2) {
296 marker = markerEvent;
297 }
298 } else {
299 break;
300 }
301 }
302 }
303 gc.dispose();
304 return marker;
305 }
306 }
This page took 0.039867 seconds and 6 git commands to generate.