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