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