8a66e16d117e3b2b553b0e3432e87d583535e2c0
[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.LinkedHashSet;
18 import java.util.List;
19 import java.util.Set;
20
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.events.MouseAdapter;
24 import org.eclipse.swt.events.MouseEvent;
25 import org.eclipse.swt.events.PaintEvent;
26 import org.eclipse.swt.graphics.Color;
27 import org.eclipse.swt.graphics.GC;
28 import org.eclipse.swt.graphics.Image;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.graphics.Rectangle;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.tracecompass.internal.tmf.ui.Activator;
34 import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent;
35
36 import com.google.common.collect.LinkedHashMultimap;
37 import com.google.common.collect.Multimap;
38
39 /**
40 * A control that shows marker labels on a time axis.
41 *
42 * @since 2.0
43 */
44 public class TimeGraphMarkerAxis extends TimeGraphBaseControl {
45
46 private static final Image COLLAPSED = Activator.getDefault().getImageFromPath("icons/ovr16/collapsed_ovr.gif"); //$NON-NLS-1$
47 private static final Image EXPANDED = Activator.getDefault().getImageFromPath("icons/ovr16/expanded_ovr.gif"); //$NON-NLS-1$
48 private static final Image HIDE = Activator.getDefault().getImageFromPath("icons/etool16/hide.gif"); //$NON-NLS-1$
49 private static final int HIDE_BORDER = 4; // transparent border of the hide icon
50
51 private static final int HEIGHT;
52 static {
53 GC gc = new GC(Display.getDefault());
54 HEIGHT = gc.getFontMetrics().getHeight() + 1;
55 gc.dispose();
56 }
57
58 private static final int TOP_MARGIN = 1;
59 private static final int MAX_LABEL_LENGTH = 256;
60 private static final int TEXT_MARGIN = 2;
61 private static final int MAX_GAP = 5;
62 private static final int X_LIMIT = Integer.MAX_VALUE / 256;
63
64 private @NonNull ITimeDataProvider fTimeProvider;
65 private final Set<IMarkerAxisListener> fListeners = new LinkedHashSet<>();
66 private Multimap<String, IMarkerEvent> fMarkers = LinkedHashMultimap.create();
67 private @NonNull List<String> fCategories = Collections.EMPTY_LIST;
68 private boolean fCollapsed = false;
69
70 /**
71 * Contructor
72 *
73 * @param parent
74 * The parent composite object
75 * @param colorScheme
76 * The color scheme to use
77 * @param timeProvider
78 * The time data provider
79 */
80 public TimeGraphMarkerAxis(Composite parent, @NonNull TimeGraphColorScheme colorScheme, @NonNull ITimeDataProvider timeProvider) {
81 super(parent, colorScheme, SWT.NO_BACKGROUND | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
82 fTimeProvider = timeProvider;
83 addMouseListener(new MouseAdapter() {
84 @Override
85 public void mouseDown(MouseEvent e) {
86 Point size = getSize();
87 Rectangle bounds = new Rectangle(0, 0, size.x, size.y);
88 TimeGraphMarkerAxis.this.mouseDown(e, bounds, fTimeProvider.getNameSpace());
89 }
90 });
91 }
92
93 @Override
94 public Point computeSize(int wHint, int hHint, boolean changed) {
95 int height = 0;
96 if (!fMarkers.isEmpty() && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
97 if (fCollapsed) {
98 height = COLLAPSED.getBounds().height;
99 } else {
100 height = TOP_MARGIN + fMarkers.keySet().size() * HEIGHT;
101 }
102 }
103 return super.computeSize(wHint, height, changed);
104 }
105
106 /**
107 * Add a marker axis listener.
108 *
109 * @param listener
110 * the listener
111 */
112 public void addMarkerAxisListener(IMarkerAxisListener listener) {
113 fListeners.add(listener);
114 }
115
116 /**
117 * Remove a marker axis listener.
118 *
119 * @param listener
120 * the listener
121 */
122 public void removeMarkerAxisListener(IMarkerAxisListener listener) {
123 fListeners.remove(listener);
124 }
125
126 /**
127 * Set the time provider
128 *
129 * @param timeProvider
130 * The provider to use
131 */
132 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider) {
133 fTimeProvider = timeProvider;
134 }
135
136 /**
137 * Set the list of marker categories.
138 *
139 * @param categories
140 * The list of marker categories, or null
141 */
142 public void setMarkerCategories(List<String> categories) {
143 if (categories == null) {
144 fCategories = Collections.EMPTY_LIST;
145 } else {
146 fCategories = categories;
147 }
148 }
149
150 /**
151 * Handle a mouseDown event.
152 *
153 * @param e
154 * the mouse event
155 * @param bounds
156 * the bounds of the marker axis in the mouse event's coordinates
157 * @param nameSpace
158 * the width of the marker name area
159 */
160 public void mouseDown(MouseEvent e, Rectangle bounds, int nameSpace) {
161 if (bounds.isEmpty()) {
162 return;
163 }
164 if (fCollapsed || (e.x < bounds.x + Math.min(nameSpace, EXPANDED.getBounds().width))) {
165 fCollapsed = !fCollapsed;
166 getParent().layout();
167 redraw();
168 return;
169 }
170 if (e.x < bounds.x + nameSpace) {
171 String category = getHiddenCategoryForEvent(e, bounds);
172 if (category != null) {
173 for (IMarkerAxisListener listener : fListeners) {
174 listener.setMarkerCategoryVisible(category, false);
175 }
176 }
177 return;
178 }
179 IMarkerEvent marker = getMarkerForEvent(e);
180 if (marker != null) {
181 fTimeProvider.setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
182 }
183 }
184
185 /**
186 * Set the markers list.
187 *
188 * @param markers
189 * The markers list
190 */
191 public void setMarkers(List<IMarkerEvent> markers) {
192 Multimap<String, IMarkerEvent> map = LinkedHashMultimap.create();
193 for (IMarkerEvent marker : markers) {
194 map.put(marker.getCategory(), marker);
195 }
196 Display.getDefault().asyncExec(() -> {
197 if (isDisposed()) {
198 return;
199 }
200 fMarkers = map;
201 getParent().layout();
202 redraw();
203 });
204 }
205
206 @Override
207 void paint(Rectangle bounds, PaintEvent e) {
208 drawMarkerAxis(bounds, fTimeProvider.getNameSpace(), e.gc);
209 }
210
211 /**
212 * Draw the marker axis
213 *
214 * @param bounds
215 * the bounds of the marker axis
216 * @param nameSpace
217 * the width of the marker name area
218 * @param gc
219 * the GC instance
220 */
221 protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
222 if (bounds.isEmpty()) {
223 return;
224 }
225 // draw background
226 gc.fillRectangle(bounds);
227
228 if (!fCollapsed) {
229 Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT);
230 for (String category : getVisibleCategories()) {
231 rect.x = bounds.x;
232 rect.width = nameSpace;
233 drawMarkerCategory(category, rect, gc);
234 rect.x = nameSpace;
235 rect.width = bounds.width - nameSpace;
236 drawMarkerLabels(category, rect, gc);
237 rect.y += HEIGHT;
238 }
239 }
240
241 Rectangle rect = new Rectangle(bounds.x, bounds.y, nameSpace, bounds.height);
242 gc.setClipping(rect);
243 drawToolbar(rect, nameSpace, gc);
244 }
245
246 /**
247 * Draw the marker category
248 *
249 * @param category
250 * the category
251 * @param rect
252 * the bounds of the marker name area
253 * @param gc
254 * the GC instance
255 */
256 protected void drawMarkerCategory(String category, Rectangle rect, GC gc) {
257 if (rect.isEmpty()) {
258 return;
259 }
260 // draw marker category
261 gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
262 gc.setClipping(rect);
263 int width = gc.textExtent(category).x + TEXT_MARGIN;
264 int x = rect.x + EXPANDED.getBounds().width + HIDE.getBounds().width;
265 gc.drawText(category, Math.max(x, rect.x + rect.width - width), rect.y, true);
266 }
267
268 /**
269 * Draw the marker labels for the specified category
270 *
271 * @param category
272 * the category
273 * @param rect
274 * the bounds of the marker time area
275 * @param gc
276 * the GC instance
277 */
278 protected void drawMarkerLabels(String category, Rectangle rect, GC gc) {
279 if (rect.isEmpty()) {
280 return;
281 }
282 long time0 = fTimeProvider.getTime0();
283 long time1 = fTimeProvider.getTime1();
284 if (time0 == time1) {
285 return;
286 }
287 int timeSpace = fTimeProvider.getTimeSpace();
288 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
289 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
290
291 gc.setClipping(rect);
292 for (IMarkerEvent markerEvent : fMarkers.get(category)) {
293 Color color = getColorScheme().getColor(markerEvent.getColor());
294 gc.setForeground(color);
295 int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime());
296 if (x1 > rect.x + rect.width) {
297 return;
298 }
299 if (markerEvent.getEntry() != null) {
300 continue;
301 }
302 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
303 String label = getTrimmedLabel(markerEvent);
304 if (label != null) {
305 int width = gc.textExtent(label).x + TEXT_MARGIN;
306 if (x1 < rect.x && x1 + width < x2) {
307 int gap = Math.min(rect.x - x1, MAX_GAP);
308 x1 = Math.min(rect.x + gap, x2 - width);
309 if (x1 > rect.x) {
310 int y = rect.y + rect.height / 2;
311 gc.drawLine(rect.x, y, x1, y);
312 }
313 }
314 gc.fillRectangle(x1, rect.y, width, rect.height - 1);
315 Utils.drawText(gc, label, x1 + TEXT_MARGIN, rect.y, true);
316 gc.drawRectangle(x1, rect.y, width, rect.height - 1);
317 if (x2 > x1 + width) {
318 int y = rect.y + rect.height / 2;
319 gc.drawLine(x1 + width, y, x2, y);
320 }
321 } else {
322 int y = rect.y + rect.height / 2;
323 gc.drawLine(x1, y, x2, y);
324 }
325 }
326 }
327
328 /**
329 * Draw the toolbar
330 *
331 * @param bounds
332 * the bounds of the marker axis
333 * @param nameSpace
334 * the width of the marker name area
335 * @param gc
336 * the GC instance
337 */
338 protected void drawToolbar(Rectangle bounds, int nameSpace, GC gc) {
339 if (bounds.isEmpty()) {
340 return;
341 }
342 if (fCollapsed) {
343 gc.drawImage(COLLAPSED, bounds.x, bounds.y);
344 } else {
345 gc.drawImage(EXPANDED, bounds.x, bounds.y);
346 int x = bounds.x + EXPANDED.getBounds().width;
347 for (int i = 0; i < fMarkers.keySet().size(); i++) {
348 int y = bounds.y + TOP_MARGIN + i * HEIGHT;
349 gc.drawImage(HIDE, x, y);
350 }
351 }
352 }
353
354 private static String getTrimmedLabel(IMarkerEvent marker) {
355 String label = marker.getLabel();
356 if (label == null) {
357 return null;
358 }
359 return label.substring(0, Math.min(label.indexOf(SWT.LF) != -1 ? label.indexOf(SWT.LF) : label.length(), MAX_LABEL_LENGTH));
360 }
361
362 private static int getXForTime(Rectangle rect, long time0, double pixelsPerNanoSec, long time) {
363 int x = rect.x + (int) (Math.floor((time - time0) * pixelsPerNanoSec));
364 return Math.min(Math.max(x, -X_LIMIT), X_LIMIT);
365 }
366
367 private IMarkerEvent getMarkerForEvent(MouseEvent event) {
368 long time0 = fTimeProvider.getTime0();
369 long time1 = fTimeProvider.getTime1();
370 if (time0 == time1) {
371 return null;
372 }
373 int timeSpace = fTimeProvider.getTimeSpace();
374 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
375 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
376
377 int categoryIndex = Math.max((event.y - TOP_MARGIN) / HEIGHT, 0);
378 String category = getVisibleCategories().get(categoryIndex);
379
380 IMarkerEvent marker = null;
381 GC gc = new GC(Display.getDefault());
382 Rectangle rect = getBounds();
383 rect.x += fTimeProvider.getNameSpace();
384 rect.width -= fTimeProvider.getNameSpace();
385
386 for (IMarkerEvent markerEvent : fMarkers.get(category)) {
387 String label = getTrimmedLabel(markerEvent);
388 if (markerEvent.getEntry() == null) {
389 int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime());
390 if (x1 <= event.x) {
391 if (label != null) {
392 int width = gc.textExtent(label).x + TEXT_MARGIN;
393 if (event.x <= x1 + width) {
394 marker = markerEvent;
395 continue;
396 }
397 }
398 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
399 if (event.x <= x2) {
400 marker = markerEvent;
401 }
402 } else {
403 break;
404 }
405 }
406 }
407 gc.dispose();
408 return marker;
409 }
410
411 private String getHiddenCategoryForEvent(MouseEvent e, Rectangle bounds) {
412 List<String> categories = getVisibleCategories();
413 Rectangle rect = HIDE.getBounds();
414 rect.x += bounds.x + EXPANDED.getBounds().width + HIDE_BORDER;
415 rect.y += bounds.y + TOP_MARGIN + HIDE_BORDER;
416 rect.width -= 2 * HIDE_BORDER;
417 rect.height -= 2 * HIDE_BORDER;
418 for (int i = 0; i < categories.size(); i++) {
419 if (rect.contains(e.x, e.y)) {
420 return categories.get(i);
421 }
422 rect.y += HEIGHT;
423 }
424 return null;
425 }
426
427 private List<String> getVisibleCategories() {
428 List<String> categories = new ArrayList<>(fCategories);
429 categories.retainAll(fMarkers.keySet());
430 return categories;
431 }
432 }
This page took 0.040923 seconds and 4 git commands to generate.