Commit | Line | Data |
---|---|---|
dc4fa715 PT |
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 | ||
a924e2ed | 15 | import java.util.ArrayList; |
dc4fa715 PT |
16 | import java.util.Collections; |
17 | import java.util.List; | |
18 | ||
19 | import org.eclipse.jdt.annotation.NonNull; | |
20 | import org.eclipse.swt.SWT; | |
21 | import org.eclipse.swt.events.MouseAdapter; | |
22 | import org.eclipse.swt.events.MouseEvent; | |
23 | import org.eclipse.swt.events.PaintEvent; | |
24 | import org.eclipse.swt.graphics.Color; | |
25 | import org.eclipse.swt.graphics.GC; | |
26 | import org.eclipse.swt.graphics.Point; | |
27 | import org.eclipse.swt.graphics.Rectangle; | |
28 | import org.eclipse.swt.widgets.Composite; | |
29 | import org.eclipse.swt.widgets.Display; | |
30 | import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent; | |
31 | ||
32 | import com.google.common.collect.LinkedHashMultimap; | |
dc4fa715 PT |
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(); | |
a924e2ed | 57 | private @NonNull List<String> fCategories = Collections.EMPTY_LIST; |
dc4fa715 PT |
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 | ||
a924e2ed PT |
102 | /** |
103 | * Set the list of marker categories. | |
104 | * | |
105 | * @param categories | |
106 | * The list of marker categories, or null | |
107 | */ | |
108 | public void setMarkerCategories(List<String> categories) { | |
109 | if (categories == null) { | |
110 | fCategories = Collections.EMPTY_LIST; | |
111 | } else { | |
112 | fCategories = categories; | |
113 | } | |
114 | } | |
115 | ||
dc4fa715 PT |
116 | /** |
117 | * Set the markers list. | |
118 | * | |
119 | * @param markers | |
120 | * The markers list | |
121 | */ | |
122 | public void setMarkers(List<IMarkerEvent> markers) { | |
123 | Multimap<String, IMarkerEvent> map = LinkedHashMultimap.create(); | |
124 | for (IMarkerEvent marker : markers) { | |
125 | map.put(marker.getCategory(), marker); | |
126 | } | |
dc4fa715 PT |
127 | Display.getDefault().asyncExec(() -> { |
128 | fMarkers = map; | |
dc4fa715 PT |
129 | getParent().layout(); |
130 | redraw(); | |
131 | }); | |
132 | } | |
133 | ||
134 | @Override | |
135 | void paint(Rectangle bounds, PaintEvent e) { | |
136 | drawMarkerAxis(bounds, fTimeProvider.getNameSpace(), e.gc); | |
137 | } | |
138 | ||
139 | /** | |
140 | * Draw the marker axis | |
141 | * | |
142 | * @param bounds | |
143 | * the bounds of the marker axis | |
144 | * @param nameSpace | |
145 | * the width of the marker name area | |
146 | * @param gc | |
147 | * the GC instance | |
148 | */ | |
149 | protected void drawMarkerAxis(Rectangle bounds, int nameSpace, GC gc) { | |
150 | // draw background | |
151 | gc.fillRectangle(bounds); | |
152 | ||
153 | Rectangle rect = new Rectangle(bounds.x, bounds.y + TOP_MARGIN, bounds.width, HEIGHT); | |
a924e2ed PT |
154 | List<String> categories = new ArrayList<>(fCategories); |
155 | categories.retainAll(fMarkers.keySet()); | |
156 | for (String category : categories) { | |
dc4fa715 PT |
157 | rect.x = bounds.x; |
158 | rect.width = nameSpace; | |
159 | drawMarkerCategory(category, rect, gc); | |
160 | rect.x = nameSpace; | |
161 | rect.width = bounds.width - nameSpace; | |
162 | drawMarkerLabels(category, rect, gc); | |
163 | rect.y += HEIGHT; | |
164 | } | |
165 | } | |
166 | ||
167 | /** | |
168 | * Draw the marker category | |
169 | * | |
170 | * @param category | |
171 | * the category | |
172 | * @param rect | |
173 | * the bounds of the marker name area | |
174 | * @param gc | |
175 | * the GC instance | |
176 | */ | |
177 | protected void drawMarkerCategory(String category, Rectangle rect, GC gc) { | |
178 | if (rect.isEmpty()) { | |
179 | return; | |
180 | } | |
181 | // draw marker category | |
182 | gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); | |
183 | gc.setClipping(rect); | |
184 | int width = gc.textExtent(category).x + TEXT_MARGIN; | |
185 | gc.drawText(category, Math.max(rect.x, rect.x + rect.width - width), rect.y, true); | |
186 | } | |
187 | ||
188 | /** | |
189 | * Draw the marker labels for the specified category | |
190 | * | |
191 | * @param category | |
192 | * the category | |
193 | * @param rect | |
194 | * the bounds of the marker time area | |
195 | * @param gc | |
196 | * the GC instance | |
197 | */ | |
198 | protected void drawMarkerLabels(String category, Rectangle rect, GC gc) { | |
199 | if (rect.isEmpty()) { | |
200 | return; | |
201 | } | |
202 | long time0 = fTimeProvider.getTime0(); | |
203 | long time1 = fTimeProvider.getTime1(); | |
204 | if (time0 == time1) { | |
205 | return; | |
206 | } | |
207 | int timeSpace = fTimeProvider.getTimeSpace(); | |
208 | double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 : | |
209 | (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0); | |
210 | ||
211 | gc.setClipping(rect); | |
212 | for (IMarkerEvent markerEvent : fMarkers.get(category)) { | |
213 | Color color = getColorScheme().getColor(markerEvent.getColor()); | |
214 | gc.setForeground(color); | |
215 | int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime()); | |
216 | if (x1 > rect.x + rect.width) { | |
217 | return; | |
218 | } | |
219 | if (markerEvent.getEntry() != null) { | |
220 | continue; | |
221 | } | |
222 | int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1; | |
223 | String label = getTrimmedLabel(markerEvent); | |
224 | if (label != null) { | |
225 | int width = gc.textExtent(label).x + TEXT_MARGIN; | |
226 | if (x1 < rect.x && x1 + width < x2) { | |
227 | int gap = Math.min(rect.x - x1, MAX_GAP); | |
228 | x1 = Math.min(rect.x + gap, x2 - width); | |
229 | if (x1 > rect.x) { | |
230 | int y = rect.y + rect.height / 2; | |
231 | gc.drawLine(rect.x, y, x1, y); | |
232 | } | |
233 | } | |
234 | gc.fillRectangle(x1, rect.y, width, rect.height - 1); | |
235 | Utils.drawText(gc, label, x1 + TEXT_MARGIN, rect.y, true); | |
236 | gc.drawRectangle(x1, rect.y, width, rect.height - 1); | |
237 | if (x2 > x1 + width) { | |
238 | int y = rect.y + rect.height / 2; | |
239 | gc.drawLine(x1 + width, y, x2, y); | |
240 | } | |
241 | } else { | |
242 | int y = rect.y + rect.height / 2; | |
243 | gc.drawLine(x1, y, x2, y); | |
244 | } | |
245 | } | |
246 | } | |
247 | ||
248 | private static String getTrimmedLabel(IMarkerEvent marker) { | |
249 | String label = marker.getLabel(); | |
250 | if (label == null) { | |
251 | return null; | |
252 | } | |
253 | return label.substring(0, Math.min(label.indexOf(SWT.LF) != -1 ? label.indexOf(SWT.LF) : label.length(), MAX_LABEL_LENGTH)); | |
254 | } | |
255 | ||
256 | private static int getXForTime(Rectangle rect, long time0, double pixelsPerNanoSec, long time) { | |
257 | int x = rect.x + (int) (Math.floor((time - time0) * pixelsPerNanoSec)); | |
258 | return Math.min(Math.max(x, -X_LIMIT), X_LIMIT); | |
259 | } | |
260 | ||
261 | private IMarkerEvent getMarkerForEvent(MouseEvent event) { | |
262 | long time0 = fTimeProvider.getTime0(); | |
263 | long time1 = fTimeProvider.getTime1(); | |
264 | if (time0 == time1) { | |
265 | return null; | |
266 | } | |
267 | int timeSpace = fTimeProvider.getTimeSpace(); | |
268 | double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 : | |
269 | (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0); | |
270 | ||
271 | int categoryIndex = Math.max((event.y - TOP_MARGIN) / HEIGHT, 0); | |
a924e2ed PT |
272 | List<String> categories = new ArrayList<>(fCategories); |
273 | categories.retainAll(fMarkers.keySet()); | |
274 | String category = categories.get(categoryIndex); | |
dc4fa715 PT |
275 | |
276 | IMarkerEvent marker = null; | |
277 | GC gc = new GC(Display.getDefault()); | |
278 | Rectangle rect = getBounds(); | |
279 | rect.x += fTimeProvider.getNameSpace(); | |
280 | rect.width -= fTimeProvider.getNameSpace(); | |
281 | ||
282 | for (IMarkerEvent markerEvent : fMarkers.get(category)) { | |
283 | String label = getTrimmedLabel(markerEvent); | |
284 | if (markerEvent.getEntry() == null) { | |
285 | int x1 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime()); | |
286 | if (x1 <= event.x) { | |
287 | if (label != null) { | |
288 | int width = gc.textExtent(label).x + TEXT_MARGIN; | |
289 | if (event.x <= x1 + width) { | |
290 | marker = markerEvent; | |
291 | continue; | |
292 | } | |
293 | } | |
294 | int x2 = getXForTime(rect, time0, pixelsPerNanoSec, markerEvent.getTime() + markerEvent.getDuration()) - 1; | |
295 | if (event.x <= x2) { | |
296 | marker = markerEvent; | |
297 | } | |
298 | } else { | |
299 | break; | |
300 | } | |
301 | } | |
302 | } | |
303 | gc.dispose(); | |
304 | return marker; | |
305 | } | |
306 | } |