Commit | Line | Data |
---|---|---|
c392540b | 1 | /******************************************************************************* |
576f0a4e | 2 | * Copyright (c) 2011, 2014 Ericsson |
20ff3b75 | 3 | * |
c392540b FC |
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 | |
20ff3b75 | 8 | * |
c392540b FC |
9 | * Contributors: |
10 | * Francois Chouinard - Initial API and implementation | |
fbd124dd | 11 | * Bernd Hufmann - Changed to updated histogram data model |
f8177ba2 | 12 | * Francois Chouinard - Reformat histogram labels on format change |
0fcf3b09 | 13 | * Patrick Tasse - Support selection range |
2fc582d2 | 14 | * Xavier Raynaud - Support multi-trace coloring |
c392540b FC |
15 | *******************************************************************************/ |
16 | ||
e0752744 | 17 | package org.eclipse.linuxtools.tmf.ui.views.histogram; |
c392540b | 18 | |
f8177ba2 FC |
19 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler; |
20 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; | |
21 | import org.eclipse.linuxtools.tmf.core.signal.TmfTimestampFormatUpdateSignal; | |
3bd46eef AM |
22 | import org.eclipse.linuxtools.tmf.core.timestamp.ITmfTimestamp; |
23 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestamp; | |
720d67cb | 24 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestampDelta; |
3bd46eef | 25 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestampFormat; |
c392540b | 26 | import org.eclipse.linuxtools.tmf.ui.views.TmfView; |
720d67cb | 27 | import org.eclipse.osgi.util.NLS; |
c392540b FC |
28 | import org.eclipse.swt.SWT; |
29 | import org.eclipse.swt.events.ControlEvent; | |
30 | import org.eclipse.swt.events.ControlListener; | |
2fea16bc MAL |
31 | import org.eclipse.swt.events.DisposeEvent; |
32 | import org.eclipse.swt.events.DisposeListener; | |
c392540b FC |
33 | import org.eclipse.swt.events.KeyEvent; |
34 | import org.eclipse.swt.events.KeyListener; | |
35 | import org.eclipse.swt.events.MouseEvent; | |
36 | import org.eclipse.swt.events.MouseListener; | |
f888477a | 37 | import org.eclipse.swt.events.MouseMoveListener; |
c392540b | 38 | import org.eclipse.swt.events.MouseTrackListener; |
65cdf787 | 39 | import org.eclipse.swt.events.MouseWheelListener; |
c392540b FC |
40 | import org.eclipse.swt.events.PaintEvent; |
41 | import org.eclipse.swt.events.PaintListener; | |
42 | import org.eclipse.swt.graphics.Color; | |
43 | import org.eclipse.swt.graphics.Font; | |
44 | import org.eclipse.swt.graphics.FontData; | |
45 | import org.eclipse.swt.graphics.GC; | |
46 | import org.eclipse.swt.graphics.Image; | |
e60df94a | 47 | import org.eclipse.swt.layout.FillLayout; |
c392540b FC |
48 | import org.eclipse.swt.layout.GridData; |
49 | import org.eclipse.swt.layout.GridLayout; | |
50 | import org.eclipse.swt.widgets.Canvas; | |
51 | import org.eclipse.swt.widgets.Composite; | |
52 | import org.eclipse.swt.widgets.Display; | |
65cdf787 | 53 | import org.eclipse.swt.widgets.Label; |
c392540b FC |
54 | |
55 | /** | |
b544077e | 56 | * Re-usable histogram widget. |
20ff3b75 | 57 | * |
b544077e | 58 | * It has the following features: |
c392540b FC |
59 | * <ul> |
60 | * <li>Y-axis labels displaying min/max count values | |
61 | * <li>X-axis labels displaying time range | |
62 | * <li>a histogram displaying the distribution of values over time (note that | |
63 | * the histogram might not necessarily fill the whole canvas) | |
64 | * </ul> | |
65 | * The widget also has 2 'markers' to identify: | |
66 | * <ul> | |
67 | * <li>a red dashed line over the bar that contains the currently selected event | |
68 | * <li>a dark red dashed line that delimits the right end of the histogram (if | |
69 | * it doesn't fill the canvas) | |
70 | * </ul> | |
71 | * Clicking on the histogram will select the current event at the mouse | |
72 | * location. | |
73 | * <p> | |
74 | * Once the histogram is selected, there is some limited keyboard support: | |
75 | * <ul> | |
76 | * <li>Home: go to the first histogram bar | |
77 | * <li>End: go to the last histogram bar | |
78 | * <li>Left: go to the previous histogram | |
79 | * <li>Right: go to the next histogram bar | |
80 | * </ul> | |
81 | * Finally, when the mouse hovers over the histogram, a tool tip showing the | |
82 | * following information about the corresponding histogram bar time range: | |
83 | * <ul> | |
84 | * <li>start of the time range | |
85 | * <li>end of the time range | |
86 | * <li>number of events in that time range | |
87 | * </ul> | |
20ff3b75 | 88 | * |
f8177ba2 | 89 | * @version 1.1 |
b544077e | 90 | * @author Francois Chouinard |
c392540b | 91 | */ |
f888477a | 92 | public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseMoveListener, MouseTrackListener, IHistogramModelListener { |
c392540b FC |
93 | |
94 | // ------------------------------------------------------------------------ | |
95 | // Constants | |
96 | // ------------------------------------------------------------------------ | |
97 | ||
c392540b | 98 | // Histogram colors |
2fea16bc MAL |
99 | |
100 | // System colors, they do not need to be disposed | |
c392540b | 101 | private final Color fBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); |
0fcf3b09 PT |
102 | private final Color fSelectionForegroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE); |
103 | private final Color fSelectionBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); | |
c392540b | 104 | private final Color fLastEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED); |
2fea16bc MAL |
105 | |
106 | // Application colors, they need to be disposed | |
2fc582d2 XR |
107 | private final Color[] fHistoBarColors = new Color[] {new Color(Display.getDefault(), 90, 90, 255), // blue |
108 | new Color(Display.getDefault(), 0, 240, 0), // green | |
109 | new Color(Display.getDefault(), 255, 0, 0), // red | |
110 | new Color(Display.getDefault(), 0, 255, 255), // cyan | |
111 | new Color(Display.getDefault(), 255, 80, 255), // magenta | |
112 | new Color(Display.getDefault(), 200, 200, 0), // yellow | |
113 | new Color(Display.getDefault(), 200, 150, 0), // brown | |
114 | new Color(Display.getDefault(), 150, 255, 150), // light green | |
115 | new Color(Display.getDefault(), 200, 80, 80), // dark red | |
116 | new Color(Display.getDefault(), 30, 150, 150), // dark cyan | |
117 | new Color(Display.getDefault(), 200, 200, 255), // light blue | |
118 | new Color(Display.getDefault(), 0, 120, 0), // dark green | |
119 | new Color(Display.getDefault(), 255, 150, 150), // lighter red | |
120 | new Color(Display.getDefault(), 140, 80, 140), // dark magenta | |
121 | new Color(Display.getDefault(), 150, 100, 50), // brown | |
122 | new Color(Display.getDefault(), 255, 80, 80), // light red | |
123 | new Color(Display.getDefault(), 200, 200, 200), // light grey | |
124 | new Color(Display.getDefault(), 255, 200, 80), // orange | |
125 | new Color(Display.getDefault(), 255, 255, 80), // pale yellow | |
126 | new Color(Display.getDefault(), 255, 200, 200), // pale red | |
127 | new Color(Display.getDefault(), 255, 200, 255), // pale magenta | |
128 | new Color(Display.getDefault(), 255, 255, 200), // pale pale yellow | |
129 | new Color(Display.getDefault(), 200, 255, 255), // pale pale blue | |
130 | }; | |
79d60771 | 131 | private final Color fTimeRangeColor = new Color(Display.getCurrent(), 255, 128, 0); |
2fea16bc | 132 | private final Color fLostEventColor = new Color(Display.getCurrent(), 208, 62, 120); |
c392540b | 133 | |
f888477a PT |
134 | // Drag states |
135 | /** | |
136 | * No drag in progress | |
137 | * @since 2.2 | |
138 | */ | |
139 | protected final int DRAG_NONE = 0; | |
140 | /** | |
141 | * Drag the selection | |
142 | * @since 2.2 | |
143 | */ | |
144 | protected final int DRAG_SELECTION = 1; | |
145 | /** | |
146 | * Drag the time range | |
147 | * @since 2.2 | |
148 | */ | |
149 | protected final int DRAG_RANGE = 2; | |
79d60771 PT |
150 | /** |
151 | * Drag the zoom range | |
152 | * @since 2.2 | |
153 | */ | |
154 | protected final int DRAG_ZOOM = 3; | |
f888477a | 155 | |
c392540b FC |
156 | // ------------------------------------------------------------------------ |
157 | // Attributes | |
158 | // ------------------------------------------------------------------------ | |
159 | ||
b544077e BH |
160 | /** |
161 | * The parent TMF view. | |
162 | */ | |
c392540b FC |
163 | protected TmfView fParentView; |
164 | ||
da7bdcbc | 165 | private Composite fComposite; |
f8177ba2 FC |
166 | private Font fFont; |
167 | ||
c392540b | 168 | // Histogram text fields |
3311a6ca PT |
169 | private Label fMaxNbEventsLabel; |
170 | private Label fMinNbEventsLabel; | |
171 | private Label fTimeRangeStartLabel; | |
172 | private Label fTimeRangeEndLabel; | |
c392540b | 173 | |
b544077e | 174 | /** |
95aa81ef | 175 | * Histogram drawing area |
b544077e | 176 | */ |
c392540b | 177 | protected Canvas fCanvas; |
f8177ba2 | 178 | |
b544077e | 179 | /** |
95aa81ef | 180 | * The histogram data model. |
b544077e | 181 | */ |
c392540b | 182 | protected final HistogramDataModel fDataModel; |
f8177ba2 | 183 | |
b544077e | 184 | /** |
20ff3b75 | 185 | * The histogram data model scaled to current resolution and screen width. |
b544077e | 186 | */ |
c392540b FC |
187 | protected HistogramScaledData fScaledData; |
188 | ||
f8177ba2 FC |
189 | /** |
190 | * The current event value | |
191 | */ | |
192 | protected long fCurrentEventTime = 0L; | |
c392540b | 193 | |
0fcf3b09 PT |
194 | /** |
195 | * The current selection begin time | |
196 | */ | |
197 | private long fSelectionBegin = 0L; | |
198 | ||
199 | /** | |
200 | * The current selection end time | |
201 | */ | |
202 | private long fSelectionEnd = 0L; | |
203 | ||
f888477a PT |
204 | /** |
205 | * The drag state | |
206 | * @see #DRAG_NONE | |
207 | * @see #DRAG_SELECTION | |
208 | * @see #DRAG_RANGE | |
79d60771 | 209 | * @see #DRAG_ZOOM |
f888477a PT |
210 | * @since 2.2 |
211 | */ | |
212 | protected int fDragState = DRAG_NONE; | |
213 | ||
214 | /** | |
215 | * The button that started a mouse drag, or 0 if no drag in progress | |
216 | * @since 2.2 | |
217 | */ | |
218 | protected int fDragButton = 0; | |
219 | ||
cc817e65 PT |
220 | /** |
221 | * The bucket display offset | |
222 | */ | |
223 | private int fOffset = 0; | |
224 | ||
2fc582d2 XR |
225 | /** |
226 | * show the traces or not | |
227 | * @since 3.0 | |
228 | */ | |
229 | static boolean showTraces = true; | |
230 | ||
c392540b FC |
231 | // ------------------------------------------------------------------------ |
232 | // Construction | |
233 | // ------------------------------------------------------------------------ | |
234 | ||
b544077e | 235 | /** |
f8177ba2 | 236 | * Full constructor. |
20ff3b75 | 237 | * |
b544077e BH |
238 | * @param view A reference to the parent TMF view. |
239 | * @param parent A parent composite | |
240 | */ | |
09e86496 | 241 | public Histogram(final TmfView view, final Composite parent) { |
c392540b FC |
242 | fParentView = view; |
243 | ||
da7bdcbc | 244 | fComposite = createWidget(parent); |
c392540b | 245 | fDataModel = new HistogramDataModel(); |
fbd124dd | 246 | fDataModel.addHistogramListener(this); |
c392540b FC |
247 | clear(); |
248 | ||
249 | fCanvas.addControlListener(this); | |
250 | fCanvas.addPaintListener(this); | |
251 | fCanvas.addKeyListener(this); | |
252 | fCanvas.addMouseListener(this); | |
253 | fCanvas.addMouseTrackListener(this); | |
f888477a | 254 | fCanvas.addMouseMoveListener(this); |
f8177ba2 FC |
255 | |
256 | TmfSignalManager.register(this); | |
c392540b FC |
257 | } |
258 | ||
b544077e | 259 | /** |
f8177ba2 | 260 | * Dispose resources and unregisters listeners. |
b544077e | 261 | */ |
c392540b | 262 | public void dispose() { |
f8177ba2 | 263 | TmfSignalManager.deregister(this); |
2fea16bc | 264 | fLostEventColor.dispose(); |
2fc582d2 XR |
265 | for (Color c : fHistoBarColors) { |
266 | c.dispose(); | |
267 | } | |
79d60771 | 268 | fTimeRangeColor.dispose(); |
2fea16bc | 269 | fFont.dispose(); |
fbd124dd | 270 | fDataModel.removeHistogramListener(this); |
c53a7992 | 271 | fDataModel.dispose(); |
c392540b FC |
272 | } |
273 | ||
da7bdcbc | 274 | private Composite createWidget(final Composite parent) { |
c392540b | 275 | |
f8177ba2 | 276 | fFont = adjustFont(parent); |
c392540b FC |
277 | |
278 | final int initalWidth = 10; | |
279 | ||
280 | // -------------------------------------------------------------------- | |
281 | // Define the histogram | |
282 | // -------------------------------------------------------------------- | |
283 | ||
09e86496 | 284 | final GridLayout gridLayout = new GridLayout(); |
c392540b FC |
285 | gridLayout.numColumns = 3; |
286 | gridLayout.marginHeight = 0; | |
287 | gridLayout.marginWidth = 0; | |
288 | gridLayout.marginTop = 0; | |
289 | gridLayout.horizontalSpacing = 0; | |
290 | gridLayout.verticalSpacing = 0; | |
291 | gridLayout.marginLeft = 0; | |
292 | gridLayout.marginRight = 0; | |
09e86496 | 293 | final Composite composite = new Composite(parent, SWT.FILL); |
c392540b FC |
294 | composite.setLayout(gridLayout); |
295 | ||
296 | // Use all the horizontal space | |
297 | GridData gridData = new GridData(); | |
298 | gridData.horizontalAlignment = SWT.FILL; | |
299 | gridData.verticalAlignment = SWT.FILL; | |
300 | gridData.grabExcessHorizontalSpace = true; | |
3a790c10 | 301 | gridData.grabExcessVerticalSpace = true; |
c392540b FC |
302 | composite.setLayoutData(gridData); |
303 | ||
304 | // Y-axis max event | |
305 | gridData = new GridData(); | |
306 | gridData.horizontalAlignment = SWT.RIGHT; | |
307 | gridData.verticalAlignment = SWT.TOP; | |
3311a6ca PT |
308 | fMaxNbEventsLabel = new Label(composite, SWT.RIGHT); |
309 | fMaxNbEventsLabel.setFont(fFont); | |
310 | fMaxNbEventsLabel.setText("0"); //$NON-NLS-1$ | |
311 | fMaxNbEventsLabel.setLayoutData(gridData); | |
c392540b FC |
312 | |
313 | // Histogram itself | |
e60df94a | 314 | Composite canvasComposite = new Composite(composite, SWT.BORDER); |
c392540b FC |
315 | gridData = new GridData(); |
316 | gridData.horizontalSpan = 2; | |
317 | gridData.verticalSpan = 2; | |
318 | gridData.horizontalAlignment = SWT.FILL; | |
319 | gridData.verticalAlignment = SWT.FILL; | |
3a790c10 PT |
320 | gridData.heightHint = 0; |
321 | gridData.widthHint = 0; | |
c392540b | 322 | gridData.grabExcessHorizontalSpace = true; |
3a790c10 | 323 | gridData.grabExcessVerticalSpace = true; |
e60df94a PT |
324 | canvasComposite.setLayoutData(gridData); |
325 | canvasComposite.setLayout(new FillLayout()); | |
326 | fCanvas = new Canvas(canvasComposite, SWT.DOUBLE_BUFFERED); | |
2fea16bc MAL |
327 | fCanvas.addDisposeListener(new DisposeListener() { |
328 | @Override | |
329 | public void widgetDisposed(DisposeEvent e) { | |
330 | Object image = fCanvas.getData(IMAGE_KEY); | |
331 | if (image instanceof Image) { | |
332 | ((Image) image).dispose(); | |
333 | } | |
334 | } | |
335 | }); | |
c392540b FC |
336 | |
337 | // Y-axis min event (always 0...) | |
338 | gridData = new GridData(); | |
339 | gridData.horizontalAlignment = SWT.RIGHT; | |
340 | gridData.verticalAlignment = SWT.BOTTOM; | |
3311a6ca PT |
341 | fMinNbEventsLabel = new Label(composite, SWT.RIGHT); |
342 | fMinNbEventsLabel.setFont(fFont); | |
343 | fMinNbEventsLabel.setText("0"); //$NON-NLS-1$ | |
344 | fMinNbEventsLabel.setLayoutData(gridData); | |
c392540b FC |
345 | |
346 | // Dummy cell | |
347 | gridData = new GridData(initalWidth, SWT.DEFAULT); | |
348 | gridData.horizontalAlignment = SWT.RIGHT; | |
349 | gridData.verticalAlignment = SWT.BOTTOM; | |
65cdf787 PT |
350 | final Label dummyLabel = new Label(composite, SWT.NONE); |
351 | dummyLabel.setLayoutData(gridData); | |
c392540b FC |
352 | |
353 | // Window range start time | |
354 | gridData = new GridData(); | |
355 | gridData.horizontalAlignment = SWT.LEFT; | |
356 | gridData.verticalAlignment = SWT.BOTTOM; | |
3311a6ca PT |
357 | fTimeRangeStartLabel = new Label(composite, SWT.NONE); |
358 | fTimeRangeStartLabel.setFont(fFont); | |
359 | fTimeRangeStartLabel.setLayoutData(gridData); | |
c392540b FC |
360 | |
361 | // Window range end time | |
362 | gridData = new GridData(); | |
363 | gridData.horizontalAlignment = SWT.RIGHT; | |
364 | gridData.verticalAlignment = SWT.BOTTOM; | |
3311a6ca PT |
365 | fTimeRangeEndLabel = new Label(composite, SWT.NONE); |
366 | fTimeRangeEndLabel.setFont(fFont); | |
367 | fTimeRangeEndLabel.setLayoutData(gridData); | |
65cdf787 | 368 | |
da7bdcbc | 369 | return composite; |
c392540b FC |
370 | } |
371 | ||
abbdd66a | 372 | private static Font adjustFont(final Composite composite) { |
c392540b | 373 | // Reduce font size for a more pleasing rendering |
09e86496 FC |
374 | final int fontSizeAdjustment = -2; |
375 | final Font font = composite.getFont(); | |
376 | final FontData fontData = font.getFontData()[0]; | |
c392540b FC |
377 | return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle()); |
378 | } | |
379 | ||
380 | // ------------------------------------------------------------------------ | |
381 | // Accessors | |
382 | // ------------------------------------------------------------------------ | |
383 | ||
b544077e | 384 | /** |
f8177ba2 | 385 | * Returns the start time (equal first bucket time) |
b544077e BH |
386 | * @return the start time. |
387 | */ | |
c392540b | 388 | public long getStartTime() { |
fbd124dd | 389 | return fDataModel.getFirstBucketTime(); |
c392540b FC |
390 | } |
391 | ||
b544077e BH |
392 | /** |
393 | * Returns the end time. | |
394 | * @return the end time. | |
395 | */ | |
c392540b FC |
396 | public long getEndTime() { |
397 | return fDataModel.getEndTime(); | |
398 | } | |
399 | ||
b544077e BH |
400 | /** |
401 | * Returns the time limit (end of last bucket) | |
402 | * @return the time limit. | |
403 | */ | |
c392540b FC |
404 | public long getTimeLimit() { |
405 | return fDataModel.getTimeLimit(); | |
406 | } | |
09e86496 | 407 | |
20ff3b75 AM |
408 | /** |
409 | * Returns a data model reference. | |
b544077e BH |
410 | * @return data model. |
411 | */ | |
fbd124dd BH |
412 | public HistogramDataModel getDataModel() { |
413 | return fDataModel; | |
414 | } | |
c392540b | 415 | |
95aa81ef | 416 | /** |
3311a6ca | 417 | * Set the max number events to be displayed |
95aa81ef | 418 | * |
3311a6ca PT |
419 | * @param maxNbEvents |
420 | * the maximum number of events | |
95aa81ef | 421 | */ |
3311a6ca PT |
422 | void setMaxNbEvents(long maxNbEvents) { |
423 | fMaxNbEventsLabel.setText(Long.toString(maxNbEvents)); | |
424 | fMaxNbEventsLabel.getParent().layout(); | |
425 | fCanvas.redraw(); | |
95aa81ef JCK |
426 | } |
427 | ||
2fc582d2 XR |
428 | /** |
429 | * Return <code>true</code> if the traces must be displayed in the histogram, | |
430 | * <code>false</code> otherwise. | |
431 | * @return whether the traces should be displayed | |
432 | * @since 3.0 | |
433 | */ | |
434 | public boolean showTraces() { | |
435 | return showTraces && fDataModel.getNbTraces() < getMaxNbTraces(); | |
436 | } | |
437 | ||
438 | /** | |
439 | * Returns the maximum number of traces the histogram can display with separate colors. | |
440 | * If there is more traces, histogram will use only one color to display them. | |
441 | * @return the maximum number of traces the histogram can display. | |
442 | * @since 3.0 | |
443 | */ | |
444 | public int getMaxNbTraces() { | |
445 | return fHistoBarColors.length; | |
446 | } | |
447 | ||
448 | /** | |
449 | * Returns the color used to display the trace at the given index. | |
450 | * @param traceIndex a trace index | |
451 | * @return a {@link Color} | |
452 | * @since 3.0 | |
453 | */ | |
454 | public Color getTraceColor(int traceIndex) { | |
455 | return fHistoBarColors[traceIndex % fHistoBarColors.length]; | |
456 | } | |
457 | ||
c392540b FC |
458 | // ------------------------------------------------------------------------ |
459 | // Operations | |
460 | // ------------------------------------------------------------------------ | |
b544077e | 461 | /** |
20ff3b75 | 462 | * Updates the time range. |
b544077e BH |
463 | * @param startTime A start time |
464 | * @param endTime A end time. | |
465 | */ | |
f888477a PT |
466 | public void updateTimeRange(long startTime, long endTime) { |
467 | if (fDragState == DRAG_NONE) { | |
468 | ((HistogramView) fParentView).updateTimeRange(startTime, endTime); | |
469 | } | |
470 | } | |
c392540b FC |
471 | |
472 | /** | |
473 | * Clear the histogram and reset the data | |
474 | */ | |
475 | public void clear() { | |
476 | fDataModel.clear(); | |
06fcc8fe PT |
477 | if (fDragState == DRAG_SELECTION) { |
478 | updateSelectionTime(); | |
479 | } | |
80c930fa PT |
480 | fDragState = DRAG_NONE; |
481 | fDragButton = 0; | |
dcb3cda5 BH |
482 | synchronized (fDataModel) { |
483 | fScaledData = null; | |
484 | } | |
c392540b FC |
485 | } |
486 | ||
0fcf3b09 PT |
487 | /** |
488 | * Sets the current selection time range and refresh the display | |
489 | * | |
95aa81ef JCK |
490 | * @param beginTime The begin time of the current selection |
491 | * @param endTime The end time of the current selection | |
0fcf3b09 PT |
492 | * @since 2.1 |
493 | */ | |
494 | public void setSelection(final long beginTime, final long endTime) { | |
495 | fSelectionBegin = (beginTime > 0) ? beginTime : 0; | |
496 | fSelectionEnd = (endTime > 0) ? endTime : 0; | |
497 | fDataModel.setSelectionNotifyListeners(beginTime, endTime); | |
c392540b FC |
498 | } |
499 | ||
500 | /** | |
501 | * Computes the timestamp of the bucket at [offset] | |
20ff3b75 | 502 | * |
c392540b FC |
503 | * @param offset offset from the left on the histogram |
504 | * @return the start timestamp of the corresponding bucket | |
505 | */ | |
09e86496 | 506 | public synchronized long getTimestamp(final int offset) { |
c392540b FC |
507 | assert offset > 0 && offset < fScaledData.fWidth; |
508 | try { | |
cc817e65 | 509 | return fScaledData.fFirstBucketTime + fScaledData.fBucketDuration * offset; |
09e86496 | 510 | } catch (final Exception e) { |
c392540b FC |
511 | return 0; // TODO: Fix that racing condition (NPE) |
512 | } | |
513 | } | |
514 | ||
515 | /** | |
516 | * Computes the offset of the timestamp in the histogram | |
20ff3b75 | 517 | * |
c392540b FC |
518 | * @param timestamp the timestamp |
519 | * @return the offset of the corresponding bucket (-1 if invalid) | |
520 | */ | |
09e86496 | 521 | public synchronized int getOffset(final long timestamp) { |
20ff3b75 | 522 | if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) { |
c392540b | 523 | return -1; |
20ff3b75 | 524 | } |
fbd124dd | 525 | return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration); |
c392540b FC |
526 | } |
527 | ||
cc817e65 PT |
528 | /** |
529 | * Set the bucket display offset | |
530 | * | |
531 | * @param offset | |
532 | * the bucket display offset | |
533 | * @since 2.2 | |
534 | */ | |
535 | protected void setOffset(final int offset) { | |
536 | fOffset = offset; | |
537 | } | |
538 | ||
c392540b FC |
539 | /** |
540 | * Move the currently selected bar cursor to a non-empty bucket. | |
20ff3b75 | 541 | * |
c392540b FC |
542 | * @param keyCode the SWT key code |
543 | */ | |
09e86496 | 544 | protected void moveCursor(final int keyCode) { |
c392540b | 545 | |
c392540b FC |
546 | int index; |
547 | switch (keyCode) { | |
548 | ||
95aa81ef JCK |
549 | case SWT.HOME: |
550 | index = 0; | |
2fc582d2 | 551 | while (index < fScaledData.fLastBucket && fScaledData.fData[index].isEmpty()) { |
95aa81ef JCK |
552 | index++; |
553 | } | |
554 | if (index < fScaledData.fLastBucket) { | |
555 | fScaledData.fSelectionBeginBucket = index; | |
556 | } | |
557 | break; | |
c392540b | 558 | |
95aa81ef JCK |
559 | case SWT.ARROW_RIGHT: |
560 | index = Math.max(0, fScaledData.fSelectionBeginBucket + 1); | |
2fc582d2 | 561 | while (index < fScaledData.fWidth && fScaledData.fData[index].isEmpty()) { |
95aa81ef JCK |
562 | index++; |
563 | } | |
564 | if (index < fScaledData.fLastBucket) { | |
565 | fScaledData.fSelectionBeginBucket = index; | |
566 | } | |
567 | break; | |
c392540b | 568 | |
95aa81ef JCK |
569 | case SWT.END: |
570 | index = fScaledData.fLastBucket; | |
2fc582d2 | 571 | while (index >= 0 && fScaledData.fData[index].isEmpty()) { |
95aa81ef JCK |
572 | index--; |
573 | } | |
574 | if (index >= 0) { | |
575 | fScaledData.fSelectionBeginBucket = index; | |
576 | } | |
577 | break; | |
c392540b | 578 | |
95aa81ef JCK |
579 | case SWT.ARROW_LEFT: |
580 | index = Math.min(fScaledData.fLastBucket - 1, fScaledData.fSelectionBeginBucket - 1); | |
2fc582d2 | 581 | while (index >= 0 && fScaledData.fData[index].isEmpty()) { |
95aa81ef JCK |
582 | index--; |
583 | } | |
584 | if (index >= 0) { | |
585 | fScaledData.fSelectionBeginBucket = index; | |
586 | } | |
587 | break; | |
c392540b | 588 | |
95aa81ef JCK |
589 | default: |
590 | return; | |
c392540b FC |
591 | } |
592 | ||
0fcf3b09 PT |
593 | fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket; |
594 | fSelectionBegin = getTimestamp(fScaledData.fSelectionBeginBucket); | |
595 | fSelectionEnd = fSelectionBegin; | |
596 | updateSelectionTime(); | |
c392540b FC |
597 | } |
598 | ||
599 | /** | |
600 | * Refresh the histogram display | |
601 | */ | |
fbd124dd BH |
602 | @Override |
603 | public void modelUpdated() { | |
20ff3b75 | 604 | if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) { |
c392540b FC |
605 | fCanvas.getDisplay().asyncExec(new Runnable() { |
606 | @Override | |
607 | public void run() { | |
608 | if (!fCanvas.isDisposed()) { | |
609 | // Retrieve and normalize the data | |
09e86496 FC |
610 | final int canvasWidth = fCanvas.getBounds().width; |
611 | final int canvasHeight = fCanvas.getBounds().height; | |
20ff3b75 | 612 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
40890fec | 613 | return; |
20ff3b75 | 614 | } |
0fcf3b09 | 615 | fDataModel.setSelection(fSelectionBegin, fSelectionEnd); |
f8177ba2 | 616 | fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, 1); |
95aa81ef | 617 | synchronized (fDataModel) { |
0316808c FC |
618 | if (fScaledData != null) { |
619 | fCanvas.redraw(); | |
d418423b PT |
620 | // Display histogram and update X-,Y-axis labels |
621 | updateRangeTextControls(); | |
95aa81ef | 622 | long maxNbEvents = HistogramScaledData.hideLostEvents ? fScaledData.fMaxValue : fScaledData.fMaxCombinedValue; |
3311a6ca | 623 | fMaxNbEventsLabel.setText(Long.toString(maxNbEvents)); |
0316808c | 624 | // The Y-axis area might need to be re-sized |
3311a6ca PT |
625 | GridData gd = (GridData) fMaxNbEventsLabel.getLayoutData(); |
626 | gd.widthHint = Math.max(gd.widthHint, fMaxNbEventsLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x); | |
627 | fMaxNbEventsLabel.getParent().layout(); | |
0316808c | 628 | } |
09e86496 | 629 | } |
c392540b FC |
630 | } |
631 | } | |
632 | }); | |
20ff3b75 | 633 | } |
c392540b FC |
634 | } |
635 | ||
65cdf787 PT |
636 | /** |
637 | * Add a mouse wheel listener to the histogram | |
638 | * @param listener the mouse wheel listener | |
639 | * @since 2.0 | |
640 | */ | |
641 | public void addMouseWheelListener(MouseWheelListener listener) { | |
642 | fCanvas.addMouseWheelListener(listener); | |
643 | } | |
644 | ||
645 | /** | |
646 | * Remove a mouse wheel listener from the histogram | |
647 | * @param listener the mouse wheel listener | |
648 | * @since 2.0 | |
649 | */ | |
650 | public void removeMouseWheelListener(MouseWheelListener listener) { | |
651 | fCanvas.removeMouseWheelListener(listener); | |
652 | } | |
653 | ||
ba1f5c20 PT |
654 | /** |
655 | * Add a key listener to the histogram | |
656 | * @param listener the key listener | |
657 | * @since 3.1 | |
658 | */ | |
659 | public void addKeyListener(KeyListener listener) { | |
660 | fCanvas.addKeyListener(listener); | |
661 | } | |
662 | ||
663 | /** | |
664 | * Remove a key listener from the histogram | |
665 | * @param listener the key listener | |
666 | * @since 3.1 | |
667 | */ | |
668 | public void removeKeyListener(KeyListener listener) { | |
669 | fCanvas.removeKeyListener(listener); | |
670 | } | |
671 | ||
c392540b FC |
672 | // ------------------------------------------------------------------------ |
673 | // Helper functions | |
674 | // ------------------------------------------------------------------------ | |
675 | ||
0fcf3b09 | 676 | private void updateSelectionTime() { |
720d67cb PT |
677 | if (fSelectionBegin > fSelectionEnd) { |
678 | long end = fSelectionBegin; | |
679 | fSelectionBegin = fSelectionEnd; | |
680 | fSelectionEnd = end; | |
681 | } | |
0fcf3b09 | 682 | ((HistogramView) fParentView).updateSelectionTime(fSelectionBegin, fSelectionEnd); |
c392540b FC |
683 | } |
684 | ||
d418423b PT |
685 | /** |
686 | * Update the range text controls | |
687 | */ | |
688 | private void updateRangeTextControls() { | |
31d6440d | 689 | if (fDataModel.getStartTime() < fDataModel.getEndTime()) { |
3311a6ca PT |
690 | fTimeRangeStartLabel.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getStartTime())); |
691 | fTimeRangeEndLabel.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime())); | |
d418423b | 692 | } else { |
3311a6ca PT |
693 | fTimeRangeStartLabel.setText(""); //$NON-NLS-1$ |
694 | fTimeRangeEndLabel.setText(""); //$NON-NLS-1$ | |
d418423b PT |
695 | } |
696 | } | |
697 | ||
c392540b FC |
698 | // ------------------------------------------------------------------------ |
699 | // PaintListener | |
700 | // ------------------------------------------------------------------------ | |
b544077e BH |
701 | /** |
702 | * Image key string for the canvas. | |
703 | */ | |
c392540b FC |
704 | protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$ |
705 | ||
706 | @Override | |
09e86496 | 707 | public void paintControl(final PaintEvent event) { |
c392540b FC |
708 | |
709 | // Get the geometry | |
09e86496 FC |
710 | final int canvasWidth = fCanvas.getBounds().width; |
711 | final int canvasHeight = fCanvas.getBounds().height; | |
c392540b FC |
712 | |
713 | // Make sure we have something to draw upon | |
20ff3b75 | 714 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
c392540b | 715 | return; |
20ff3b75 | 716 | } |
c392540b FC |
717 | |
718 | // Retrieve image; re-create only if necessary | |
719 | Image image = (Image) fCanvas.getData(IMAGE_KEY); | |
720 | if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) { | |
2fea16bc MAL |
721 | if (image != null) { |
722 | image.dispose(); | |
723 | } | |
c392540b FC |
724 | image = new Image(event.display, canvasWidth, canvasHeight); |
725 | fCanvas.setData(IMAGE_KEY, image); | |
726 | } | |
727 | ||
728 | // Draw the histogram on its canvas | |
09e86496 | 729 | final GC imageGC = new GC(image); |
c392540b FC |
730 | formatImage(imageGC, image); |
731 | event.gc.drawImage(image, 0, 0); | |
732 | imageGC.dispose(); | |
733 | } | |
734 | ||
09e86496 | 735 | private void formatImage(final GC imageGC, final Image image) { |
c392540b | 736 | |
20ff3b75 | 737 | if (fScaledData == null) { |
c392540b | 738 | return; |
20ff3b75 | 739 | } |
c392540b | 740 | |
09e86496 | 741 | final HistogramScaledData scaledData = new HistogramScaledData(fScaledData); |
c392540b FC |
742 | |
743 | try { | |
744 | // Get drawing boundaries | |
09e86496 FC |
745 | final int width = image.getBounds().width; |
746 | final int height = image.getBounds().height; | |
c392540b FC |
747 | |
748 | // Clear the drawing area | |
749 | imageGC.setBackground(fBackgroundColor); | |
750 | imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1); | |
751 | ||
752 | // Draw the histogram bars | |
09e86496 | 753 | final int limit = width < scaledData.fWidth ? width : scaledData.fWidth; |
95aa81ef | 754 | double factor = HistogramScaledData.hideLostEvents ? scaledData.fScalingFactor : scaledData.fScalingFactorCombined; |
2fc582d2 | 755 | final boolean showTracesColors = showTraces(); |
e6953950 | 756 | for (int i = 0; i < limit; i++) { |
2fc582d2 XR |
757 | HistogramBucket hb = scaledData.fData[i]; |
758 | int totalNbEvents = hb.getNbEvents(); | |
759 | int value = (int) Math.ceil(totalNbEvents * factor); | |
cc817e65 | 760 | int x = i + fOffset; |
95aa81ef | 761 | |
b10913b2 PT |
762 | // in Linux, the last pixel in a line is not drawn, |
763 | // so draw lost events first, one pixel too far | |
95aa81ef JCK |
764 | if (!HistogramScaledData.hideLostEvents) { |
765 | imageGC.setForeground(fLostEventColor); | |
766 | final int lostEventValue = (int) Math.ceil(scaledData.fLostEventsData[i] * factor); | |
767 | if (lostEventValue != 0) { | |
b10913b2 PT |
768 | // drawing a line is inclusive, so we should remove 1 from y2 |
769 | // but we don't because Linux | |
770 | imageGC.drawLine(x, height - value - lostEventValue, x, height - value); | |
95aa81ef JCK |
771 | } |
772 | } | |
b10913b2 PT |
773 | |
774 | // then draw normal events second, to overwrite that extra pixel | |
2fc582d2 XR |
775 | if (!hb.isEmpty()) { |
776 | if (showTracesColors) { | |
777 | for (int traceIndex = 0; traceIndex < hb.getNbTraces(); traceIndex++) { | |
778 | int nbEventsForTrace = hb.getNbEvent(traceIndex); | |
779 | if (nbEventsForTrace > 0) { | |
780 | Color c = fHistoBarColors[traceIndex % fHistoBarColors.length]; | |
781 | imageGC.setForeground(c); | |
782 | imageGC.drawLine(x, height - value, x, height); | |
783 | totalNbEvents -= nbEventsForTrace; | |
784 | value = (int) Math.ceil(totalNbEvents * scaledData.fScalingFactor); | |
785 | } | |
786 | } | |
787 | } else { | |
788 | Color c = fHistoBarColors[0]; | |
789 | imageGC.setForeground(c); | |
790 | imageGC.drawLine(x, height - value, x, height); | |
791 | } | |
792 | } | |
c392540b FC |
793 | } |
794 | ||
0fcf3b09 PT |
795 | // Draw the selection bars |
796 | int alpha = imageGC.getAlpha(); | |
797 | imageGC.setAlpha(100); | |
798 | imageGC.setForeground(fSelectionForegroundColor); | |
799 | imageGC.setBackground(fSelectionBackgroundColor); | |
cc817e65 | 800 | final int beginBucket = scaledData.fSelectionBeginBucket + fOffset; |
0fcf3b09 PT |
801 | if (beginBucket >= 0 && beginBucket < limit) { |
802 | imageGC.drawLine(beginBucket, 0, beginBucket, height); | |
803 | } | |
cc817e65 | 804 | final int endBucket = scaledData.fSelectionEndBucket + fOffset; |
0fcf3b09 PT |
805 | if (endBucket >= 0 && endBucket < limit && endBucket != beginBucket) { |
806 | imageGC.drawLine(endBucket, 0, endBucket, height); | |
807 | } | |
f888477a PT |
808 | if (Math.abs(endBucket - beginBucket) > 1) { |
809 | if (endBucket > beginBucket) { | |
810 | imageGC.fillRectangle(beginBucket + 1, 0, endBucket - beginBucket - 1, height); | |
811 | } else { | |
812 | imageGC.fillRectangle(endBucket + 1, 0, beginBucket - endBucket - 1, height); | |
813 | } | |
0fcf3b09 PT |
814 | } |
815 | imageGC.setAlpha(alpha); | |
f888477a PT |
816 | |
817 | // Add a dashed line as a delimiter | |
818 | int delimiterIndex = (int) ((getDataModel().getEndTime() - scaledData.getFirstBucketTime()) / scaledData.fBucketDuration) + 1; | |
819 | drawDelimiter(imageGC, fLastEventColor, height, delimiterIndex); | |
820 | ||
821 | // Fill the area to the right of delimiter with background color | |
3311a6ca | 822 | imageGC.setBackground(fComposite.getBackground()); |
f888477a PT |
823 | imageGC.fillRectangle(delimiterIndex + 1, 0, width - (delimiterIndex + 1), height); |
824 | ||
09e86496 | 825 | } catch (final Exception e) { |
c392540b FC |
826 | // Do nothing |
827 | } | |
828 | } | |
829 | ||
abbdd66a AM |
830 | private static void drawDelimiter(final GC imageGC, final Color color, |
831 | final int height, final int index) { | |
c392540b | 832 | imageGC.setBackground(color); |
09e86496 | 833 | final int dash = height / 4; |
c392540b FC |
834 | imageGC.fillRectangle(index, 0 * dash, 1, dash - 1); |
835 | imageGC.fillRectangle(index, 1 * dash, 1, dash - 1); | |
836 | imageGC.fillRectangle(index, 2 * dash, 1, dash - 1); | |
837 | imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash); | |
838 | } | |
839 | ||
79d60771 PT |
840 | /** |
841 | * Draw a time range window | |
842 | * | |
843 | * @param imageGC | |
844 | * the GC | |
845 | * @param rangeStartTime | |
846 | * the range start time | |
847 | * @param rangeDuration | |
848 | * the range duration | |
849 | * @since 2.2 | |
850 | */ | |
851 | protected void drawTimeRangeWindow(GC imageGC, long rangeStartTime, long rangeDuration) { | |
852 | ||
647640df BH |
853 | if (fScaledData == null) { |
854 | return; | |
855 | } | |
856 | ||
79d60771 PT |
857 | // Map times to histogram coordinates |
858 | long bucketSpan = Math.max(fScaledData.fBucketDuration, 1); | |
859 | long startTime = Math.min(rangeStartTime, rangeStartTime + rangeDuration); | |
860 | int rangeWidth = (int) (Math.abs(rangeDuration) / bucketSpan); | |
861 | ||
862 | int left = (int) ((startTime - fDataModel.getFirstBucketTime()) / bucketSpan); | |
863 | int right = left + rangeWidth; | |
864 | int center = (left + right) / 2; | |
865 | int height = fCanvas.getSize().y; | |
b10913b2 | 866 | int arc = Math.min(15, rangeWidth); |
79d60771 PT |
867 | |
868 | // Draw the selection window | |
869 | imageGC.setForeground(fTimeRangeColor); | |
870 | imageGC.setLineWidth(1); | |
871 | imageGC.setLineStyle(SWT.LINE_SOLID); | |
b10913b2 | 872 | imageGC.drawRoundRectangle(left, 0, rangeWidth, height - 1, arc, arc); |
79d60771 PT |
873 | |
874 | // Fill the selection window | |
875 | imageGC.setBackground(fTimeRangeColor); | |
876 | imageGC.setAlpha(35); | |
b10913b2 | 877 | imageGC.fillRoundRectangle(left + 1, 1, rangeWidth - 1, height - 2, arc, arc); |
79d60771 PT |
878 | imageGC.setAlpha(255); |
879 | ||
880 | // Draw the cross hair | |
881 | imageGC.setForeground(fTimeRangeColor); | |
882 | imageGC.setLineWidth(1); | |
883 | imageGC.setLineStyle(SWT.LINE_SOLID); | |
884 | ||
885 | int chHalfWidth = ((rangeWidth < 60) ? (rangeWidth * 2) / 3 : 40) / 2; | |
886 | imageGC.drawLine(center - chHalfWidth, height / 2, center + chHalfWidth, height / 2); | |
887 | imageGC.drawLine(center, (height / 2) - chHalfWidth, center, (height / 2) + chHalfWidth); | |
888 | } | |
889 | ||
c392540b FC |
890 | // ------------------------------------------------------------------------ |
891 | // KeyListener | |
892 | // ------------------------------------------------------------------------ | |
893 | ||
894 | @Override | |
09e86496 | 895 | public void keyPressed(final KeyEvent event) { |
c392540b FC |
896 | moveCursor(event.keyCode); |
897 | } | |
898 | ||
899 | @Override | |
09e86496 | 900 | public void keyReleased(final KeyEvent event) { |
c392540b FC |
901 | } |
902 | ||
903 | // ------------------------------------------------------------------------ | |
904 | // MouseListener | |
905 | // ------------------------------------------------------------------------ | |
906 | ||
907 | @Override | |
09e86496 | 908 | public void mouseDoubleClick(final MouseEvent event) { |
c392540b FC |
909 | } |
910 | ||
911 | @Override | |
09e86496 | 912 | public void mouseDown(final MouseEvent event) { |
31d6440d | 913 | if (fScaledData != null && event.button == 1 && fDragState == DRAG_NONE && fDataModel.getStartTime() < fDataModel.getEndTime()) { |
f888477a PT |
914 | fDragState = DRAG_SELECTION; |
915 | fDragButton = event.button; | |
916 | if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) { | |
917 | if (Math.abs(event.x - fScaledData.fSelectionBeginBucket) < Math.abs(event.x - fScaledData.fSelectionEndBucket)) { | |
918 | fScaledData.fSelectionBeginBucket = fScaledData.fSelectionEndBucket; | |
919 | fSelectionBegin = fSelectionEnd; | |
0fcf3b09 | 920 | } |
f888477a PT |
921 | fSelectionEnd = Math.min(getTimestamp(event.x), getEndTime()); |
922 | fScaledData.fSelectionEndBucket = (int) ((fSelectionEnd - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration); | |
923 | } else { | |
924 | fSelectionBegin = Math.min(getTimestamp(event.x), getEndTime()); | |
925 | fScaledData.fSelectionBeginBucket = (int) ((fSelectionBegin - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration); | |
926 | fSelectionEnd = fSelectionBegin; | |
927 | fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket; | |
0fcf3b09 | 928 | } |
f888477a | 929 | fCanvas.redraw(); |
c392540b FC |
930 | } |
931 | } | |
932 | ||
933 | @Override | |
09e86496 | 934 | public void mouseUp(final MouseEvent event) { |
f888477a PT |
935 | if (fDragState == DRAG_SELECTION && event.button == fDragButton) { |
936 | fDragState = DRAG_NONE; | |
937 | fDragButton = 0; | |
938 | updateSelectionTime(); | |
939 | } | |
940 | } | |
941 | ||
942 | // ------------------------------------------------------------------------ | |
943 | // MouseMoveListener | |
944 | // ------------------------------------------------------------------------ | |
945 | ||
946 | /** | |
947 | * @since 2.2 | |
948 | */ | |
949 | @Override | |
950 | public void mouseMove(MouseEvent event) { | |
31d6440d | 951 | if (fDragState == DRAG_SELECTION && fDataModel.getStartTime() < fDataModel.getEndTime()) { |
f888477a PT |
952 | fSelectionEnd = Math.max(getStartTime(), Math.min(getEndTime(), getTimestamp(event.x))); |
953 | fScaledData.fSelectionEndBucket = (int) ((fSelectionEnd - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration); | |
954 | fCanvas.redraw(); | |
955 | } | |
c392540b FC |
956 | } |
957 | ||
958 | // ------------------------------------------------------------------------ | |
959 | // MouseTrackListener | |
960 | // ------------------------------------------------------------------------ | |
961 | ||
962 | @Override | |
09e86496 | 963 | public void mouseEnter(final MouseEvent event) { |
c392540b FC |
964 | } |
965 | ||
966 | @Override | |
09e86496 | 967 | public void mouseExit(final MouseEvent event) { |
c392540b FC |
968 | } |
969 | ||
970 | @Override | |
09e86496 | 971 | public void mouseHover(final MouseEvent event) { |
31d6440d | 972 | if (fDataModel.getStartTime() < fDataModel.getEndTime() && fScaledData != null) { |
cc817e65 PT |
973 | int delimiterIndex = (int) ((fDataModel.getEndTime() - fScaledData.getFirstBucketTime()) / fScaledData.fBucketDuration) + 1; |
974 | if (event.x < delimiterIndex) { | |
975 | final String tooltip = formatToolTipLabel(event.x - fOffset); | |
976 | fCanvas.setToolTipText(tooltip); | |
977 | return; | |
978 | } | |
c392540b | 979 | } |
cc817e65 | 980 | fCanvas.setToolTipText(null); |
c392540b FC |
981 | } |
982 | ||
09e86496 | 983 | private String formatToolTipLabel(final int index) { |
466857f6 | 984 | long startTime = fScaledData.getBucketStartTime(index); |
09e86496 | 985 | // negative values are possible if time values came into the model in decreasing order |
20ff3b75 | 986 | if (startTime < 0) { |
fbd124dd | 987 | startTime = 0; |
20ff3b75 | 988 | } |
466857f6 | 989 | final long endTime = fScaledData.getBucketEndTime(index); |
2fc582d2 | 990 | final int nbEvents = (index >= 0) ? fScaledData.fData[index].getNbEvents() : 0; |
95aa81ef | 991 | final String newLine = System.getProperty("line.separator"); //$NON-NLS-1$ |
09e86496 | 992 | final StringBuffer buffer = new StringBuffer(); |
720d67cb PT |
993 | int selectionBeginBucket = Math.min(fScaledData.fSelectionBeginBucket, fScaledData.fSelectionEndBucket); |
994 | int selectionEndBucket = Math.max(fScaledData.fSelectionBeginBucket, fScaledData.fSelectionEndBucket); | |
995 | if (selectionBeginBucket <= index && index <= selectionEndBucket && fSelectionBegin != fSelectionEnd) { | |
996 | TmfTimestampDelta delta = new TmfTimestampDelta(Math.abs(fSelectionEnd - fSelectionBegin), ITmfTimestamp.NANOSECOND_SCALE); | |
997 | buffer.append(NLS.bind(Messages.Histogram_selectionSpanToolTip, delta.toString())); | |
998 | buffer.append(newLine); | |
999 | } | |
1000 | buffer.append(NLS.bind(Messages.Histogram_bucketRangeToolTip, | |
1001 | new TmfTimestamp(startTime, ITmfTimestamp.NANOSECOND_SCALE).toString(), | |
1002 | new TmfTimestamp(endTime, ITmfTimestamp.NANOSECOND_SCALE).toString())); | |
95aa81ef | 1003 | buffer.append(newLine); |
720d67cb | 1004 | buffer.append(NLS.bind(Messages.Histogram_eventCountToolTip, nbEvents)); |
95aa81ef JCK |
1005 | if (!HistogramScaledData.hideLostEvents) { |
1006 | final int nbLostEvents = (index >= 0) ? fScaledData.fLostEventsData[index] : 0; | |
1007 | buffer.append(newLine); | |
720d67cb | 1008 | buffer.append(NLS.bind(Messages.Histogram_lostEventCountToolTip, nbLostEvents)); |
95aa81ef | 1009 | } |
c392540b FC |
1010 | return buffer.toString(); |
1011 | } | |
1012 | ||
1013 | // ------------------------------------------------------------------------ | |
1014 | // ControlListener | |
1015 | // ------------------------------------------------------------------------ | |
1016 | ||
1017 | @Override | |
09e86496 | 1018 | public void controlMoved(final ControlEvent event) { |
fbd124dd | 1019 | fDataModel.complete(); |
c392540b FC |
1020 | } |
1021 | ||
1022 | @Override | |
09e86496 | 1023 | public void controlResized(final ControlEvent event) { |
fbd124dd | 1024 | fDataModel.complete(); |
c392540b | 1025 | } |
f8177ba2 FC |
1026 | |
1027 | // ------------------------------------------------------------------------ | |
1028 | // Signal Handlers | |
1029 | // ------------------------------------------------------------------------ | |
1030 | ||
1031 | /** | |
1032 | * Format the timestamp and update the display | |
1033 | * | |
95aa81ef JCK |
1034 | * @param signal |
1035 | * the incoming signal | |
f8177ba2 FC |
1036 | * @since 2.0 |
1037 | */ | |
1038 | @TmfSignalHandler | |
1039 | public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) { | |
d418423b | 1040 | updateRangeTextControls(); |
f8177ba2 | 1041 | |
da7bdcbc | 1042 | fComposite.layout(); |
f8177ba2 FC |
1043 | } |
1044 | ||
c392540b | 1045 | } |