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 | ||
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 | } |