Commit | Line | Data |
---|---|---|
c392540b | 1 | /******************************************************************************* |
e0752744 | 2 | * Copyright (c) 2011, 2012 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 |
e0752744 | 12 | * Francois Chouinard - Initial API and implementation |
c392540b FC |
13 | *******************************************************************************/ |
14 | ||
e0752744 | 15 | package org.eclipse.linuxtools.tmf.ui.views.histogram; |
c392540b FC |
16 | |
17 | import org.eclipse.linuxtools.tmf.ui.views.TmfView; | |
18 | import org.eclipse.swt.SWT; | |
19 | import org.eclipse.swt.events.ControlEvent; | |
20 | import org.eclipse.swt.events.ControlListener; | |
21 | import org.eclipse.swt.events.KeyEvent; | |
22 | import org.eclipse.swt.events.KeyListener; | |
23 | import org.eclipse.swt.events.MouseEvent; | |
24 | import org.eclipse.swt.events.MouseListener; | |
25 | import org.eclipse.swt.events.MouseTrackListener; | |
26 | import org.eclipse.swt.events.PaintEvent; | |
27 | import org.eclipse.swt.events.PaintListener; | |
28 | import org.eclipse.swt.graphics.Color; | |
29 | import org.eclipse.swt.graphics.Font; | |
30 | import org.eclipse.swt.graphics.FontData; | |
31 | import org.eclipse.swt.graphics.GC; | |
32 | import org.eclipse.swt.graphics.Image; | |
e60df94a | 33 | import org.eclipse.swt.layout.FillLayout; |
c392540b FC |
34 | import org.eclipse.swt.layout.GridData; |
35 | import org.eclipse.swt.layout.GridLayout; | |
36 | import org.eclipse.swt.widgets.Canvas; | |
37 | import org.eclipse.swt.widgets.Composite; | |
38 | import org.eclipse.swt.widgets.Display; | |
39 | import org.eclipse.swt.widgets.Text; | |
40 | ||
41 | /** | |
b544077e | 42 | * Re-usable histogram widget. |
20ff3b75 | 43 | * |
b544077e | 44 | * It has the following features: |
c392540b FC |
45 | * <ul> |
46 | * <li>Y-axis labels displaying min/max count values | |
47 | * <li>X-axis labels displaying time range | |
48 | * <li>a histogram displaying the distribution of values over time (note that | |
49 | * the histogram might not necessarily fill the whole canvas) | |
50 | * </ul> | |
51 | * The widget also has 2 'markers' to identify: | |
52 | * <ul> | |
53 | * <li>a red dashed line over the bar that contains the currently selected event | |
54 | * <li>a dark red dashed line that delimits the right end of the histogram (if | |
55 | * it doesn't fill the canvas) | |
56 | * </ul> | |
57 | * Clicking on the histogram will select the current event at the mouse | |
58 | * location. | |
59 | * <p> | |
60 | * Once the histogram is selected, there is some limited keyboard support: | |
61 | * <ul> | |
62 | * <li>Home: go to the first histogram bar | |
63 | * <li>End: go to the last histogram bar | |
64 | * <li>Left: go to the previous histogram | |
65 | * <li>Right: go to the next histogram bar | |
66 | * </ul> | |
67 | * Finally, when the mouse hovers over the histogram, a tool tip showing the | |
68 | * following information about the corresponding histogram bar time range: | |
69 | * <ul> | |
70 | * <li>start of the time range | |
71 | * <li>end of the time range | |
72 | * <li>number of events in that time range | |
73 | * </ul> | |
20ff3b75 | 74 | * |
b544077e BH |
75 | * @version 1.0 |
76 | * @author Francois Chouinard | |
c392540b | 77 | */ |
fbd124dd | 78 | public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseTrackListener, IHistogramModelListener { |
c392540b FC |
79 | |
80 | // ------------------------------------------------------------------------ | |
81 | // Constants | |
82 | // ------------------------------------------------------------------------ | |
83 | ||
c392540b FC |
84 | // Histogram colors |
85 | private final Color fBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE); | |
86 | private final Color fCurrentEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_RED); | |
87 | private final Color fLastEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED); | |
88 | private final Color fHistoBarColor = new Color(Display.getDefault(), 74, 112, 139); | |
89 | ||
90 | // Timestamp scale (nanosecond) | |
b544077e BH |
91 | /** |
92 | * The time scale of the histogram (nano seconds) | |
93 | */ | |
c392540b | 94 | public static final byte TIME_SCALE = -9; |
09e86496 | 95 | |
b544077e BH |
96 | /** |
97 | * The histogram bar width (number of pixels). | |
98 | */ | |
fbd124dd | 99 | public static final int HISTOGRAM_BAR_WIDTH = 1; |
c392540b FC |
100 | |
101 | // ------------------------------------------------------------------------ | |
102 | // Attributes | |
103 | // ------------------------------------------------------------------------ | |
104 | ||
105 | // Owner view | |
b544077e BH |
106 | /** |
107 | * The parent TMF view. | |
108 | */ | |
c392540b FC |
109 | protected TmfView fParentView; |
110 | ||
111 | // Histogram text fields | |
112 | private Text fMaxNbEventsText; | |
113 | private Text fMinNbEventsText; | |
114 | private Text fTimeRangeStartText; | |
115 | private Text fTimeRangeEndText; | |
116 | ||
b544077e BH |
117 | /** |
118 | * Histogram drawing area | |
119 | */ | |
c392540b | 120 | protected Canvas fCanvas; |
b544077e BH |
121 | /** |
122 | * The histogram data model. | |
123 | */ | |
c392540b | 124 | protected final HistogramDataModel fDataModel; |
b544077e | 125 | /** |
20ff3b75 | 126 | * The histogram data model scaled to current resolution and screen width. |
b544077e | 127 | */ |
c392540b FC |
128 | protected HistogramScaledData fScaledData; |
129 | ||
130 | protected long fCurrentEventTime = 0; | |
131 | ||
132 | // ------------------------------------------------------------------------ | |
133 | // Construction | |
134 | // ------------------------------------------------------------------------ | |
135 | ||
b544077e BH |
136 | /** |
137 | * Standard constructor. | |
20ff3b75 | 138 | * |
b544077e BH |
139 | * @param view A reference to the parent TMF view. |
140 | * @param parent A parent composite | |
141 | */ | |
09e86496 | 142 | public Histogram(final TmfView view, final Composite parent) { |
c392540b FC |
143 | fParentView = view; |
144 | ||
145 | createWidget(parent); | |
146 | fDataModel = new HistogramDataModel(); | |
fbd124dd | 147 | fDataModel.addHistogramListener(this); |
c392540b FC |
148 | clear(); |
149 | ||
150 | fCanvas.addControlListener(this); | |
151 | fCanvas.addPaintListener(this); | |
152 | fCanvas.addKeyListener(this); | |
153 | fCanvas.addMouseListener(this); | |
154 | fCanvas.addMouseTrackListener(this); | |
155 | } | |
156 | ||
b544077e BH |
157 | /** |
158 | * Dispose resources and deregisters listeners. | |
159 | */ | |
c392540b FC |
160 | public void dispose() { |
161 | fHistoBarColor.dispose(); | |
fbd124dd | 162 | fDataModel.removeHistogramListener(this); |
c392540b FC |
163 | } |
164 | ||
09e86496 | 165 | private void createWidget(final Composite parent) { |
c392540b | 166 | |
0301dc43 | 167 | final Color labelColor = parent.getBackground(); |
c392540b FC |
168 | final Font fFont = adjustFont(parent); |
169 | ||
170 | final int initalWidth = 10; | |
171 | ||
172 | // -------------------------------------------------------------------- | |
173 | // Define the histogram | |
174 | // -------------------------------------------------------------------- | |
175 | ||
09e86496 | 176 | final GridLayout gridLayout = new GridLayout(); |
c392540b FC |
177 | gridLayout.numColumns = 3; |
178 | gridLayout.marginHeight = 0; | |
179 | gridLayout.marginWidth = 0; | |
180 | gridLayout.marginTop = 0; | |
181 | gridLayout.horizontalSpacing = 0; | |
182 | gridLayout.verticalSpacing = 0; | |
183 | gridLayout.marginLeft = 0; | |
184 | gridLayout.marginRight = 0; | |
09e86496 | 185 | final Composite composite = new Composite(parent, SWT.FILL); |
c392540b FC |
186 | composite.setLayout(gridLayout); |
187 | ||
188 | // Use all the horizontal space | |
189 | GridData gridData = new GridData(); | |
190 | gridData.horizontalAlignment = SWT.FILL; | |
191 | gridData.verticalAlignment = SWT.FILL; | |
192 | gridData.grabExcessHorizontalSpace = true; | |
193 | composite.setLayoutData(gridData); | |
194 | ||
195 | // Y-axis max event | |
196 | gridData = new GridData(); | |
197 | gridData.horizontalAlignment = SWT.RIGHT; | |
198 | gridData.verticalAlignment = SWT.TOP; | |
199 | fMaxNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT); | |
200 | fMaxNbEventsText.setFont(fFont); | |
201 | fMaxNbEventsText.setBackground(labelColor); | |
202 | fMaxNbEventsText.setEditable(false); | |
203 | fMaxNbEventsText.setText("0"); //$NON-NLS-1$ | |
204 | fMaxNbEventsText.setLayoutData(gridData); | |
205 | ||
206 | // Histogram itself | |
e60df94a | 207 | Composite canvasComposite = new Composite(composite, SWT.BORDER); |
c392540b FC |
208 | gridData = new GridData(); |
209 | gridData.horizontalSpan = 2; | |
210 | gridData.verticalSpan = 2; | |
211 | gridData.horizontalAlignment = SWT.FILL; | |
212 | gridData.verticalAlignment = SWT.FILL; | |
213 | gridData.grabExcessHorizontalSpace = true; | |
e60df94a PT |
214 | canvasComposite.setLayoutData(gridData); |
215 | canvasComposite.setLayout(new FillLayout()); | |
216 | fCanvas = new Canvas(canvasComposite, SWT.DOUBLE_BUFFERED); | |
c392540b FC |
217 | |
218 | // Y-axis min event (always 0...) | |
219 | gridData = new GridData(); | |
220 | gridData.horizontalAlignment = SWT.RIGHT; | |
221 | gridData.verticalAlignment = SWT.BOTTOM; | |
222 | fMinNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT); | |
223 | fMinNbEventsText.setFont(fFont); | |
224 | fMinNbEventsText.setBackground(labelColor); | |
225 | fMinNbEventsText.setEditable(false); | |
226 | fMinNbEventsText.setText("0"); //$NON-NLS-1$ | |
227 | fMinNbEventsText.setLayoutData(gridData); | |
228 | ||
229 | // Dummy cell | |
230 | gridData = new GridData(initalWidth, SWT.DEFAULT); | |
231 | gridData.horizontalAlignment = SWT.RIGHT; | |
232 | gridData.verticalAlignment = SWT.BOTTOM; | |
09e86496 | 233 | final Text dummyText = new Text(composite, SWT.READ_ONLY); |
c392540b FC |
234 | dummyText.setFont(fFont); |
235 | dummyText.setBackground(labelColor); | |
236 | dummyText.setEditable(false); | |
237 | dummyText.setText(""); //$NON-NLS-1$ | |
238 | dummyText.setLayoutData(gridData); | |
239 | ||
240 | // Window range start time | |
241 | gridData = new GridData(); | |
242 | gridData.horizontalAlignment = SWT.LEFT; | |
243 | gridData.verticalAlignment = SWT.BOTTOM; | |
244 | fTimeRangeStartText = new Text(composite, SWT.READ_ONLY); | |
245 | fTimeRangeStartText.setFont(fFont); | |
246 | fTimeRangeStartText.setBackground(labelColor); | |
247 | fTimeRangeStartText.setText(HistogramUtils.nanosecondsToString(0)); | |
248 | fTimeRangeStartText.setLayoutData(gridData); | |
249 | ||
250 | // Window range end time | |
251 | gridData = new GridData(); | |
252 | gridData.horizontalAlignment = SWT.RIGHT; | |
253 | gridData.verticalAlignment = SWT.BOTTOM; | |
254 | fTimeRangeEndText = new Text(composite, SWT.READ_ONLY); | |
255 | fTimeRangeEndText.setFont(fFont); | |
256 | fTimeRangeEndText.setBackground(labelColor); | |
257 | fTimeRangeEndText.setText(HistogramUtils.nanosecondsToString(0)); | |
258 | fTimeRangeEndText.setLayoutData(gridData); | |
259 | } | |
260 | ||
abbdd66a | 261 | private static Font adjustFont(final Composite composite) { |
c392540b | 262 | // Reduce font size for a more pleasing rendering |
09e86496 FC |
263 | final int fontSizeAdjustment = -2; |
264 | final Font font = composite.getFont(); | |
265 | final FontData fontData = font.getFontData()[0]; | |
c392540b FC |
266 | return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle()); |
267 | } | |
268 | ||
269 | // ------------------------------------------------------------------------ | |
270 | // Accessors | |
271 | // ------------------------------------------------------------------------ | |
272 | ||
b544077e BH |
273 | /** |
274 | * Returns the start time (equal first bucket time). | |
275 | * @return the start time. | |
276 | */ | |
c392540b | 277 | public long getStartTime() { |
fbd124dd | 278 | return fDataModel.getFirstBucketTime(); |
c392540b FC |
279 | } |
280 | ||
b544077e BH |
281 | /** |
282 | * Returns the end time. | |
283 | * @return the end time. | |
284 | */ | |
c392540b FC |
285 | public long getEndTime() { |
286 | return fDataModel.getEndTime(); | |
287 | } | |
288 | ||
b544077e BH |
289 | /** |
290 | * Returns the time limit (end of last bucket) | |
291 | * @return the time limit. | |
292 | */ | |
c392540b FC |
293 | public long getTimeLimit() { |
294 | return fDataModel.getTimeLimit(); | |
295 | } | |
09e86496 | 296 | |
20ff3b75 AM |
297 | /** |
298 | * Returns a data model reference. | |
b544077e BH |
299 | * @return data model. |
300 | */ | |
fbd124dd BH |
301 | public HistogramDataModel getDataModel() { |
302 | return fDataModel; | |
303 | } | |
c392540b FC |
304 | |
305 | // ------------------------------------------------------------------------ | |
306 | // Operations | |
307 | // ------------------------------------------------------------------------ | |
b544077e | 308 | /** |
20ff3b75 | 309 | * Updates the time range. |
b544077e BH |
310 | * @param startTime A start time |
311 | * @param endTime A end time. | |
312 | */ | |
c392540b FC |
313 | public abstract void updateTimeRange(long startTime, long endTime); |
314 | ||
315 | /** | |
316 | * Clear the histogram and reset the data | |
317 | */ | |
318 | public void clear() { | |
319 | fDataModel.clear(); | |
320 | fScaledData = null; | |
c392540b FC |
321 | } |
322 | ||
323 | /** | |
324 | * Increase the histogram bucket corresponding to [timestamp] | |
20ff3b75 AM |
325 | * |
326 | * @param eventCount | |
327 | * The new event count | |
c392540b | 328 | * @param timestamp |
20ff3b75 | 329 | * The latest timestamp |
c392540b | 330 | */ |
09e86496 | 331 | public void countEvent(final long eventCount, final long timestamp) { |
fbd124dd | 332 | fDataModel.countEvent(eventCount, timestamp); |
c392540b FC |
333 | } |
334 | ||
335 | /** | |
336 | * Sets the current event time and refresh the display | |
20ff3b75 | 337 | * |
c392540b | 338 | * @param timestamp |
20ff3b75 | 339 | * The time of the current event |
c392540b | 340 | */ |
09e86496 | 341 | public void setCurrentEvent(final long timestamp) { |
74237cc3 | 342 | fCurrentEventTime = (timestamp > 0) ? timestamp : 0; |
fbd124dd | 343 | fDataModel.setCurrentEventNotifyListeners(timestamp); |
c392540b FC |
344 | } |
345 | ||
346 | /** | |
347 | * Computes the timestamp of the bucket at [offset] | |
20ff3b75 | 348 | * |
c392540b FC |
349 | * @param offset offset from the left on the histogram |
350 | * @return the start timestamp of the corresponding bucket | |
351 | */ | |
09e86496 | 352 | public synchronized long getTimestamp(final int offset) { |
c392540b FC |
353 | assert offset > 0 && offset < fScaledData.fWidth; |
354 | try { | |
fbd124dd | 355 | return fDataModel.getFirstBucketTime() + fScaledData.fBucketDuration * offset; |
09e86496 | 356 | } catch (final Exception e) { |
c392540b FC |
357 | return 0; // TODO: Fix that racing condition (NPE) |
358 | } | |
359 | } | |
360 | ||
361 | /** | |
362 | * Computes the offset of the timestamp in the histogram | |
20ff3b75 | 363 | * |
c392540b FC |
364 | * @param timestamp the timestamp |
365 | * @return the offset of the corresponding bucket (-1 if invalid) | |
366 | */ | |
09e86496 | 367 | public synchronized int getOffset(final long timestamp) { |
20ff3b75 | 368 | if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) { |
c392540b | 369 | return -1; |
20ff3b75 | 370 | } |
fbd124dd | 371 | return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration); |
c392540b FC |
372 | } |
373 | ||
374 | /** | |
375 | * Move the currently selected bar cursor to a non-empty bucket. | |
20ff3b75 | 376 | * |
c392540b FC |
377 | * @param keyCode the SWT key code |
378 | */ | |
09e86496 | 379 | protected void moveCursor(final int keyCode) { |
c392540b | 380 | |
20ff3b75 | 381 | if (fScaledData.fCurrentBucket == HistogramScaledData.OUT_OF_RANGE_BUCKET) { |
c392540b | 382 | return; |
20ff3b75 | 383 | } |
c392540b FC |
384 | |
385 | int index; | |
386 | switch (keyCode) { | |
387 | ||
388 | case SWT.HOME: | |
389 | index = 0; | |
20ff3b75 | 390 | while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0) { |
c392540b | 391 | index++; |
20ff3b75 AM |
392 | } |
393 | if (index < fScaledData.fLastBucket) { | |
c392540b | 394 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 395 | } |
c392540b FC |
396 | break; |
397 | ||
398 | case SWT.ARROW_RIGHT: | |
399 | index = fScaledData.fCurrentBucket + 1; | |
20ff3b75 | 400 | while (index < fScaledData.fWidth && fScaledData.fData[index] == 0) { |
c392540b | 401 | index++; |
20ff3b75 AM |
402 | } |
403 | if (index < fScaledData.fLastBucket) { | |
c392540b | 404 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 405 | } |
c392540b FC |
406 | break; |
407 | ||
408 | case SWT.END: | |
409 | index = fScaledData.fLastBucket; | |
20ff3b75 | 410 | while (index >= 0 && fScaledData.fData[index] == 0) { |
c392540b | 411 | index--; |
20ff3b75 AM |
412 | } |
413 | if (index >= 0) { | |
c392540b | 414 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 415 | } |
c392540b FC |
416 | break; |
417 | ||
418 | case SWT.ARROW_LEFT: | |
419 | index = fScaledData.fCurrentBucket - 1; | |
20ff3b75 | 420 | while (index >= 0 && fScaledData.fData[index] == 0) { |
c392540b | 421 | index--; |
20ff3b75 AM |
422 | } |
423 | if (index >= 0) { | |
c392540b | 424 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 425 | } |
c392540b FC |
426 | break; |
427 | ||
428 | default: | |
429 | return; | |
430 | } | |
431 | ||
432 | updateCurrentEventTime(); | |
433 | } | |
434 | ||
435 | /** | |
436 | * Refresh the histogram display | |
437 | */ | |
fbd124dd BH |
438 | @Override |
439 | public void modelUpdated() { | |
20ff3b75 | 440 | if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) { |
c392540b FC |
441 | fCanvas.getDisplay().asyncExec(new Runnable() { |
442 | @Override | |
443 | public void run() { | |
444 | if (!fCanvas.isDisposed()) { | |
445 | // Retrieve and normalize the data | |
09e86496 FC |
446 | final int canvasWidth = fCanvas.getBounds().width; |
447 | final int canvasHeight = fCanvas.getBounds().height; | |
20ff3b75 | 448 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
40890fec | 449 | return; |
20ff3b75 | 450 | } |
c392540b | 451 | fDataModel.setCurrentEvent(fCurrentEventTime); |
fbd124dd | 452 | fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, HISTOGRAM_BAR_WIDTH); |
0316808c FC |
453 | synchronized(fScaledData) { |
454 | if (fScaledData != null) { | |
455 | fCanvas.redraw(); | |
456 | // Display histogram and update X-,Y-axis labels | |
457 | fTimeRangeStartText.setText(HistogramUtils.nanosecondsToString(fDataModel.getFirstBucketTime())); | |
458 | fTimeRangeEndText.setText(HistogramUtils.nanosecondsToString(fDataModel.getEndTime())); | |
459 | fMaxNbEventsText.setText(Long.toString(fScaledData.fMaxValue)); | |
460 | // The Y-axis area might need to be re-sized | |
461 | fMaxNbEventsText.getParent().layout(); | |
462 | } | |
09e86496 | 463 | } |
c392540b FC |
464 | } |
465 | } | |
466 | }); | |
20ff3b75 | 467 | } |
c392540b FC |
468 | } |
469 | ||
470 | // ------------------------------------------------------------------------ | |
471 | // Helper functions | |
472 | // ------------------------------------------------------------------------ | |
473 | ||
474 | private void updateCurrentEventTime() { | |
09e86496 | 475 | final long bucketStartTime = getTimestamp(fScaledData.fCurrentBucket); |
c392540b FC |
476 | ((HistogramView) fParentView).updateCurrentEventTime(bucketStartTime); |
477 | } | |
478 | ||
479 | // ------------------------------------------------------------------------ | |
480 | // PaintListener | |
481 | // ------------------------------------------------------------------------ | |
b544077e BH |
482 | /** |
483 | * Image key string for the canvas. | |
484 | */ | |
c392540b FC |
485 | protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$ |
486 | ||
487 | @Override | |
09e86496 | 488 | public void paintControl(final PaintEvent event) { |
c392540b FC |
489 | |
490 | // Get the geometry | |
09e86496 FC |
491 | final int canvasWidth = fCanvas.getBounds().width; |
492 | final int canvasHeight = fCanvas.getBounds().height; | |
c392540b FC |
493 | |
494 | // Make sure we have something to draw upon | |
20ff3b75 | 495 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
c392540b | 496 | return; |
20ff3b75 | 497 | } |
c392540b FC |
498 | |
499 | // Retrieve image; re-create only if necessary | |
500 | Image image = (Image) fCanvas.getData(IMAGE_KEY); | |
501 | if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) { | |
502 | image = new Image(event.display, canvasWidth, canvasHeight); | |
503 | fCanvas.setData(IMAGE_KEY, image); | |
504 | } | |
505 | ||
506 | // Draw the histogram on its canvas | |
09e86496 | 507 | final GC imageGC = new GC(image); |
c392540b FC |
508 | formatImage(imageGC, image); |
509 | event.gc.drawImage(image, 0, 0); | |
510 | imageGC.dispose(); | |
511 | } | |
512 | ||
09e86496 | 513 | private void formatImage(final GC imageGC, final Image image) { |
c392540b | 514 | |
20ff3b75 | 515 | if (fScaledData == null) { |
c392540b | 516 | return; |
20ff3b75 | 517 | } |
c392540b | 518 | |
09e86496 | 519 | final HistogramScaledData scaledData = new HistogramScaledData(fScaledData); |
c392540b FC |
520 | |
521 | try { | |
522 | // Get drawing boundaries | |
09e86496 FC |
523 | final int width = image.getBounds().width; |
524 | final int height = image.getBounds().height; | |
c392540b FC |
525 | |
526 | // Clear the drawing area | |
527 | imageGC.setBackground(fBackgroundColor); | |
528 | imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1); | |
529 | ||
530 | // Draw the histogram bars | |
531 | imageGC.setBackground(fHistoBarColor); | |
09e86496 | 532 | final int limit = width < scaledData.fWidth ? width : scaledData.fWidth; |
c392540b | 533 | for (int i = 1; i < limit; i++) { |
e60df94a | 534 | final int value = (int) Math.ceil(scaledData.fData[i] * scaledData.fScalingFactor); |
c392540b FC |
535 | imageGC.fillRectangle(i, height - value, 1, value); |
536 | } | |
537 | ||
538 | // Draw the current event bar | |
09e86496 | 539 | final int currentBucket = scaledData.fCurrentBucket; |
20ff3b75 | 540 | if (currentBucket >= 0 && currentBucket < limit) { |
c392540b | 541 | drawDelimiter(imageGC, fCurrentEventColor, height, currentBucket); |
20ff3b75 | 542 | } |
c392540b FC |
543 | |
544 | // Add a dashed line as a delimiter (at the right of the last bar) | |
545 | int lastEventIndex = limit - 1; | |
20ff3b75 | 546 | while (lastEventIndex >= 0 && scaledData.fData[lastEventIndex] == 0) { |
c392540b | 547 | lastEventIndex--; |
20ff3b75 | 548 | } |
c392540b FC |
549 | lastEventIndex += (lastEventIndex < limit - 1) ? 1 : 0; |
550 | drawDelimiter(imageGC, fLastEventColor, height, lastEventIndex); | |
09e86496 | 551 | } catch (final Exception e) { |
c392540b FC |
552 | // Do nothing |
553 | } | |
554 | } | |
555 | ||
abbdd66a AM |
556 | private static void drawDelimiter(final GC imageGC, final Color color, |
557 | final int height, final int index) { | |
c392540b | 558 | imageGC.setBackground(color); |
09e86496 | 559 | final int dash = height / 4; |
c392540b FC |
560 | imageGC.fillRectangle(index, 0 * dash, 1, dash - 1); |
561 | imageGC.fillRectangle(index, 1 * dash, 1, dash - 1); | |
562 | imageGC.fillRectangle(index, 2 * dash, 1, dash - 1); | |
563 | imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash); | |
564 | } | |
565 | ||
566 | // ------------------------------------------------------------------------ | |
567 | // KeyListener | |
568 | // ------------------------------------------------------------------------ | |
569 | ||
570 | @Override | |
09e86496 | 571 | public void keyPressed(final KeyEvent event) { |
c392540b FC |
572 | moveCursor(event.keyCode); |
573 | } | |
574 | ||
575 | @Override | |
09e86496 | 576 | public void keyReleased(final KeyEvent event) { |
c392540b FC |
577 | } |
578 | ||
579 | // ------------------------------------------------------------------------ | |
580 | // MouseListener | |
581 | // ------------------------------------------------------------------------ | |
582 | ||
583 | @Override | |
09e86496 | 584 | public void mouseDoubleClick(final MouseEvent event) { |
c392540b FC |
585 | } |
586 | ||
587 | @Override | |
09e86496 | 588 | public void mouseDown(final MouseEvent event) { |
c392540b FC |
589 | if (fDataModel.getNbEvents() > 0 && fScaledData.fLastBucket >= event.x) { |
590 | fScaledData.fCurrentBucket = event.x; | |
591 | updateCurrentEventTime(); | |
592 | } | |
593 | } | |
594 | ||
595 | @Override | |
09e86496 | 596 | public void mouseUp(final MouseEvent event) { |
c392540b FC |
597 | } |
598 | ||
599 | // ------------------------------------------------------------------------ | |
600 | // MouseTrackListener | |
601 | // ------------------------------------------------------------------------ | |
602 | ||
603 | @Override | |
09e86496 | 604 | public void mouseEnter(final MouseEvent event) { |
c392540b FC |
605 | } |
606 | ||
607 | @Override | |
09e86496 | 608 | public void mouseExit(final MouseEvent event) { |
c392540b FC |
609 | } |
610 | ||
611 | @Override | |
09e86496 | 612 | public void mouseHover(final MouseEvent event) { |
74237cc3 | 613 | if (fDataModel.getNbEvents() > 0 && fScaledData != null && fScaledData.fLastBucket >= event.x) { |
09e86496 | 614 | final String tooltip = formatToolTipLabel(event.x); |
c392540b FC |
615 | fCanvas.setToolTipText(tooltip); |
616 | } | |
617 | } | |
618 | ||
09e86496 | 619 | private String formatToolTipLabel(final int index) { |
466857f6 | 620 | long startTime = fScaledData.getBucketStartTime(index); |
09e86496 | 621 | // negative values are possible if time values came into the model in decreasing order |
20ff3b75 | 622 | if (startTime < 0) { |
fbd124dd | 623 | startTime = 0; |
20ff3b75 | 624 | } |
466857f6 | 625 | final long endTime = fScaledData.getBucketEndTime(index); |
09e86496 | 626 | final int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0; |
c392540b | 627 | |
09e86496 | 628 | final StringBuffer buffer = new StringBuffer(); |
c392540b FC |
629 | buffer.append("Range = ["); //$NON-NLS-1$ |
630 | buffer.append(HistogramUtils.nanosecondsToString(startTime)); | |
631 | buffer.append(","); //$NON-NLS-1$ | |
632 | buffer.append(HistogramUtils.nanosecondsToString(endTime)); | |
633 | buffer.append(")\n"); //$NON-NLS-1$ | |
634 | buffer.append("Event count = "); //$NON-NLS-1$ | |
635 | buffer.append(nbEvents); | |
636 | return buffer.toString(); | |
637 | } | |
638 | ||
639 | // ------------------------------------------------------------------------ | |
640 | // ControlListener | |
641 | // ------------------------------------------------------------------------ | |
642 | ||
643 | @Override | |
09e86496 | 644 | public void controlMoved(final ControlEvent event) { |
fbd124dd | 645 | fDataModel.complete(); |
c392540b FC |
646 | } |
647 | ||
648 | @Override | |
09e86496 | 649 | public void controlResized(final ControlEvent event) { |
fbd124dd | 650 | fDataModel.complete(); |
c392540b | 651 | } |
c392540b | 652 | } |