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
.Collections
;
16 import java
.util
.List
;
18 import org
.eclipse
.jdt
.annotation
.NonNull
;
19 import org
.eclipse
.swt
.SWT
;
20 import org
.eclipse
.swt
.events
.MouseAdapter
;
21 import org
.eclipse
.swt
.events
.MouseEvent
;
22 import org
.eclipse
.swt
.events
.PaintEvent
;
23 import org
.eclipse
.swt
.graphics
.Color
;
24 import org
.eclipse
.swt
.graphics
.GC
;
25 import org
.eclipse
.swt
.graphics
.Point
;
26 import org
.eclipse
.swt
.graphics
.Rectangle
;
27 import org
.eclipse
.swt
.widgets
.Composite
;
28 import org
.eclipse
.swt
.widgets
.Display
;
29 import org
.eclipse
.tracecompass
.tmf
.ui
.widgets
.timegraph
.model
.IMarkerEvent
;
31 import com
.google
.common
.collect
.LinkedHashMultimap
;
32 import com
.google
.common
.collect
.Lists
;
33 import com
.google
.common
.collect
.Multimap
;
36 * A control that shows marker labels on a time axis.
40 public class TimeGraphMarkerAxis
extends TimeGraphBaseControl
{
42 private static final int HEIGHT
;
44 GC gc
= new GC(Display
.getDefault());
45 HEIGHT
= gc
.getFontMetrics().getHeight() + 1;
49 private static final int TOP_MARGIN
= 1;
50 private static final int MAX_LABEL_LENGTH
= 256;
51 private static final int TEXT_MARGIN
= 2;
52 private static final int MAX_GAP
= 5;
53 private static final int X_LIMIT
= Integer
.MAX_VALUE
/ 256;
55 private @NonNull ITimeDataProvider fTimeProvider
;
56 private Multimap
<String
, IMarkerEvent
> fMarkers
= LinkedHashMultimap
.create();
57 private List
<String
> fCategories
= Collections
.EMPTY_LIST
;
63 * The parent composite object
65 * The color scheme to use
67 * The time data provider
69 public TimeGraphMarkerAxis(Composite parent
, @NonNull TimeGraphColorScheme colorScheme
, @NonNull ITimeDataProvider timeProvider
) {
70 super(parent
, colorScheme
, SWT
.NO_BACKGROUND
| SWT
.NO_FOCUS
| SWT
.DOUBLE_BUFFERED
);
71 fTimeProvider
= timeProvider
;
72 addMouseListener(new MouseAdapter() {
74 public void mouseDown(MouseEvent e
) {
75 IMarkerEvent marker
= getMarkerForEvent(e
);
77 fTimeProvider
.setSelectionRangeNotify(marker
.getTime(), marker
.getTime() + marker
.getDuration(), false);
84 public Point
computeSize(int wHint
, int hHint
, boolean changed
) {
86 if (!fMarkers
.isEmpty() && fTimeProvider
.getTime0() != fTimeProvider
.getTime1()) {
87 height
= TOP_MARGIN
+ fMarkers
.keySet().size() * HEIGHT
;
89 return super.computeSize(wHint
, height
, changed
);
93 * Set the time provider
98 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider
) {
99 fTimeProvider
= timeProvider
;
103 * Set the markers list.
108 public void setMarkers(List
<IMarkerEvent
> markers
) {
109 Multimap
<String
, IMarkerEvent
> map
= LinkedHashMultimap
.create();
110 for (IMarkerEvent marker
: markers
) {
111 map
.put(marker
.getCategory(), marker
);
113 List
<String
> categories
= Lists
.newArrayList(map
.keySet());
114 Collections
.sort(categories
);
115 Display
.getDefault().asyncExec(() -> {
117 fCategories
= categories
;
118 getParent().layout();
124 void paint(Rectangle bounds
, PaintEvent e
) {
125 drawMarkerAxis(bounds
, fTimeProvider
.getNameSpace(), e
.gc
);
129 * Draw the marker axis
132 * the bounds of the marker axis
134 * the width of the marker name area
138 protected void drawMarkerAxis(Rectangle bounds
, int nameSpace
, GC gc
) {
140 gc
.fillRectangle(bounds
);
142 Rectangle rect
= new Rectangle(bounds
.x
, bounds
.y
+ TOP_MARGIN
, bounds
.width
, HEIGHT
);
143 for (String category
: fCategories
) {
145 rect
.width
= nameSpace
;
146 drawMarkerCategory(category
, rect
, gc
);
148 rect
.width
= bounds
.width
- nameSpace
;
149 drawMarkerLabels(category
, rect
, gc
);
155 * Draw the marker category
160 * the bounds of the marker name area
164 protected void drawMarkerCategory(String category
, Rectangle rect
, GC gc
) {
165 if (rect
.isEmpty()) {
168 // draw marker category
169 gc
.setForeground(gc
.getDevice().getSystemColor(SWT
.COLOR_WIDGET_FOREGROUND
));
170 gc
.setClipping(rect
);
171 int width
= gc
.textExtent(category
).x
+ TEXT_MARGIN
;
172 gc
.drawText(category
, Math
.max(rect
.x
, rect
.x
+ rect
.width
- width
), rect
.y
, true);
176 * Draw the marker labels for the specified category
181 * the bounds of the marker time area
185 protected void drawMarkerLabels(String category
, Rectangle rect
, GC gc
) {
186 if (rect
.isEmpty()) {
189 long time0
= fTimeProvider
.getTime0();
190 long time1
= fTimeProvider
.getTime1();
191 if (time0
== time1
) {
194 int timeSpace
= fTimeProvider
.getTimeSpace();
195 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
196 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
198 gc
.setClipping(rect
);
199 for (IMarkerEvent markerEvent
: fMarkers
.get(category
)) {
200 Color color
= getColorScheme().getColor(markerEvent
.getColor());
201 gc
.setForeground(color
);
202 int x1
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime());
203 if (x1
> rect
.x
+ rect
.width
) {
206 if (markerEvent
.getEntry() != null) {
209 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
210 String label
= getTrimmedLabel(markerEvent
);
212 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
213 if (x1
< rect
.x
&& x1
+ width
< x2
) {
214 int gap
= Math
.min(rect
.x
- x1
, MAX_GAP
);
215 x1
= Math
.min(rect
.x
+ gap
, x2
- width
);
217 int y
= rect
.y
+ rect
.height
/ 2;
218 gc
.drawLine(rect
.x
, y
, x1
, y
);
221 gc
.fillRectangle(x1
, rect
.y
, width
, rect
.height
- 1);
222 Utils
.drawText(gc
, label
, x1
+ TEXT_MARGIN
, rect
.y
, true);
223 gc
.drawRectangle(x1
, rect
.y
, width
, rect
.height
- 1);
224 if (x2
> x1
+ width
) {
225 int y
= rect
.y
+ rect
.height
/ 2;
226 gc
.drawLine(x1
+ width
, y
, x2
, y
);
229 int y
= rect
.y
+ rect
.height
/ 2;
230 gc
.drawLine(x1
, y
, x2
, y
);
235 private static String
getTrimmedLabel(IMarkerEvent marker
) {
236 String label
= marker
.getLabel();
240 return label
.substring(0, Math
.min(label
.indexOf(SWT
.LF
) != -1 ? label
.indexOf(SWT
.LF
) : label
.length(), MAX_LABEL_LENGTH
));
243 private static int getXForTime(Rectangle rect
, long time0
, double pixelsPerNanoSec
, long time
) {
244 int x
= rect
.x
+ (int) (Math
.floor((time
- time0
) * pixelsPerNanoSec
));
245 return Math
.min(Math
.max(x
, -X_LIMIT
), X_LIMIT
);
248 private IMarkerEvent
getMarkerForEvent(MouseEvent event
) {
249 long time0
= fTimeProvider
.getTime0();
250 long time1
= fTimeProvider
.getTime1();
251 if (time0
== time1
) {
254 int timeSpace
= fTimeProvider
.getTimeSpace();
255 double pixelsPerNanoSec
= (timeSpace
<= RIGHT_MARGIN
) ?
0 :
256 (double) (timeSpace
- RIGHT_MARGIN
) / (time1
- time0
);
258 int categoryIndex
= Math
.max((event
.y
- TOP_MARGIN
) / HEIGHT
, 0);
259 String category
= fCategories
.get(categoryIndex
);
261 IMarkerEvent marker
= null;
262 GC gc
= new GC(Display
.getDefault());
263 Rectangle rect
= getBounds();
264 rect
.x
+= fTimeProvider
.getNameSpace();
265 rect
.width
-= fTimeProvider
.getNameSpace();
267 for (IMarkerEvent markerEvent
: fMarkers
.get(category
)) {
268 String label
= getTrimmedLabel(markerEvent
);
269 if (markerEvent
.getEntry() == null) {
270 int x1
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime());
273 int width
= gc
.textExtent(label
).x
+ TEXT_MARGIN
;
274 if (event
.x
<= x1
+ width
) {
275 marker
= markerEvent
;
279 int x2
= getXForTime(rect
, time0
, pixelsPerNanoSec
, markerEvent
.getTime() + markerEvent
.getDuration()) - 1;
281 marker
= markerEvent
;