tmf: Add time graph marker axis for marker labels
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / tmf / ui / widgets / timegraph / widgets / TimeGraphMarkerAxis.java
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
13 package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
14
15 import java.util.Collections;
16 import java.util.List;
17
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;
30
31 import com.google.common.collect.LinkedHashMultimap;
32 import com.google.common.collect.Lists;
33 import com.google.common.collect.Multimap;
34
35 /**
36 * A control that shows marker labels on a time axis.
37 *
38 * @since 2.0
39 */
40 public class TimeGraphMarkerAxis extends TimeGraphBaseControl {
41
42 private static final int HEIGHT;
43 static {
44 GC gc = new GC(Display.getDefault());
45 HEIGHT = gc.getFontMetrics().getHeight() + 1;
46 gc.dispose();
47 }
48
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;
54
55 private @NonNull ITimeDataProvider fTimeProvider;
56 private Multimap<String, IMarkerEvent> fMarkers = LinkedHashMultimap.create();
57 private List<String> fCategories = Collections.EMPTY_LIST;
58
59 /**
60 * Contructor
61 *
62 * @param parent
63 * The parent composite object
64 * @param colorScheme
65 * The color scheme to use
66 * @param timeProvider
67 * The time data provider
68 */
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() {
73 @Override
74 public void mouseDown(MouseEvent e) {
75 IMarkerEvent marker = getMarkerForEvent(e);
76 if (marker != null) {
77 fTimeProvider.setSelectionRangeNotify(marker.getTime(), marker.getTime() + marker.getDuration(), false);
78 }
79 }
80 });
81 }
82
83 @Override
84 public Point computeSize(int wHint, int hHint, boolean changed) {
85 int height = 0;
86 if (!fMarkers.isEmpty() && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
87 height = TOP_MARGIN + fMarkers.keySet().size() * HEIGHT;
88 }
89 return super.computeSize(wHint, height, changed);
90 }
91
92 /**
93 * Set the time provider
94 *
95 * @param timeProvider
96 * The provider to use
97 */
98 public void setTimeProvider(@NonNull ITimeDataProvider timeProvider) {
99 fTimeProvider = timeProvider;
100 }
101
102 /**
103 * Set the markers list.
104 *
105 * @param markers
106 * The markers list
107 */
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);
112 }
113 List<String> categories = Lists.newArrayList(map.keySet());
114 Collections.sort(categories);
115 Display.getDefault().asyncExec(() -> {
116 fMarkers = map;
117 fCategories = categories;
118 getParent().layout();
119 redraw();
120 });
121 }
122
123 @Override
124 void paint(Rectangle bounds, PaintEvent e) {
125 drawMarkerAxis(bounds, fTimeProvider.getNameSpace(), e.gc);
126 }
127
128 /**
129 * Draw the marker axis
130 *
131 * @param bounds
132 * the bounds of the marker axis
133 * @param nameSpace
134 * the width of the marker name area
135 * @param gc
136 * the GC instance
137 */
138 protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) {
139 // draw background
140 gc.fillRectangle(bounds);
141
142 Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT);
143 for (String category : fCategories) {
144 rect.x = bounds.x;
145 rect.width = nameSpace;
146 drawMarkerCategory(category, rect, gc);
147 rect.x = nameSpace;
148 rect.width = bounds.width - nameSpace;
149 drawMarkerLabels(category, rect, gc);
150 rect.y += HEIGHT;
151 }
152 }
153
154 /**
155 * Draw the marker category
156 *
157 * @param category
158 * the category
159 * @param rect
160 * the bounds of the marker name area
161 * @param gc
162 * the GC instance
163 */
164 protected void drawMarkerCategory(String category, Rectangle rect, GC gc) {
165 if (rect.isEmpty()) {
166 return;
167 }
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);
173 }
174
175 /**
176 * Draw the marker labels for the specified category
177 *
178 * @param category
179 * the category
180 * @param rect
181 * the bounds of the marker time area
182 * @param gc
183 * the GC instance
184 */
185 protected void drawMarkerLabels(String category, Rectangle rect, GC gc) {
186 if (rect.isEmpty()) {
187 return;
188 }
189 long time0 = fTimeProvider.getTime0();
190 long time1 = fTimeProvider.getTime1();
191 if (time0 == time1) {
192 return;
193 }
194 int timeSpace = fTimeProvider.getTimeSpace();
195 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
196 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
197
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) {
204 return;
205 }
206 if (markerEvent.getEntry() != null) {
207 continue;
208 }
209 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
210 String label = getTrimmedLabel(markerEvent);
211 if (label != null) {
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);
216 if (x1 > rect.x) {
217 int y = rect.y + rect.height / 2;
218 gc.drawLine(rect.x, y, x1, y);
219 }
220 }
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);
227 }
228 } else {
229 int y = rect.y + rect.height / 2;
230 gc.drawLine(x1, y, x2, y);
231 }
232 }
233 }
234
235 private static String getTrimmedLabel(IMarkerEvent marker) {
236 String label = marker.getLabel();
237 if (label == null) {
238 return null;
239 }
240 return label.substring(0, Math.min(label.indexOf(SWT.LF) != -1 ? label.indexOf(SWT.LF) : label.length(), MAX_LABEL_LENGTH));
241 }
242
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);
246 }
247
248 private IMarkerEvent getMarkerForEvent(MouseEvent event) {
249 long time0 = fTimeProvider.getTime0();
250 long time1 = fTimeProvider.getTime1();
251 if (time0 == time1) {
252 return null;
253 }
254 int timeSpace = fTimeProvider.getTimeSpace();
255 double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 :
256 (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
257
258 int categoryIndex = Math.max((event.y - TOP_MARGIN) / HEIGHT, 0);
259 String category = fCategories.get(categoryIndex);
260
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();
266
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());
271 if (x1 <= event.x) {
272 if (label != null) {
273 int width = gc.textExtent(label).x + TEXT_MARGIN;
274 if (event.x <= x1 + width) {
275 marker = markerEvent;
276 continue;
277 }
278 }
279 int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1;
280 if (event.x <= x2) {
281 marker = markerEvent;
282 }
283 } else {
284 break;
285 }
286 }
287 }
288 gc.dispose();
289 return marker;
290 }
291 }
This page took 0.050687 seconds and 6 git commands to generate.