Commit | Line | Data |
---|---|---|
c392540b | 1 | /******************************************************************************* |
c8422608 | 2 | * Copyright (c) 2011, 2013 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 |
c392540b FC |
14 | *******************************************************************************/ |
15 | ||
e0752744 | 16 | package org.eclipse.linuxtools.tmf.ui.views.histogram; |
c392540b | 17 | |
f8177ba2 FC |
18 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler; |
19 | import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager; | |
20 | import org.eclipse.linuxtools.tmf.core.signal.TmfTimestampFormatUpdateSignal; | |
3bd46eef AM |
21 | import org.eclipse.linuxtools.tmf.core.timestamp.ITmfTimestamp; |
22 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestamp; | |
23 | import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestampFormat; | |
c392540b FC |
24 | import org.eclipse.linuxtools.tmf.ui.views.TmfView; |
25 | import org.eclipse.swt.SWT; | |
26 | import org.eclipse.swt.events.ControlEvent; | |
27 | import org.eclipse.swt.events.ControlListener; | |
65cdf787 PT |
28 | import org.eclipse.swt.events.FocusAdapter; |
29 | import org.eclipse.swt.events.FocusEvent; | |
30 | import org.eclipse.swt.events.FocusListener; | |
c392540b FC |
31 | import org.eclipse.swt.events.KeyEvent; |
32 | import org.eclipse.swt.events.KeyListener; | |
33 | import org.eclipse.swt.events.MouseEvent; | |
34 | import org.eclipse.swt.events.MouseListener; | |
35 | import org.eclipse.swt.events.MouseTrackListener; | |
65cdf787 | 36 | import org.eclipse.swt.events.MouseWheelListener; |
c392540b FC |
37 | import org.eclipse.swt.events.PaintEvent; |
38 | import org.eclipse.swt.events.PaintListener; | |
39 | import org.eclipse.swt.graphics.Color; | |
40 | import org.eclipse.swt.graphics.Font; | |
41 | import org.eclipse.swt.graphics.FontData; | |
42 | import org.eclipse.swt.graphics.GC; | |
43 | import org.eclipse.swt.graphics.Image; | |
e60df94a | 44 | import org.eclipse.swt.layout.FillLayout; |
c392540b FC |
45 | import org.eclipse.swt.layout.GridData; |
46 | import org.eclipse.swt.layout.GridLayout; | |
47 | import org.eclipse.swt.widgets.Canvas; | |
48 | import org.eclipse.swt.widgets.Composite; | |
49 | import org.eclipse.swt.widgets.Display; | |
65cdf787 | 50 | import org.eclipse.swt.widgets.Label; |
c392540b FC |
51 | import org.eclipse.swt.widgets.Text; |
52 | ||
53 | /** | |
b544077e | 54 | * Re-usable histogram widget. |
20ff3b75 | 55 | * |
b544077e | 56 | * It has the following features: |
c392540b FC |
57 | * <ul> |
58 | * <li>Y-axis labels displaying min/max count values | |
59 | * <li>X-axis labels displaying time range | |
60 | * <li>a histogram displaying the distribution of values over time (note that | |
61 | * the histogram might not necessarily fill the whole canvas) | |
62 | * </ul> | |
63 | * The widget also has 2 'markers' to identify: | |
64 | * <ul> | |
65 | * <li>a red dashed line over the bar that contains the currently selected event | |
66 | * <li>a dark red dashed line that delimits the right end of the histogram (if | |
67 | * it doesn't fill the canvas) | |
68 | * </ul> | |
69 | * Clicking on the histogram will select the current event at the mouse | |
70 | * location. | |
71 | * <p> | |
72 | * Once the histogram is selected, there is some limited keyboard support: | |
73 | * <ul> | |
74 | * <li>Home: go to the first histogram bar | |
75 | * <li>End: go to the last histogram bar | |
76 | * <li>Left: go to the previous histogram | |
77 | * <li>Right: go to the next histogram bar | |
78 | * </ul> | |
79 | * Finally, when the mouse hovers over the histogram, a tool tip showing the | |
80 | * following information about the corresponding histogram bar time range: | |
81 | * <ul> | |
82 | * <li>start of the time range | |
83 | * <li>end of the time range | |
84 | * <li>number of events in that time range | |
85 | * </ul> | |
20ff3b75 | 86 | * |
f8177ba2 | 87 | * @version 1.1 |
b544077e | 88 | * @author Francois Chouinard |
c392540b | 89 | */ |
fbd124dd | 90 | public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseTrackListener, IHistogramModelListener { |
c392540b FC |
91 | |
92 | // ------------------------------------------------------------------------ | |
93 | // Constants | |
94 | // ------------------------------------------------------------------------ | |
95 | ||
c392540b FC |
96 | // Histogram colors |
97 | private final Color fBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); | |
0fcf3b09 PT |
98 | private final Color fSelectionForegroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE); |
99 | private final Color fSelectionBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); | |
c392540b FC |
100 | private final Color fLastEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED); |
101 | private final Color fHistoBarColor = new Color(Display.getDefault(), 74, 112, 139); | |
95aa81ef | 102 | private final Color fLostEventColor = new Color(Display.getCurrent(), 208, 62, 120); |
c392540b | 103 | |
c392540b FC |
104 | // ------------------------------------------------------------------------ |
105 | // Attributes | |
106 | // ------------------------------------------------------------------------ | |
107 | ||
b544077e BH |
108 | /** |
109 | * The parent TMF view. | |
110 | */ | |
c392540b FC |
111 | protected TmfView fParentView; |
112 | ||
da7bdcbc | 113 | private Composite fComposite; |
f8177ba2 FC |
114 | private Font fFont; |
115 | ||
c392540b FC |
116 | // Histogram text fields |
117 | private Text fMaxNbEventsText; | |
118 | private Text fMinNbEventsText; | |
119 | private Text fTimeRangeStartText; | |
120 | private Text fTimeRangeEndText; | |
121 | ||
b544077e | 122 | /** |
95aa81ef | 123 | * Histogram drawing area |
b544077e | 124 | */ |
c392540b | 125 | protected Canvas fCanvas; |
f8177ba2 | 126 | |
b544077e | 127 | /** |
95aa81ef | 128 | * The histogram data model. |
b544077e | 129 | */ |
c392540b | 130 | protected final HistogramDataModel fDataModel; |
f8177ba2 | 131 | |
b544077e | 132 | /** |
20ff3b75 | 133 | * The histogram data model scaled to current resolution and screen width. |
b544077e | 134 | */ |
c392540b FC |
135 | protected HistogramScaledData fScaledData; |
136 | ||
f8177ba2 FC |
137 | /** |
138 | * The current event value | |
139 | */ | |
140 | protected long fCurrentEventTime = 0L; | |
c392540b | 141 | |
0fcf3b09 PT |
142 | /** |
143 | * The current selection begin time | |
144 | */ | |
145 | private long fSelectionBegin = 0L; | |
146 | ||
147 | /** | |
148 | * The current selection end time | |
149 | */ | |
150 | private long fSelectionEnd = 0L; | |
151 | ||
c392540b FC |
152 | // ------------------------------------------------------------------------ |
153 | // Construction | |
154 | // ------------------------------------------------------------------------ | |
155 | ||
b544077e | 156 | /** |
f8177ba2 | 157 | * Full constructor. |
20ff3b75 | 158 | * |
b544077e BH |
159 | * @param view A reference to the parent TMF view. |
160 | * @param parent A parent composite | |
161 | */ | |
09e86496 | 162 | public Histogram(final TmfView view, final Composite parent) { |
c392540b FC |
163 | fParentView = view; |
164 | ||
da7bdcbc | 165 | fComposite = createWidget(parent); |
c392540b | 166 | fDataModel = new HistogramDataModel(); |
fbd124dd | 167 | fDataModel.addHistogramListener(this); |
c392540b FC |
168 | clear(); |
169 | ||
170 | fCanvas.addControlListener(this); | |
171 | fCanvas.addPaintListener(this); | |
172 | fCanvas.addKeyListener(this); | |
173 | fCanvas.addMouseListener(this); | |
174 | fCanvas.addMouseTrackListener(this); | |
f8177ba2 FC |
175 | |
176 | TmfSignalManager.register(this); | |
c392540b FC |
177 | } |
178 | ||
b544077e | 179 | /** |
f8177ba2 | 180 | * Dispose resources and unregisters listeners. |
b544077e | 181 | */ |
c392540b | 182 | public void dispose() { |
f8177ba2 FC |
183 | TmfSignalManager.deregister(this); |
184 | ||
c392540b | 185 | fHistoBarColor.dispose(); |
95aa81ef | 186 | fLastEventColor.dispose(); |
fbd124dd | 187 | fDataModel.removeHistogramListener(this); |
c392540b FC |
188 | } |
189 | ||
da7bdcbc | 190 | private Composite createWidget(final Composite parent) { |
c392540b | 191 | |
0301dc43 | 192 | final Color labelColor = parent.getBackground(); |
f8177ba2 | 193 | fFont = adjustFont(parent); |
c392540b FC |
194 | |
195 | final int initalWidth = 10; | |
196 | ||
197 | // -------------------------------------------------------------------- | |
198 | // Define the histogram | |
199 | // -------------------------------------------------------------------- | |
200 | ||
09e86496 | 201 | final GridLayout gridLayout = new GridLayout(); |
c392540b FC |
202 | gridLayout.numColumns = 3; |
203 | gridLayout.marginHeight = 0; | |
204 | gridLayout.marginWidth = 0; | |
205 | gridLayout.marginTop = 0; | |
206 | gridLayout.horizontalSpacing = 0; | |
207 | gridLayout.verticalSpacing = 0; | |
208 | gridLayout.marginLeft = 0; | |
209 | gridLayout.marginRight = 0; | |
09e86496 | 210 | final Composite composite = new Composite(parent, SWT.FILL); |
c392540b FC |
211 | composite.setLayout(gridLayout); |
212 | ||
213 | // Use all the horizontal space | |
214 | GridData gridData = new GridData(); | |
215 | gridData.horizontalAlignment = SWT.FILL; | |
216 | gridData.verticalAlignment = SWT.FILL; | |
217 | gridData.grabExcessHorizontalSpace = true; | |
218 | composite.setLayoutData(gridData); | |
219 | ||
220 | // Y-axis max event | |
221 | gridData = new GridData(); | |
222 | gridData.horizontalAlignment = SWT.RIGHT; | |
223 | gridData.verticalAlignment = SWT.TOP; | |
224 | fMaxNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT); | |
225 | fMaxNbEventsText.setFont(fFont); | |
226 | fMaxNbEventsText.setBackground(labelColor); | |
227 | fMaxNbEventsText.setEditable(false); | |
228 | fMaxNbEventsText.setText("0"); //$NON-NLS-1$ | |
229 | fMaxNbEventsText.setLayoutData(gridData); | |
230 | ||
231 | // Histogram itself | |
e60df94a | 232 | Composite canvasComposite = new Composite(composite, SWT.BORDER); |
c392540b FC |
233 | gridData = new GridData(); |
234 | gridData.horizontalSpan = 2; | |
235 | gridData.verticalSpan = 2; | |
236 | gridData.horizontalAlignment = SWT.FILL; | |
237 | gridData.verticalAlignment = SWT.FILL; | |
238 | gridData.grabExcessHorizontalSpace = true; | |
e60df94a PT |
239 | canvasComposite.setLayoutData(gridData); |
240 | canvasComposite.setLayout(new FillLayout()); | |
241 | fCanvas = new Canvas(canvasComposite, SWT.DOUBLE_BUFFERED); | |
c392540b FC |
242 | |
243 | // Y-axis min event (always 0...) | |
244 | gridData = new GridData(); | |
245 | gridData.horizontalAlignment = SWT.RIGHT; | |
246 | gridData.verticalAlignment = SWT.BOTTOM; | |
247 | fMinNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT); | |
248 | fMinNbEventsText.setFont(fFont); | |
249 | fMinNbEventsText.setBackground(labelColor); | |
250 | fMinNbEventsText.setEditable(false); | |
251 | fMinNbEventsText.setText("0"); //$NON-NLS-1$ | |
252 | fMinNbEventsText.setLayoutData(gridData); | |
253 | ||
254 | // Dummy cell | |
255 | gridData = new GridData(initalWidth, SWT.DEFAULT); | |
256 | gridData.horizontalAlignment = SWT.RIGHT; | |
257 | gridData.verticalAlignment = SWT.BOTTOM; | |
65cdf787 PT |
258 | final Label dummyLabel = new Label(composite, SWT.NONE); |
259 | dummyLabel.setLayoutData(gridData); | |
c392540b FC |
260 | |
261 | // Window range start time | |
262 | gridData = new GridData(); | |
263 | gridData.horizontalAlignment = SWT.LEFT; | |
264 | gridData.verticalAlignment = SWT.BOTTOM; | |
265 | fTimeRangeStartText = new Text(composite, SWT.READ_ONLY); | |
266 | fTimeRangeStartText.setFont(fFont); | |
267 | fTimeRangeStartText.setBackground(labelColor); | |
c392540b FC |
268 | fTimeRangeStartText.setLayoutData(gridData); |
269 | ||
270 | // Window range end time | |
271 | gridData = new GridData(); | |
272 | gridData.horizontalAlignment = SWT.RIGHT; | |
273 | gridData.verticalAlignment = SWT.BOTTOM; | |
274 | fTimeRangeEndText = new Text(composite, SWT.READ_ONLY); | |
275 | fTimeRangeEndText.setFont(fFont); | |
276 | fTimeRangeEndText.setBackground(labelColor); | |
c392540b | 277 | fTimeRangeEndText.setLayoutData(gridData); |
da7bdcbc | 278 | |
65cdf787 PT |
279 | FocusListener listener = new FocusAdapter() { |
280 | @Override | |
281 | public void focusGained(FocusEvent e) { | |
282 | fCanvas.setFocus(); | |
283 | } | |
284 | }; | |
285 | fMaxNbEventsText.addFocusListener(listener); | |
286 | fMinNbEventsText.addFocusListener(listener); | |
287 | fTimeRangeStartText.addFocusListener(listener); | |
288 | fTimeRangeEndText.addFocusListener(listener); | |
289 | ||
da7bdcbc | 290 | return composite; |
c392540b FC |
291 | } |
292 | ||
abbdd66a | 293 | private static Font adjustFont(final Composite composite) { |
c392540b | 294 | // Reduce font size for a more pleasing rendering |
09e86496 FC |
295 | final int fontSizeAdjustment = -2; |
296 | final Font font = composite.getFont(); | |
297 | final FontData fontData = font.getFontData()[0]; | |
c392540b FC |
298 | return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle()); |
299 | } | |
300 | ||
301 | // ------------------------------------------------------------------------ | |
302 | // Accessors | |
303 | // ------------------------------------------------------------------------ | |
304 | ||
b544077e | 305 | /** |
f8177ba2 | 306 | * Returns the start time (equal first bucket time) |
b544077e BH |
307 | * @return the start time. |
308 | */ | |
c392540b | 309 | public long getStartTime() { |
fbd124dd | 310 | return fDataModel.getFirstBucketTime(); |
c392540b FC |
311 | } |
312 | ||
b544077e BH |
313 | /** |
314 | * Returns the end time. | |
315 | * @return the end time. | |
316 | */ | |
c392540b FC |
317 | public long getEndTime() { |
318 | return fDataModel.getEndTime(); | |
319 | } | |
320 | ||
b544077e BH |
321 | /** |
322 | * Returns the time limit (end of last bucket) | |
323 | * @return the time limit. | |
324 | */ | |
c392540b FC |
325 | public long getTimeLimit() { |
326 | return fDataModel.getTimeLimit(); | |
327 | } | |
09e86496 | 328 | |
20ff3b75 AM |
329 | /** |
330 | * Returns a data model reference. | |
b544077e BH |
331 | * @return data model. |
332 | */ | |
fbd124dd BH |
333 | public HistogramDataModel getDataModel() { |
334 | return fDataModel; | |
335 | } | |
c392540b | 336 | |
95aa81ef JCK |
337 | /** |
338 | * Returns the text control for the maximum of events in one bar | |
339 | * | |
340 | * @return the text control | |
46a59db7 | 341 | * @since 2.2 |
95aa81ef JCK |
342 | */ |
343 | public Text getMaxNbEventsText() { | |
344 | return fMaxNbEventsText; | |
345 | } | |
346 | ||
c392540b FC |
347 | // ------------------------------------------------------------------------ |
348 | // Operations | |
349 | // ------------------------------------------------------------------------ | |
b544077e | 350 | /** |
20ff3b75 | 351 | * Updates the time range. |
b544077e BH |
352 | * @param startTime A start time |
353 | * @param endTime A end time. | |
354 | */ | |
c392540b FC |
355 | public abstract void updateTimeRange(long startTime, long endTime); |
356 | ||
357 | /** | |
358 | * Clear the histogram and reset the data | |
359 | */ | |
360 | public void clear() { | |
361 | fDataModel.clear(); | |
dcb3cda5 BH |
362 | synchronized (fDataModel) { |
363 | fScaledData = null; | |
364 | } | |
c392540b FC |
365 | } |
366 | ||
367 | /** | |
368 | * Increase the histogram bucket corresponding to [timestamp] | |
20ff3b75 | 369 | * |
95aa81ef JCK |
370 | * @param eventCount The new event count |
371 | * @param timestamp The latest timestamp | |
c392540b | 372 | */ |
09e86496 | 373 | public void countEvent(final long eventCount, final long timestamp) { |
fbd124dd | 374 | fDataModel.countEvent(eventCount, timestamp); |
c392540b FC |
375 | } |
376 | ||
377 | /** | |
378 | * Sets the current event time and refresh the display | |
20ff3b75 | 379 | * |
95aa81ef | 380 | * @param timestamp The time of the current event |
0fcf3b09 | 381 | * @deprecated As of 2.1, use {@link #setSelection(long, long)} |
c392540b | 382 | */ |
0fcf3b09 | 383 | @Deprecated |
09e86496 | 384 | public void setCurrentEvent(final long timestamp) { |
0fcf3b09 PT |
385 | fSelectionBegin = (timestamp > 0) ? timestamp : 0; |
386 | fSelectionEnd = (timestamp > 0) ? timestamp : 0; | |
387 | fDataModel.setSelectionNotifyListeners(timestamp, timestamp); | |
388 | } | |
389 | ||
390 | /** | |
391 | * Sets the current selection time range and refresh the display | |
392 | * | |
95aa81ef JCK |
393 | * @param beginTime The begin time of the current selection |
394 | * @param endTime The end time of the current selection | |
0fcf3b09 PT |
395 | * @since 2.1 |
396 | */ | |
397 | public void setSelection(final long beginTime, final long endTime) { | |
398 | fSelectionBegin = (beginTime > 0) ? beginTime : 0; | |
399 | fSelectionEnd = (endTime > 0) ? endTime : 0; | |
400 | fDataModel.setSelectionNotifyListeners(beginTime, endTime); | |
c392540b FC |
401 | } |
402 | ||
403 | /** | |
404 | * Computes the timestamp of the bucket at [offset] | |
20ff3b75 | 405 | * |
c392540b FC |
406 | * @param offset offset from the left on the histogram |
407 | * @return the start timestamp of the corresponding bucket | |
408 | */ | |
09e86496 | 409 | public synchronized long getTimestamp(final int offset) { |
c392540b FC |
410 | assert offset > 0 && offset < fScaledData.fWidth; |
411 | try { | |
fbd124dd | 412 | return fDataModel.getFirstBucketTime() + fScaledData.fBucketDuration * offset; |
09e86496 | 413 | } catch (final Exception e) { |
c392540b FC |
414 | return 0; // TODO: Fix that racing condition (NPE) |
415 | } | |
416 | } | |
417 | ||
418 | /** | |
419 | * Computes the offset of the timestamp in the histogram | |
20ff3b75 | 420 | * |
c392540b FC |
421 | * @param timestamp the timestamp |
422 | * @return the offset of the corresponding bucket (-1 if invalid) | |
423 | */ | |
09e86496 | 424 | public synchronized int getOffset(final long timestamp) { |
20ff3b75 | 425 | if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) { |
c392540b | 426 | return -1; |
20ff3b75 | 427 | } |
fbd124dd | 428 | return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration); |
c392540b FC |
429 | } |
430 | ||
431 | /** | |
432 | * Move the currently selected bar cursor to a non-empty bucket. | |
20ff3b75 | 433 | * |
c392540b FC |
434 | * @param keyCode the SWT key code |
435 | */ | |
09e86496 | 436 | protected void moveCursor(final int keyCode) { |
c392540b | 437 | |
c392540b FC |
438 | int index; |
439 | switch (keyCode) { | |
440 | ||
95aa81ef JCK |
441 | case SWT.HOME: |
442 | index = 0; | |
443 | while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0) { | |
444 | index++; | |
445 | } | |
446 | if (index < fScaledData.fLastBucket) { | |
447 | fScaledData.fSelectionBeginBucket = index; | |
448 | } | |
449 | break; | |
c392540b | 450 | |
95aa81ef JCK |
451 | case SWT.ARROW_RIGHT: |
452 | index = Math.max(0, fScaledData.fSelectionBeginBucket + 1); | |
453 | while (index < fScaledData.fWidth && fScaledData.fData[index] == 0) { | |
454 | index++; | |
455 | } | |
456 | if (index < fScaledData.fLastBucket) { | |
457 | fScaledData.fSelectionBeginBucket = index; | |
458 | } | |
459 | break; | |
c392540b | 460 | |
95aa81ef JCK |
461 | case SWT.END: |
462 | index = fScaledData.fLastBucket; | |
463 | while (index >= 0 && fScaledData.fData[index] == 0) { | |
464 | index--; | |
465 | } | |
466 | if (index >= 0) { | |
467 | fScaledData.fSelectionBeginBucket = index; | |
468 | } | |
469 | break; | |
c392540b | 470 | |
95aa81ef JCK |
471 | case SWT.ARROW_LEFT: |
472 | index = Math.min(fScaledData.fLastBucket - 1, fScaledData.fSelectionBeginBucket - 1); | |
473 | while (index >= 0 && fScaledData.fData[index] == 0) { | |
474 | index--; | |
475 | } | |
476 | if (index >= 0) { | |
477 | fScaledData.fSelectionBeginBucket = index; | |
478 | } | |
479 | break; | |
c392540b | 480 | |
95aa81ef JCK |
481 | default: |
482 | return; | |
c392540b FC |
483 | } |
484 | ||
0fcf3b09 PT |
485 | fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket; |
486 | fSelectionBegin = getTimestamp(fScaledData.fSelectionBeginBucket); | |
487 | fSelectionEnd = fSelectionBegin; | |
488 | updateSelectionTime(); | |
c392540b FC |
489 | } |
490 | ||
491 | /** | |
492 | * Refresh the histogram display | |
493 | */ | |
fbd124dd BH |
494 | @Override |
495 | public void modelUpdated() { | |
20ff3b75 | 496 | if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) { |
c392540b FC |
497 | fCanvas.getDisplay().asyncExec(new Runnable() { |
498 | @Override | |
499 | public void run() { | |
500 | if (!fCanvas.isDisposed()) { | |
501 | // Retrieve and normalize the data | |
09e86496 FC |
502 | final int canvasWidth = fCanvas.getBounds().width; |
503 | final int canvasHeight = fCanvas.getBounds().height; | |
20ff3b75 | 504 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
40890fec | 505 | return; |
20ff3b75 | 506 | } |
0fcf3b09 | 507 | fDataModel.setSelection(fSelectionBegin, fSelectionEnd); |
f8177ba2 | 508 | fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, 1); |
95aa81ef | 509 | synchronized (fDataModel) { |
0316808c FC |
510 | if (fScaledData != null) { |
511 | fCanvas.redraw(); | |
da7bdcbc | 512 | if (fDataModel.getNbEvents() != 0) { |
95aa81ef JCK |
513 | // Display histogram and update X-,Y-axis |
514 | // labels | |
da7bdcbc PT |
515 | fTimeRangeStartText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getFirstBucketTime())); |
516 | fTimeRangeEndText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime())); | |
517 | } else { | |
518 | fTimeRangeStartText.setText(""); //$NON-NLS-1$ | |
519 | fTimeRangeEndText.setText(""); //$NON-NLS-1$ | |
520 | } | |
95aa81ef JCK |
521 | long maxNbEvents = HistogramScaledData.hideLostEvents ? fScaledData.fMaxValue : fScaledData.fMaxCombinedValue; |
522 | fMaxNbEventsText.setText(Long.toString(maxNbEvents)); | |
0316808c FC |
523 | // The Y-axis area might need to be re-sized |
524 | fMaxNbEventsText.getParent().layout(); | |
525 | } | |
09e86496 | 526 | } |
c392540b FC |
527 | } |
528 | } | |
529 | }); | |
20ff3b75 | 530 | } |
c392540b FC |
531 | } |
532 | ||
65cdf787 PT |
533 | /** |
534 | * Add a mouse wheel listener to the histogram | |
535 | * @param listener the mouse wheel listener | |
536 | * @since 2.0 | |
537 | */ | |
538 | public void addMouseWheelListener(MouseWheelListener listener) { | |
539 | fCanvas.addMouseWheelListener(listener); | |
540 | } | |
541 | ||
542 | /** | |
543 | * Remove a mouse wheel listener from the histogram | |
544 | * @param listener the mouse wheel listener | |
545 | * @since 2.0 | |
546 | */ | |
547 | public void removeMouseWheelListener(MouseWheelListener listener) { | |
548 | fCanvas.removeMouseWheelListener(listener); | |
549 | } | |
550 | ||
c392540b FC |
551 | // ------------------------------------------------------------------------ |
552 | // Helper functions | |
553 | // ------------------------------------------------------------------------ | |
554 | ||
0fcf3b09 PT |
555 | private void updateSelectionTime() { |
556 | ((HistogramView) fParentView).updateSelectionTime(fSelectionBegin, fSelectionEnd); | |
c392540b FC |
557 | } |
558 | ||
559 | // ------------------------------------------------------------------------ | |
560 | // PaintListener | |
561 | // ------------------------------------------------------------------------ | |
b544077e BH |
562 | /** |
563 | * Image key string for the canvas. | |
564 | */ | |
c392540b FC |
565 | protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$ |
566 | ||
567 | @Override | |
09e86496 | 568 | public void paintControl(final PaintEvent event) { |
c392540b FC |
569 | |
570 | // Get the geometry | |
09e86496 FC |
571 | final int canvasWidth = fCanvas.getBounds().width; |
572 | final int canvasHeight = fCanvas.getBounds().height; | |
c392540b FC |
573 | |
574 | // Make sure we have something to draw upon | |
20ff3b75 | 575 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
c392540b | 576 | return; |
20ff3b75 | 577 | } |
c392540b FC |
578 | |
579 | // Retrieve image; re-create only if necessary | |
580 | Image image = (Image) fCanvas.getData(IMAGE_KEY); | |
581 | if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) { | |
582 | image = new Image(event.display, canvasWidth, canvasHeight); | |
583 | fCanvas.setData(IMAGE_KEY, image); | |
584 | } | |
585 | ||
586 | // Draw the histogram on its canvas | |
09e86496 | 587 | final GC imageGC = new GC(image); |
c392540b FC |
588 | formatImage(imageGC, image); |
589 | event.gc.drawImage(image, 0, 0); | |
590 | imageGC.dispose(); | |
591 | } | |
592 | ||
09e86496 | 593 | private void formatImage(final GC imageGC, final Image image) { |
c392540b | 594 | |
20ff3b75 | 595 | if (fScaledData == null) { |
c392540b | 596 | return; |
20ff3b75 | 597 | } |
c392540b | 598 | |
09e86496 | 599 | final HistogramScaledData scaledData = new HistogramScaledData(fScaledData); |
c392540b FC |
600 | |
601 | try { | |
602 | // Get drawing boundaries | |
09e86496 FC |
603 | final int width = image.getBounds().width; |
604 | final int height = image.getBounds().height; | |
c392540b FC |
605 | |
606 | // Clear the drawing area | |
607 | imageGC.setBackground(fBackgroundColor); | |
608 | imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1); | |
609 | ||
610 | // Draw the histogram bars | |
09e86496 | 611 | final int limit = width < scaledData.fWidth ? width : scaledData.fWidth; |
95aa81ef | 612 | double factor = HistogramScaledData.hideLostEvents ? scaledData.fScalingFactor : scaledData.fScalingFactorCombined; |
e6953950 | 613 | for (int i = 0; i < limit; i++) { |
95aa81ef JCK |
614 | imageGC.setForeground(fHistoBarColor); |
615 | final int value = (int) Math.ceil(scaledData.fData[i] * factor); | |
616 | imageGC.drawLine(i, height - value, i, height); | |
617 | ||
618 | if (!HistogramScaledData.hideLostEvents) { | |
619 | imageGC.setForeground(fLostEventColor); | |
620 | final int lostEventValue = (int) Math.ceil(scaledData.fLostEventsData[i] * factor); | |
621 | if (lostEventValue != 0) { | |
622 | if (lostEventValue == 1) { | |
623 | // in linux, a line from x to x is not drawn, in windows it is. | |
624 | imageGC.drawPoint(i, height - value - 1); | |
625 | } else { | |
626 | // drawing a line is inclusive, so we need to remove 1 from the destination to have the correct length | |
627 | imageGC.drawLine(i, height - value - lostEventValue, i, height - value - 1); | |
628 | } | |
629 | } | |
630 | } | |
c392540b FC |
631 | } |
632 | ||
c392540b FC |
633 | // Add a dashed line as a delimiter (at the right of the last bar) |
634 | int lastEventIndex = limit - 1; | |
20ff3b75 | 635 | while (lastEventIndex >= 0 && scaledData.fData[lastEventIndex] == 0) { |
c392540b | 636 | lastEventIndex--; |
20ff3b75 | 637 | } |
c392540b FC |
638 | lastEventIndex += (lastEventIndex < limit - 1) ? 1 : 0; |
639 | drawDelimiter(imageGC, fLastEventColor, height, lastEventIndex); | |
0fcf3b09 PT |
640 | |
641 | // Draw the selection bars | |
642 | int alpha = imageGC.getAlpha(); | |
643 | imageGC.setAlpha(100); | |
644 | imageGC.setForeground(fSelectionForegroundColor); | |
645 | imageGC.setBackground(fSelectionBackgroundColor); | |
646 | final int beginBucket = scaledData.fSelectionBeginBucket; | |
647 | if (beginBucket >= 0 && beginBucket < limit) { | |
648 | imageGC.drawLine(beginBucket, 0, beginBucket, height); | |
649 | } | |
650 | final int endBucket = Math.min(lastEventIndex, scaledData.fSelectionEndBucket); | |
651 | if (endBucket >= 0 && endBucket < limit && endBucket != beginBucket) { | |
652 | imageGC.drawLine(endBucket, 0, endBucket, height); | |
653 | } | |
654 | if (endBucket - beginBucket > 1) { | |
655 | imageGC.fillRectangle(beginBucket + 1, 0, endBucket - beginBucket - 1, height); | |
656 | } | |
657 | imageGC.setAlpha(alpha); | |
09e86496 | 658 | } catch (final Exception e) { |
c392540b FC |
659 | // Do nothing |
660 | } | |
661 | } | |
662 | ||
abbdd66a AM |
663 | private static void drawDelimiter(final GC imageGC, final Color color, |
664 | final int height, final int index) { | |
c392540b | 665 | imageGC.setBackground(color); |
09e86496 | 666 | final int dash = height / 4; |
c392540b FC |
667 | imageGC.fillRectangle(index, 0 * dash, 1, dash - 1); |
668 | imageGC.fillRectangle(index, 1 * dash, 1, dash - 1); | |
669 | imageGC.fillRectangle(index, 2 * dash, 1, dash - 1); | |
670 | imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash); | |
671 | } | |
672 | ||
673 | // ------------------------------------------------------------------------ | |
674 | // KeyListener | |
675 | // ------------------------------------------------------------------------ | |
676 | ||
677 | @Override | |
09e86496 | 678 | public void keyPressed(final KeyEvent event) { |
c392540b FC |
679 | moveCursor(event.keyCode); |
680 | } | |
681 | ||
682 | @Override | |
09e86496 | 683 | public void keyReleased(final KeyEvent event) { |
c392540b FC |
684 | } |
685 | ||
686 | // ------------------------------------------------------------------------ | |
687 | // MouseListener | |
688 | // ------------------------------------------------------------------------ | |
689 | ||
690 | @Override | |
09e86496 | 691 | public void mouseDoubleClick(final MouseEvent event) { |
c392540b FC |
692 | } |
693 | ||
694 | @Override | |
09e86496 | 695 | public void mouseDown(final MouseEvent event) { |
c392540b | 696 | if (fDataModel.getNbEvents() > 0 && fScaledData.fLastBucket >= event.x) { |
0fcf3b09 PT |
697 | if ((event.stateMask & SWT.MODIFIER_MASK) == 0) { |
698 | fScaledData.fSelectionBeginBucket = event.x; | |
699 | fScaledData.fSelectionEndBucket = event.x; | |
700 | fSelectionBegin = getTimestamp(event.x); | |
701 | fSelectionEnd = fSelectionBegin; | |
702 | } else if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) { | |
703 | if (fSelectionBegin == fSelectionEnd) { | |
704 | if (event.x < fScaledData.fSelectionBeginBucket) { | |
705 | fScaledData.fSelectionBeginBucket = event.x; | |
706 | fSelectionBegin = getTimestamp(event.x); | |
707 | } else { | |
708 | fScaledData.fSelectionEndBucket = event.x; | |
709 | fSelectionEnd = getTimestamp(event.x); | |
710 | } | |
711 | } else if (Math.abs(event.x - fScaledData.fSelectionBeginBucket) <= Math.abs(event.x - fScaledData.fSelectionEndBucket)) { | |
712 | fScaledData.fSelectionBeginBucket = event.x; | |
713 | fSelectionBegin = getTimestamp(event.x); | |
714 | } else { | |
715 | fScaledData.fSelectionEndBucket = event.x; | |
716 | fSelectionEnd = getTimestamp(event.x); | |
717 | } | |
718 | } | |
719 | updateSelectionTime(); | |
c392540b FC |
720 | } |
721 | } | |
722 | ||
723 | @Override | |
09e86496 | 724 | public void mouseUp(final MouseEvent event) { |
c392540b FC |
725 | } |
726 | ||
727 | // ------------------------------------------------------------------------ | |
728 | // MouseTrackListener | |
729 | // ------------------------------------------------------------------------ | |
730 | ||
731 | @Override | |
09e86496 | 732 | public void mouseEnter(final MouseEvent event) { |
c392540b FC |
733 | } |
734 | ||
735 | @Override | |
09e86496 | 736 | public void mouseExit(final MouseEvent event) { |
c392540b FC |
737 | } |
738 | ||
739 | @Override | |
09e86496 | 740 | public void mouseHover(final MouseEvent event) { |
74237cc3 | 741 | if (fDataModel.getNbEvents() > 0 && fScaledData != null && fScaledData.fLastBucket >= event.x) { |
09e86496 | 742 | final String tooltip = formatToolTipLabel(event.x); |
c392540b | 743 | fCanvas.setToolTipText(tooltip); |
1be49d83 PT |
744 | } else { |
745 | fCanvas.setToolTipText(null); | |
c392540b FC |
746 | } |
747 | } | |
748 | ||
09e86496 | 749 | private String formatToolTipLabel(final int index) { |
466857f6 | 750 | long startTime = fScaledData.getBucketStartTime(index); |
09e86496 | 751 | // negative values are possible if time values came into the model in decreasing order |
20ff3b75 | 752 | if (startTime < 0) { |
fbd124dd | 753 | startTime = 0; |
20ff3b75 | 754 | } |
466857f6 | 755 | final long endTime = fScaledData.getBucketEndTime(index); |
09e86496 | 756 | final int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0; |
95aa81ef | 757 | final String newLine = System.getProperty("line.separator"); //$NON-NLS-1$ |
09e86496 | 758 | final StringBuffer buffer = new StringBuffer(); |
c392540b | 759 | buffer.append("Range = ["); //$NON-NLS-1$ |
f8177ba2 | 760 | buffer.append(new TmfTimestamp(startTime, ITmfTimestamp.NANOSECOND_SCALE).toString()); |
95aa81ef | 761 | buffer.append(','); |
f8177ba2 | 762 | buffer.append(new TmfTimestamp(endTime, ITmfTimestamp.NANOSECOND_SCALE).toString()); |
95aa81ef JCK |
763 | buffer.append(')'); |
764 | buffer.append(newLine); | |
c392540b FC |
765 | buffer.append("Event count = "); //$NON-NLS-1$ |
766 | buffer.append(nbEvents); | |
95aa81ef JCK |
767 | if (!HistogramScaledData.hideLostEvents) { |
768 | final int nbLostEvents = (index >= 0) ? fScaledData.fLostEventsData[index] : 0; | |
769 | buffer.append(newLine); | |
770 | buffer.append("Lost events count = "); //$NON-NLS-1$ | |
771 | buffer.append(nbLostEvents); | |
772 | } | |
c392540b FC |
773 | return buffer.toString(); |
774 | } | |
775 | ||
776 | // ------------------------------------------------------------------------ | |
777 | // ControlListener | |
778 | // ------------------------------------------------------------------------ | |
779 | ||
780 | @Override | |
09e86496 | 781 | public void controlMoved(final ControlEvent event) { |
fbd124dd | 782 | fDataModel.complete(); |
c392540b FC |
783 | } |
784 | ||
785 | @Override | |
09e86496 | 786 | public void controlResized(final ControlEvent event) { |
fbd124dd | 787 | fDataModel.complete(); |
c392540b | 788 | } |
f8177ba2 FC |
789 | |
790 | // ------------------------------------------------------------------------ | |
791 | // Signal Handlers | |
792 | // ------------------------------------------------------------------------ | |
793 | ||
794 | /** | |
795 | * Format the timestamp and update the display | |
796 | * | |
95aa81ef JCK |
797 | * @param signal |
798 | * the incoming signal | |
f8177ba2 FC |
799 | * @since 2.0 |
800 | */ | |
801 | @TmfSignalHandler | |
802 | public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) { | |
da7bdcbc PT |
803 | if (fDataModel.getNbEvents() == 0) { |
804 | return; | |
805 | } | |
806 | ||
f8177ba2 | 807 | String newTS = TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getFirstBucketTime()); |
f8177ba2 FC |
808 | fTimeRangeStartText.setText(newTS); |
809 | ||
810 | newTS = TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime()); | |
f8177ba2 | 811 | fTimeRangeEndText.setText(newTS); |
f8177ba2 | 812 | |
da7bdcbc | 813 | fComposite.layout(); |
f8177ba2 FC |
814 | } |
815 | ||
c392540b | 816 | } |