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(() -> {
198 getParent().layout();
204 void paint(Rectangle bounds
, PaintEvent e
) {
205 drawMarkerAxis(bounds
, fTimeProvider
.getNameSpace(), e
.gc
);
209 * Draw the marker axis
212 * the bounds of the marker axis
214 * the width of the marker name area
218 protected void drawMarkerAxis(Rectangle bounds
, int nameSpace
, GC gc
) {
219 if (bounds
.isEmpty()) {
223 gc
.fillRectangle(bounds
);
226 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
+ TOP_MARGIN
, bounds
.width
, HEIGHT
);
227 for (String category
: getVisibleCategories()) {
229 rect
.width
= nameSpace
;
230 drawMarkerCategory(category
, rect
, gc
);
232 rect
.width
= bounds
.width
- nameSpace
;
233 drawMarkerLabels(category
, rect
, gc
);
238 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
, nameSpace
, bounds
.height
);
239 gc
.setClipping(rect
);
240 drawToolbar(rect
, nameSpace
, gc
);
244 * Draw the marker category
249 * the bounds of the marker name area
253 protected void drawMarkerCategory(String category
, Rectangle rect
, GC gc
) {
254 if (rect
.isEmpty()) {
257 // draw marker category
258 gc
.setForeground(gc
.getDevice().getSystemColor(SWT
.COLOR_WIDGET_FOREGROUND
));
259 gc
.setClipping(rect
);
260 int width
= gc
.textExtent(category
).x
+ TEXT_MARGIN
;
261 int x
= rect
.x
+ EXPANDED
.getBounds().width
+ HIDE
.getBounds().width
;
262 gc
.drawText(category
, Math
.max(x
, rect
.x
+ rect
.width
- width
), rect
.y
, true);
266 * Draw the marker labels for the specified category
271 * the bounds of the marker time area
275 protected void drawMarkerLabels(String category
, Rectangle rect
, GC gc
) {
276 if (rect
.isEmpty()) {
279 long time0
= fTimeProvider
.getTime0();
280 long time1
= fTimeProvider
.getTime1();
281 if (time0
== time1
) {
284 int timeSpace
= fTimeProvider
.getTimeSpace();
285 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
286 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
288 gc
.setClipping(rect
);
289 for (IMarkerEvent markerEvent
: fMarkers
.get(category
)) {
290 Color color
= getColorScheme().getColor(markerEvent
.getColor());
291 gc
.setForeground(color
);
292 int x1
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime());
293 if (x1
> rect
.x
+ rect
.width
) {
296 if (markerEvent
.getEntry() != null) {
299 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
300 String label
= getTrimmedLabel(markerEvent
);
302 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
303 if (x1
< rect
.x
&& x1
+ width
< x2
) {
304 int gap
= Math
.min(rect
.x
- x1
, MAX_GAP
);
305 x1
= Math
.min(rect
.x
+ gap
, x2
- width
);
307 int y
= rect
.y
+ rect
.height
/ 2;
308 gc
.drawLine(rect
.x
, y
, x1
, y
);
311 gc
.fillRectangle(x1
, rect
.y
, width
, rect
.height
- 1);
312 Utils
.drawText(gc
, label
, x1
+ TEXT_MARGIN
, rect
.y
, true);
313 gc
.drawRectangle(x1
, rect
.y
, width
, rect
.height
- 1);
314 if (x2
> x1
+ width
) {
315 int y
= rect
.y
+ rect
.height
/ 2;
316 gc
.drawLine(x1
+ width
, y
, x2
, y
);
319 int y
= rect
.y
+ rect
.height
/ 2;
320 gc
.drawLine(x1
, y
, x2
, y
);
329 * the bounds of the marker axis
331 * the width of the marker name area
335 protected void drawToolbar(Rectangle bounds
, int nameSpace
, GC gc
) {
336 if (bounds
.isEmpty()) {
340 gc
.drawImage(COLLAPSED
, bounds
.x
, bounds
.y
);
342 gc
.drawImage(EXPANDED
, bounds
.x
, bounds
.y
);
343 int x
= bounds
.x
+ EXPANDED
.getBounds().width
;
344 for (int i
= 0; i
< fMarkers
.keySet().size(); i
++) {
345 int y
= bounds
.y
+ TOP_MARGIN
+ i
* HEIGHT
;
346 gc
.drawImage(HIDE
, x
, y
);
351 private static String
getTrimmedLabel(IMarkerEvent marker
) {
352 String label
= marker
.getLabel();
356 return label
.substring(0, Math
.min(label
.indexOf(SWT
.LF
) != -1 ? label
.indexOf(SWT
.LF
) : label
.length(), MAX_LABEL_LENGTH
));
359 private static int getXForTime(Rectangle rect
, long time0
, double pixelsPerNanoSec
, long time
) {
360 int x
= rect
.x
+ (int) (Math
.floor((time
- time0
) * pixelsPerNanoSec
));
361 return Math
.min(Math
.max(x
, -X_LIMIT
), X_LIMIT
);
364 private IMarkerEvent
getMarkerForEvent(MouseEvent event
) {
365 long time0
= fTimeProvider
.getTime0();
366 long time1
= fTimeProvider
.getTime1();
367 if (time0
== time1
) {
370 int timeSpace
= fTimeProvider
.getTimeSpace();
371 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
372 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
374 int categoryIndex
= Math
.max((event
.y
- TOP_MARGIN
) / HEIGHT
, 0);
375 String category
= getVisibleCategories().get(categoryIndex
);
377 IMarkerEvent marker
= null;
378 GC gc
= new GC(Display
.getDefault());
379 Rectangle rect
= getBounds();
380 rect
.x
+= fTimeProvider
.getNameSpace();
381 rect
.width
-= fTimeProvider
.getNameSpace();
383 for (IMarkerEvent markerEvent
: fMarkers
.get(category
)) {
384 String label
= getTrimmedLabel(markerEvent
);
385 if (markerEvent
.getEntry() == null) {
386 int x1
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime());
389 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
390 if (event
.x
<= x1
+ width
) {
391 marker
= markerEvent
;
395 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
397 marker
= markerEvent
;
408 private String
getHiddenCategoryForEvent(MouseEvent e
, Rectangle bounds
) {
409 List
<String
> categories
= getVisibleCategories();
410 Rectangle rect
= HIDE
.getBounds();
411 rect
.x
+= bounds
.x
+ EXPANDED
.getBounds().width
+ HIDE_BORDER
;
412 rect
.y
+= bounds
.y
+ TOP_MARGIN
+ HIDE_BORDER
;
413 rect
.width
-= 2 * HIDE_BORDER
;
414 rect
.height
-= 2 * HIDE_BORDER
;
415 for (int i
= 0; i
< categories
.size(); i
++) {
416 if (rect
.contains(e
.x
, e
.y
)) {
417 return categories
.get(i
);
424 private List
<String
> getVisibleCategories() {
425 List
<String
> categories
= new ArrayList
<>(fCategories
);
426 categories
.retainAll(fMarkers
.keySet());