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
.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
;
36 import com
.google
.common
.collect
.LinkedHashMultimap
;
37 import com
.google
.common
.collect
.Multimap
;
40 * A control that shows marker labels on a time axis.
44 public class TimeGraphMarkerAxis
extends TimeGraphBaseControl
{
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
51 private static final int HEIGHT
;
53 GC gc
= new GC(Display
.getDefault());
54 HEIGHT
= gc
.getFontMetrics().getHeight() + 1;
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;
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;
74 * The parent composite object
76 * The color scheme to use
78 * The time data provider
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() {
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());
94 public Point
computeSize(int wHint
, int hHint
, boolean changed
) {
96 if (!fMarkers
.isEmpty() && fTimeProvider
.getTime0() != fTimeProvider
.getTime1()) {
98 height
= COLLAPSED
.getBounds().height
;
100 height
= TOP_MARGIN
+ fMarkers
.keySet().size() * HEIGHT
;
103 return super.computeSize(wHint
, height
, changed
);
107 * Add a marker axis listener.
112 public void addMarkerAxisListener(IMarkerAxisListener listener
) {
113 fListeners
.add(listener
);
117 * Remove a marker axis listener.
122 public void removeMarkerAxisListener(IMarkerAxisListener listener
) {
123 fListeners
.remove(listener
);
127 * Set the time provider
129 * @param timeProvider
130 * The provider to use
132 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider
) {
133 fTimeProvider
= timeProvider
;
137 * Set the list of marker categories.
140 * The list of marker categories, or null
142 public void setMarkerCategories(List
<String
> categories
) {
143 if (categories
== null) {
144 fCategories
= Collections
.EMPTY_LIST
;
146 fCategories
= categories
;
151 * Handle a mouseDown event.
156 * the bounds of the marker axis in the mouse event's coordinates
158 * the width of the marker name area
160 public void mouseDown(MouseEvent e
, Rectangle bounds
, int nameSpace
) {
161 if (bounds
.isEmpty()) {
164 if (fCollapsed
|| (e
.x
< bounds
.x
+ Math
.min(nameSpace
, EXPANDED
.getBounds().width
))) {
165 fCollapsed
= !fCollapsed
;
166 getParent().layout();
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);
179 IMarkerEvent marker
= getMarkerForEvent(e
);
180 if (marker
!= null) {
181 fTimeProvider
.setSelectionRangeNotify(marker
.getTime(), marker
.getTime() + marker
.getDuration(), false);
186 * Set the markers list.
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
);
196 Display
.getDefault().asyncExec(() -> {
201 getParent().layout();
207 void paint(Rectangle bounds
, PaintEvent e
) {
208 drawMarkerAxis(bounds
, fTimeProvider
.getNameSpace(), e
.gc
);
212 * Draw the marker axis
215 * the bounds of the marker axis
217 * the width of the marker name area
221 protected void drawMarkerAxis(Rectangle bounds
, int nameSpace
, GC gc
) {
222 if (bounds
.isEmpty()) {
226 gc
.fillRectangle(bounds
);
229 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
+ TOP_MARGIN
, bounds
.width
, HEIGHT
);
230 for (String category
: getVisibleCategories()) {
232 rect
.width
= nameSpace
;
233 drawMarkerCategory(category
, rect
, gc
);
235 rect
.width
= bounds
.width
- nameSpace
;
236 drawMarkerLabels(category
, rect
, gc
);
241 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
, nameSpace
, bounds
.height
);
242 gc
.setClipping(rect
);
243 drawToolbar(rect
, nameSpace
, gc
);
247 * Draw the marker category
252 * the bounds of the marker name area
256 protected void drawMarkerCategory(String category
, Rectangle rect
, GC gc
) {
257 if (rect
.isEmpty()) {
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);
269 * Draw the marker labels for the specified category
274 * the bounds of the marker time area
278 protected void drawMarkerLabels(String category
, Rectangle rect
, GC gc
) {
279 if (rect
.isEmpty()) {
282 long time0
= fTimeProvider
.getTime0();
283 long time1
= fTimeProvider
.getTime1();
284 if (time0
== time1
) {
287 int timeSpace
= fTimeProvider
.getTimeSpace();
288 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
289 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
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
) {
299 if (markerEvent
.getEntry() != null) {
302 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
303 String label
= getTrimmedLabel(markerEvent
);
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
);
310 int y
= rect
.y
+ rect
.height
/ 2;
311 gc
.drawLine(rect
.x
, y
, x1
, y
);
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
);
322 int y
= rect
.y
+ rect
.height
/ 2;
323 gc
.drawLine(x1
, y
, x2
, y
);
332 * the bounds of the marker axis
334 * the width of the marker name area
338 protected void drawToolbar(Rectangle bounds
, int nameSpace
, GC gc
) {
339 if (bounds
.isEmpty()) {
343 gc
.drawImage(COLLAPSED
, bounds
.x
, bounds
.y
);
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
);
354 private static String
getTrimmedLabel(IMarkerEvent marker
) {
355 String label
= marker
.getLabel();
359 return label
.substring(0, Math
.min(label
.indexOf(SWT
.LF
) != -1 ? label
.indexOf(SWT
.LF
) : label
.length(), MAX_LABEL_LENGTH
));
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
);
367 private IMarkerEvent
getMarkerForEvent(MouseEvent event
) {
368 long time0
= fTimeProvider
.getTime0();
369 long time1
= fTimeProvider
.getTime1();
370 if (time0
== time1
) {
373 int timeSpace
= fTimeProvider
.getTimeSpace();
374 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
375 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
377 int categoryIndex
= Math
.max((event
.y
- TOP_MARGIN
) / HEIGHT
, 0);
378 String category
= getVisibleCategories().get(categoryIndex
);
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();
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());
392 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
393 if (event
.x
<= x1
+ width
) {
394 marker
= markerEvent
;
398 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
400 marker
= markerEvent
;
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
);
427 private List
<String
> getVisibleCategories() {
428 List
<String
> categories
= new ArrayList
<>(fCategories
);
429 categories
.retainAll(fMarkers
.keySet());