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