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(); | |
329 | fScaledData = null; | |
c392540b FC |
330 | } |
331 | ||
332 | /** | |
333 | * Increase the histogram bucket corresponding to [timestamp] | |
20ff3b75 AM |
334 | * |
335 | * @param eventCount | |
336 | * The new event count | |
c392540b | 337 | * @param timestamp |
20ff3b75 | 338 | * The latest timestamp |
c392540b | 339 | */ |
09e86496 | 340 | public void countEvent(final long eventCount, final long timestamp) { |
fbd124dd | 341 | fDataModel.countEvent(eventCount, timestamp); |
c392540b FC |
342 | } |
343 | ||
344 | /** | |
345 | * Sets the current event time and refresh the display | |
20ff3b75 | 346 | * |
c392540b | 347 | * @param timestamp |
20ff3b75 | 348 | * The time of the current event |
c392540b | 349 | */ |
09e86496 | 350 | public void setCurrentEvent(final long timestamp) { |
74237cc3 | 351 | fCurrentEventTime = (timestamp > 0) ? timestamp : 0; |
fbd124dd | 352 | fDataModel.setCurrentEventNotifyListeners(timestamp); |
c392540b FC |
353 | } |
354 | ||
355 | /** | |
356 | * Computes the timestamp of the bucket at [offset] | |
20ff3b75 | 357 | * |
c392540b FC |
358 | * @param offset offset from the left on the histogram |
359 | * @return the start timestamp of the corresponding bucket | |
360 | */ | |
09e86496 | 361 | public synchronized long getTimestamp(final int offset) { |
c392540b FC |
362 | assert offset > 0 && offset < fScaledData.fWidth; |
363 | try { | |
fbd124dd | 364 | return fDataModel.getFirstBucketTime() + fScaledData.fBucketDuration * offset; |
09e86496 | 365 | } catch (final Exception e) { |
c392540b FC |
366 | return 0; // TODO: Fix that racing condition (NPE) |
367 | } | |
368 | } | |
369 | ||
370 | /** | |
371 | * Computes the offset of the timestamp in the histogram | |
20ff3b75 | 372 | * |
c392540b FC |
373 | * @param timestamp the timestamp |
374 | * @return the offset of the corresponding bucket (-1 if invalid) | |
375 | */ | |
09e86496 | 376 | public synchronized int getOffset(final long timestamp) { |
20ff3b75 | 377 | if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) { |
c392540b | 378 | return -1; |
20ff3b75 | 379 | } |
fbd124dd | 380 | return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration); |
c392540b FC |
381 | } |
382 | ||
383 | /** | |
384 | * Move the currently selected bar cursor to a non-empty bucket. | |
20ff3b75 | 385 | * |
c392540b FC |
386 | * @param keyCode the SWT key code |
387 | */ | |
09e86496 | 388 | protected void moveCursor(final int keyCode) { |
c392540b | 389 | |
20ff3b75 | 390 | if (fScaledData.fCurrentBucket == HistogramScaledData.OUT_OF_RANGE_BUCKET) { |
c392540b | 391 | return; |
20ff3b75 | 392 | } |
c392540b FC |
393 | |
394 | int index; | |
395 | switch (keyCode) { | |
396 | ||
397 | case SWT.HOME: | |
398 | index = 0; | |
20ff3b75 | 399 | while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0) { |
c392540b | 400 | index++; |
20ff3b75 AM |
401 | } |
402 | if (index < fScaledData.fLastBucket) { | |
c392540b | 403 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 404 | } |
c392540b FC |
405 | break; |
406 | ||
407 | case SWT.ARROW_RIGHT: | |
408 | index = fScaledData.fCurrentBucket + 1; | |
20ff3b75 | 409 | while (index < fScaledData.fWidth && fScaledData.fData[index] == 0) { |
c392540b | 410 | index++; |
20ff3b75 AM |
411 | } |
412 | if (index < fScaledData.fLastBucket) { | |
c392540b | 413 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 414 | } |
c392540b FC |
415 | break; |
416 | ||
417 | case SWT.END: | |
418 | index = fScaledData.fLastBucket; | |
20ff3b75 | 419 | while (index >= 0 && fScaledData.fData[index] == 0) { |
c392540b | 420 | index--; |
20ff3b75 AM |
421 | } |
422 | if (index >= 0) { | |
c392540b | 423 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 424 | } |
c392540b FC |
425 | break; |
426 | ||
427 | case SWT.ARROW_LEFT: | |
428 | index = fScaledData.fCurrentBucket - 1; | |
20ff3b75 | 429 | while (index >= 0 && fScaledData.fData[index] == 0) { |
c392540b | 430 | index--; |
20ff3b75 AM |
431 | } |
432 | if (index >= 0) { | |
c392540b | 433 | fScaledData.fCurrentBucket = index; |
20ff3b75 | 434 | } |
c392540b FC |
435 | break; |
436 | ||
437 | default: | |
438 | return; | |
439 | } | |
440 | ||
441 | updateCurrentEventTime(); | |
442 | } | |
443 | ||
444 | /** | |
445 | * Refresh the histogram display | |
446 | */ | |
fbd124dd BH |
447 | @Override |
448 | public void modelUpdated() { | |
20ff3b75 | 449 | if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) { |
c392540b FC |
450 | fCanvas.getDisplay().asyncExec(new Runnable() { |
451 | @Override | |
452 | public void run() { | |
453 | if (!fCanvas.isDisposed()) { | |
454 | // Retrieve and normalize the data | |
09e86496 FC |
455 | final int canvasWidth = fCanvas.getBounds().width; |
456 | final int canvasHeight = fCanvas.getBounds().height; | |
20ff3b75 | 457 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
40890fec | 458 | return; |
20ff3b75 | 459 | } |
c392540b | 460 | fDataModel.setCurrentEvent(fCurrentEventTime); |
f8177ba2 | 461 | fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, 1); |
0316808c FC |
462 | synchronized(fScaledData) { |
463 | if (fScaledData != null) { | |
464 | fCanvas.redraw(); | |
465 | // Display histogram and update X-,Y-axis labels | |
f8177ba2 FC |
466 | fTimeRangeStartText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getFirstBucketTime())); |
467 | fTimeRangeEndText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime())); | |
0316808c FC |
468 | fMaxNbEventsText.setText(Long.toString(fScaledData.fMaxValue)); |
469 | // The Y-axis area might need to be re-sized | |
470 | fMaxNbEventsText.getParent().layout(); | |
471 | } | |
09e86496 | 472 | } |
c392540b FC |
473 | } |
474 | } | |
475 | }); | |
20ff3b75 | 476 | } |
c392540b FC |
477 | } |
478 | ||
479 | // ------------------------------------------------------------------------ | |
480 | // Helper functions | |
481 | // ------------------------------------------------------------------------ | |
482 | ||
483 | private void updateCurrentEventTime() { | |
09e86496 | 484 | final long bucketStartTime = getTimestamp(fScaledData.fCurrentBucket); |
c392540b FC |
485 | ((HistogramView) fParentView).updateCurrentEventTime(bucketStartTime); |
486 | } | |
487 | ||
488 | // ------------------------------------------------------------------------ | |
489 | // PaintListener | |
490 | // ------------------------------------------------------------------------ | |
b544077e BH |
491 | /** |
492 | * Image key string for the canvas. | |
493 | */ | |
c392540b FC |
494 | protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$ |
495 | ||
496 | @Override | |
09e86496 | 497 | public void paintControl(final PaintEvent event) { |
c392540b FC |
498 | |
499 | // Get the geometry | |
09e86496 FC |
500 | final int canvasWidth = fCanvas.getBounds().width; |
501 | final int canvasHeight = fCanvas.getBounds().height; | |
c392540b FC |
502 | |
503 | // Make sure we have something to draw upon | |
20ff3b75 | 504 | if (canvasWidth <= 0 || canvasHeight <= 0) { |
c392540b | 505 | return; |
20ff3b75 | 506 | } |
c392540b FC |
507 | |
508 | // Retrieve image; re-create only if necessary | |
509 | Image image = (Image) fCanvas.getData(IMAGE_KEY); | |
510 | if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) { | |
511 | image = new Image(event.display, canvasWidth, canvasHeight); | |
512 | fCanvas.setData(IMAGE_KEY, image); | |
513 | } | |
514 | ||
515 | // Draw the histogram on its canvas | |
09e86496 | 516 | final GC imageGC = new GC(image); |
c392540b FC |
517 | formatImage(imageGC, image); |
518 | event.gc.drawImage(image, 0, 0); | |
519 | imageGC.dispose(); | |
520 | } | |
521 | ||
09e86496 | 522 | private void formatImage(final GC imageGC, final Image image) { |
c392540b | 523 | |
20ff3b75 | 524 | if (fScaledData == null) { |
c392540b | 525 | return; |
20ff3b75 | 526 | } |
c392540b | 527 | |
09e86496 | 528 | final HistogramScaledData scaledData = new HistogramScaledData(fScaledData); |
c392540b FC |
529 | |
530 | try { | |
531 | // Get drawing boundaries | |
09e86496 FC |
532 | final int width = image.getBounds().width; |
533 | final int height = image.getBounds().height; | |
c392540b FC |
534 | |
535 | // Clear the drawing area | |
536 | imageGC.setBackground(fBackgroundColor); | |
537 | imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1); | |
538 | ||
539 | // Draw the histogram bars | |
540 | imageGC.setBackground(fHistoBarColor); | |
09e86496 | 541 | final int limit = width < scaledData.fWidth ? width : scaledData.fWidth; |
c392540b | 542 | for (int i = 1; i < limit; i++) { |
e60df94a | 543 | final int value = (int) Math.ceil(scaledData.fData[i] * scaledData.fScalingFactor); |
c392540b FC |
544 | imageGC.fillRectangle(i, height - value, 1, value); |
545 | } | |
546 | ||
547 | // Draw the current event bar | |
09e86496 | 548 | final int currentBucket = scaledData.fCurrentBucket; |
20ff3b75 | 549 | if (currentBucket >= 0 && currentBucket < limit) { |
c392540b | 550 | drawDelimiter(imageGC, fCurrentEventColor, height, currentBucket); |
20ff3b75 | 551 | } |
c392540b FC |
552 | |
553 | // Add a dashed line as a delimiter (at the right of the last bar) | |
554 | int lastEventIndex = limit - 1; | |
20ff3b75 | 555 | while (lastEventIndex >= 0 && scaledData.fData[lastEventIndex] == 0) { |
c392540b | 556 | lastEventIndex--; |
20ff3b75 | 557 | } |
c392540b FC |
558 | lastEventIndex += (lastEventIndex < limit - 1) ? 1 : 0; |
559 | drawDelimiter(imageGC, fLastEventColor, height, lastEventIndex); | |
09e86496 | 560 | } catch (final Exception e) { |
c392540b FC |
561 | // Do nothing |
562 | } | |
563 | } | |
564 | ||
abbdd66a AM |
565 | private static void drawDelimiter(final GC imageGC, final Color color, |
566 | final int height, final int index) { | |
c392540b | 567 | imageGC.setBackground(color); |
09e86496 | 568 | final int dash = height / 4; |
c392540b FC |
569 | imageGC.fillRectangle(index, 0 * dash, 1, dash - 1); |
570 | imageGC.fillRectangle(index, 1 * dash, 1, dash - 1); | |
571 | imageGC.fillRectangle(index, 2 * dash, 1, dash - 1); | |
572 | imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash); | |
573 | } | |
574 | ||
575 | // ------------------------------------------------------------------------ | |
576 | // KeyListener | |
577 | // ------------------------------------------------------------------------ | |
578 | ||
579 | @Override | |
09e86496 | 580 | public void keyPressed(final KeyEvent event) { |
c392540b FC |
581 | moveCursor(event.keyCode); |
582 | } | |
583 | ||
584 | @Override | |
09e86496 | 585 | public void keyReleased(final KeyEvent event) { |
c392540b FC |
586 | } |
587 | ||
588 | // ------------------------------------------------------------------------ | |
589 | // MouseListener | |
590 | // ------------------------------------------------------------------------ | |
591 | ||
592 | @Override | |
09e86496 | 593 | public void mouseDoubleClick(final MouseEvent event) { |
c392540b FC |
594 | } |
595 | ||
596 | @Override | |
09e86496 | 597 | public void mouseDown(final MouseEvent event) { |
c392540b FC |
598 | if (fDataModel.getNbEvents() > 0 && fScaledData.fLastBucket >= event.x) { |
599 | fScaledData.fCurrentBucket = event.x; | |
600 | updateCurrentEventTime(); | |
601 | } | |
602 | } | |
603 | ||
604 | @Override | |
09e86496 | 605 | public void mouseUp(final MouseEvent event) { |
c392540b FC |
606 | } |
607 | ||
608 | // ------------------------------------------------------------------------ | |
609 | // MouseTrackListener | |
610 | // ------------------------------------------------------------------------ | |
611 | ||
612 | @Override | |
09e86496 | 613 | public void mouseEnter(final MouseEvent event) { |
c392540b FC |
614 | } |
615 | ||
616 | @Override | |
09e86496 | 617 | public void mouseExit(final MouseEvent event) { |
c392540b FC |
618 | } |
619 | ||
620 | @Override | |
09e86496 | 621 | public void mouseHover(final MouseEvent event) { |
74237cc3 | 622 | if (fDataModel.getNbEvents() > 0 && fScaledData != null && fScaledData.fLastBucket >= event.x) { |
09e86496 | 623 | final String tooltip = formatToolTipLabel(event.x); |
c392540b FC |
624 | fCanvas.setToolTipText(tooltip); |
625 | } | |
626 | } | |
627 | ||
09e86496 | 628 | private String formatToolTipLabel(final int index) { |
466857f6 | 629 | long startTime = fScaledData.getBucketStartTime(index); |
09e86496 | 630 | // negative values are possible if time values came into the model in decreasing order |
20ff3b75 | 631 | if (startTime < 0) { |
fbd124dd | 632 | startTime = 0; |
20ff3b75 | 633 | } |
466857f6 | 634 | final long endTime = fScaledData.getBucketEndTime(index); |
09e86496 | 635 | final int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0; |
c392540b | 636 | |
09e86496 | 637 | final StringBuffer buffer = new StringBuffer(); |
c392540b | 638 | buffer.append("Range = ["); //$NON-NLS-1$ |
f8177ba2 | 639 | buffer.append(new TmfTimestamp(startTime, ITmfTimestamp.NANOSECOND_SCALE).toString()); |
c392540b | 640 | buffer.append(","); //$NON-NLS-1$ |
f8177ba2 | 641 | buffer.append(new TmfTimestamp(endTime, ITmfTimestamp.NANOSECOND_SCALE).toString()); |
c392540b FC |
642 | buffer.append(")\n"); //$NON-NLS-1$ |
643 | buffer.append("Event count = "); //$NON-NLS-1$ | |
644 | buffer.append(nbEvents); | |
645 | return buffer.toString(); | |
646 | } | |
647 | ||
648 | // ------------------------------------------------------------------------ | |
649 | // ControlListener | |
650 | // ------------------------------------------------------------------------ | |
651 | ||
652 | @Override | |
09e86496 | 653 | public void controlMoved(final ControlEvent event) { |
fbd124dd | 654 | fDataModel.complete(); |
c392540b FC |
655 | } |
656 | ||
657 | @Override | |
09e86496 | 658 | public void controlResized(final ControlEvent event) { |
fbd124dd | 659 | fDataModel.complete(); |
c392540b | 660 | } |
f8177ba2 FC |
661 | |
662 | // ------------------------------------------------------------------------ | |
663 | // Signal Handlers | |
664 | // ------------------------------------------------------------------------ | |
665 | ||
666 | /** | |
667 | * Format the timestamp and update the display | |
668 | * | |
669 | * @param signal the incoming signal | |
670 | * @since 2.0 | |
671 | */ | |
672 | @TmfSignalHandler | |
673 | public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) { | |
674 | Point size = fTimeRangeStartText.getSize(); | |
675 | String newTS = TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getFirstBucketTime()); | |
676 | size.x = getTextSize(newTS); | |
677 | fTimeRangeStartText.setSize(size); | |
678 | fTimeRangeStartText.setText(newTS); | |
679 | ||
680 | newTS = TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime()); | |
681 | Rectangle rect = fTimeRangeEndText.getBounds(); | |
682 | int newWidth = getTextSize(newTS); | |
683 | rect.x += rect.width - newWidth; | |
684 | rect.width = newWidth; | |
685 | fTimeRangeEndText.setBounds(rect); | |
686 | fTimeRangeEndText.setText(newTS); | |
687 | } | |
688 | ||
689 | /** | |
690 | * Compute the width of a String. | |
691 | * | |
692 | * @param text the Text to measure | |
693 | * @return The result size | |
694 | * @since 2.0 | |
695 | */ | |
696 | private int getTextSize(final String text) { | |
697 | GC controlGC = new GC(fParent); | |
698 | controlGC.setFont(fFont); | |
699 | ||
700 | int textSize = 0; | |
701 | for (int pos = 0; pos < text.length(); pos++) { | |
702 | textSize += controlGC.getAdvanceWidth(text.charAt(pos)); | |
703 | } | |
704 | // Add an extra space | |
705 | textSize += controlGC.getAdvanceWidth(' '); | |
706 | ||
707 | controlGC.dispose(); | |
708 | ||
709 | return textSize; | |
710 | } | |
711 | ||
c392540b | 712 | } |