1 /*******************************************************************************
2 * Copyright (c) 2016 Ericsson
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
10 * Patrick Tasse - Initial API and implementation
11 *******************************************************************************/
13 package org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.widgets
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Collections
;
17 import java
.util
.LinkedHashSet
;
18 import java
.util
.List
;
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
.RGB
;
31 import org
.eclipse
.swt
.graphics
.Rectangle
;
32 import org
.eclipse
.swt
.widgets
.Composite
;
33 import org
.eclipse
.swt
.widgets
.Display
;
34 import org
.eclipse
.tracecompass
.internal
.tmf
.ui
.Activator
;
35 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.IMarkerEvent
;
37 import com
.google
.common
.collect
.LinkedHashMultimap
;
38 import com
.google
.common
.collect
.Multimap
;
41 * A control that shows marker labels on a time axis.
45 public class TimeGraphMarkerAxis
extends TimeGraphBaseControl
{
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
52 private static final int HEIGHT
;
54 GC gc
= new GC(Display
.getDefault());
55 HEIGHT
= gc
.getFontMetrics().getHeight() + 1;
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;
65 private @NonNull ITimeDataProvider fTimeProvider
;
66 private final Set
<IMarkerAxisListener
> fListeners
= new LinkedHashSet
<>();
67 private Multimap
<String
, IMarkerEvent
> fMarkers
= LinkedHashMultimap
.create();
68 private @NonNull List
<String
> fCategories
= Collections
.EMPTY_LIST
;
69 private boolean fCollapsed
= false;
75 * The parent composite object
77 * The color scheme to use
79 * The time data provider
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() {
86 public void mouseDown(MouseEvent e
) {
87 Point size
= getSize();
88 Rectangle bounds
= new Rectangle(0, 0, size
.x
, size
.y
);
89 TimeGraphMarkerAxis
.this.mouseDown(e
, bounds
, fTimeProvider
.getNameSpace());
95 public Point
computeSize(int wHint
, int hHint
, boolean changed
) {
97 if (!fMarkers
.isEmpty() && fTimeProvider
.getTime0() != fTimeProvider
.getTime1()) {
99 height
= COLLAPSED
.getBounds().height
;
101 height
= TOP_MARGIN
+ fMarkers
.keySet().size() * HEIGHT
;
104 return super.computeSize(wHint
, height
, changed
);
108 * Add a marker axis listener.
113 public void addMarkerAxisListener(IMarkerAxisListener listener
) {
114 fListeners
.add(listener
);
118 * Remove a marker axis listener.
123 public void removeMarkerAxisListener(IMarkerAxisListener listener
) {
124 fListeners
.remove(listener
);
128 * Set the time provider
130 * @param timeProvider
131 * The provider to use
133 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider
) {
134 fTimeProvider
= timeProvider
;
138 * Set the list of marker categories.
141 * The list of marker categories, or null
143 public void setMarkerCategories(List
<String
> categories
) {
144 if (categories
== null) {
145 fCategories
= Collections
.EMPTY_LIST
;
147 fCategories
= categories
;
152 * Handle a mouseDown event.
157 * the bounds of the marker axis in the mouse event's coordinates
159 * the width of the marker name area
161 public void mouseDown(MouseEvent e
, Rectangle bounds
, int nameSpace
) {
162 if (bounds
.isEmpty()) {
165 if (fCollapsed
|| (e
.x
< bounds
.x
+ Math
.min(nameSpace
, EXPANDED
.getBounds().width
))) {
166 fCollapsed
= !fCollapsed
;
167 getParent().layout();
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);
180 IMarkerEvent marker
= getMarkerForEvent(e
);
181 if (marker
!= null) {
182 fTimeProvider
.setSelectionRangeNotify(marker
.getTime(), marker
.getTime() + marker
.getDuration(), false);
187 * Set the markers list.
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
);
197 Display
.getDefault().asyncExec(() -> {
202 getParent().layout();
208 void paint(Rectangle bounds
, PaintEvent e
) {
209 drawMarkerAxis(bounds
, fTimeProvider
.getNameSpace(), e
.gc
);
213 * Draw the marker axis
216 * the bounds of the marker axis
218 * the width of the marker name area
222 protected void drawMarkerAxis(Rectangle bounds
, int nameSpace
, GC gc
) {
223 if (bounds
.isEmpty()) {
227 gc
.fillRectangle(bounds
);
230 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
+ TOP_MARGIN
, bounds
.width
, HEIGHT
);
231 for (String category
: getVisibleCategories()) {
233 rect
.width
= nameSpace
;
234 drawMarkerCategory(category
, rect
, gc
);
236 rect
.width
= bounds
.width
- nameSpace
;
237 drawMarkerLabels(category
, rect
, gc
);
242 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
, nameSpace
, bounds
.height
);
243 gc
.setClipping(rect
);
244 drawToolbar(rect
, nameSpace
, gc
);
248 * Draw the marker category
253 * the bounds of the marker name area
257 protected void drawMarkerCategory(String category
, Rectangle rect
, GC gc
) {
258 if (rect
.isEmpty()) {
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
;
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);
270 * Draw the marker labels for the specified category
275 * the bounds of the marker time area
279 protected void drawMarkerLabels(String category
, Rectangle rect
, GC gc
) {
280 if (rect
.isEmpty()) {
283 long time0
= fTimeProvider
.getTime0();
284 long time1
= fTimeProvider
.getTime1();
285 if (time0
== time1
) {
288 int timeSpace
= fTimeProvider
.getTimeSpace();
289 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
290 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
292 gc
.setClipping(rect
);
293 for (IMarkerEvent markerEvent
: fMarkers
.get(category
)) {
294 Color color
= getColorScheme().getColor(markerEvent
.getColor());
295 gc
.setForeground(color
);
296 gc
.setBackground(color
);
297 int x1
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime());
298 if (x1
> rect
.x
+ rect
.width
) {
301 if (markerEvent
.getEntry() != null) {
304 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
305 String label
= getTrimmedLabel(markerEvent
);
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
);
312 int y
= rect
.y
+ rect
.height
/ 2;
313 gc
.drawLine(rect
.x
, y
, x1
, y
);
316 gc
.fillRectangle(x1
, rect
.y
, width
, rect
.height
- 1);
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
);
322 gc
.setForeground(getDistinctForeground(color
.getRGB()));
323 Utils
.drawText(gc
, label
, x1
+ TEXT_MARGIN
, rect
.y
, true);
325 int y
= rect
.y
+ rect
.height
/ 2;
326 gc
.drawLine(x1
, y
, x2
, y
);
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
);
348 * the bounds of the marker axis
350 * the width of the marker name area
354 protected void drawToolbar(Rectangle bounds
, int nameSpace
, GC gc
) {
355 if (bounds
.isEmpty()) {
359 gc
.drawImage(COLLAPSED
, bounds
.x
, bounds
.y
);
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
);
370 private static String
getTrimmedLabel(IMarkerEvent marker
) {
371 String label
= marker
.getLabel();
375 return label
.substring(0, Math
.min(label
.indexOf(SWT
.LF
) != -1 ? label
.indexOf(SWT
.LF
) : label
.length(), MAX_LABEL_LENGTH
));
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
);
383 private IMarkerEvent
getMarkerForEvent(MouseEvent event
) {
384 long time0
= fTimeProvider
.getTime0();
385 long time1
= fTimeProvider
.getTime1();
386 if (time0
== time1
) {
389 int timeSpace
= fTimeProvider
.getTimeSpace();
390 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
391 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
393 int categoryIndex
= Math
.max((event
.y
- TOP_MARGIN
) / HEIGHT
, 0);
394 String category
= getVisibleCategories().get(categoryIndex
);
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();
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());
408 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
409 if (event
.x
<= x1
+ width
) {
410 marker
= markerEvent
;
414 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
416 marker
= markerEvent
;
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
);
443 private List
<String
> getVisibleCategories() {
444 List
<String
> categories
= new ArrayList
<>(fCategories
);
445 categories
.retainAll(fMarkers
.keySet());