Fixed JavaDoc in TMF UI plugin
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / histogram / Histogram.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2012 Ericsson
3 *
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
8 *
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
11 * Bernd Hufmann - Changed to updated histogram data model
12 * Francois Chouinard - Initial API and implementation
13 *******************************************************************************/
14
15 package org.eclipse.linuxtools.tmf.ui.views.histogram;
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 /**
41 * Re-usable histogram widget.
42 *
43 * It has the following features:
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>
73 *
74 * @version 1.0
75 * @author Francois Chouinard
76 */
77 public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseTrackListener, IHistogramModelListener {
78
79 // ------------------------------------------------------------------------
80 // Constants
81 // ------------------------------------------------------------------------
82
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)
90 /**
91 * The time scale of the histogram (nano seconds)
92 */
93 public static final byte TIME_SCALE = -9;
94
95 /**
96 * The histogram bar width (number of pixels).
97 */
98 public static final int HISTOGRAM_BAR_WIDTH = 1;
99
100 // ------------------------------------------------------------------------
101 // Attributes
102 // ------------------------------------------------------------------------
103
104 // Owner view
105 /**
106 * The parent TMF view.
107 */
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
116 /**
117 * Histogram drawing area
118 */
119 protected Canvas fCanvas;
120 /**
121 * The histogram data model.
122 */
123 protected final HistogramDataModel fDataModel;
124 /**
125 * The histogram data model scaled to current resolution and screen width.
126 */
127 protected HistogramScaledData fScaledData;
128
129 protected long fCurrentEventTime = 0;
130
131 // ------------------------------------------------------------------------
132 // Construction
133 // ------------------------------------------------------------------------
134
135 /**
136 * Standard constructor.
137 *
138 * @param view A reference to the parent TMF view.
139 * @param parent A parent composite
140 */
141 public Histogram(final TmfView view, final Composite parent) {
142 fParentView = view;
143
144 createWidget(parent);
145 fDataModel = new HistogramDataModel();
146 fDataModel.addHistogramListener(this);
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
156 /**
157 * Dispose resources and deregisters listeners.
158 */
159 public void dispose() {
160 fHistoBarColor.dispose();
161 fDataModel.removeHistogramListener(this);
162 }
163
164 private void createWidget(final Composite parent) {
165
166 final Color labelColor = parent.getBackground();
167 final Font fFont = adjustFont(parent);
168
169 final int initalWidth = 10;
170
171 // --------------------------------------------------------------------
172 // Define the histogram
173 // --------------------------------------------------------------------
174
175 final GridLayout gridLayout = new GridLayout();
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;
184 final Composite composite = new Composite(parent, SWT.FILL);
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;
230 final Text dummyText = new Text(composite, SWT.READ_ONLY);
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
258 private Font adjustFont(final Composite composite) {
259 // Reduce font size for a more pleasing rendering
260 final int fontSizeAdjustment = -2;
261 final Font font = composite.getFont();
262 final FontData fontData = font.getFontData()[0];
263 return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle());
264 }
265
266 // ------------------------------------------------------------------------
267 // Accessors
268 // ------------------------------------------------------------------------
269
270 /**
271 * Returns the start time (equal first bucket time).
272 * @return the start time.
273 */
274 public long getStartTime() {
275 return fDataModel.getFirstBucketTime();
276 }
277
278 /**
279 * Returns the end time.
280 * @return the end time.
281 */
282 public long getEndTime() {
283 return fDataModel.getEndTime();
284 }
285
286 /**
287 * Returns the time limit (end of last bucket)
288 * @return the time limit.
289 */
290 public long getTimeLimit() {
291 return fDataModel.getTimeLimit();
292 }
293
294 /**
295 * Returns a data model reference.
296 * @return data model.
297 */
298 public HistogramDataModel getDataModel() {
299 return fDataModel;
300 }
301
302 // ------------------------------------------------------------------------
303 // Operations
304 // ------------------------------------------------------------------------
305 /**
306 * Updates the time range.
307 * @param startTime A start time
308 * @param endTime A end time.
309 */
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;
318 }
319
320 /**
321 * Increase the histogram bucket corresponding to [timestamp]
322 *
323 * @param timestamp
324 */
325 public void countEvent(final long eventCount, final long timestamp) {
326 fDataModel.countEvent(eventCount, timestamp);
327 }
328
329 /**
330 * Sets the current event time and refresh the display
331 *
332 * @param timestamp
333 */
334 public void setCurrentEvent(final long timestamp) {
335 fCurrentEventTime = (timestamp > 0) ? timestamp : 0;
336 fDataModel.setCurrentEventNotifyListeners(timestamp);
337 }
338
339 /**
340 * Computes the timestamp of the bucket at [offset]
341 *
342 * @param offset offset from the left on the histogram
343 * @return the start timestamp of the corresponding bucket
344 */
345 public synchronized long getTimestamp(final int offset) {
346 assert offset > 0 && offset < fScaledData.fWidth;
347 try {
348 return fDataModel.getFirstBucketTime() + fScaledData.fBucketDuration * offset;
349 } catch (final Exception e) {
350 return 0; // TODO: Fix that racing condition (NPE)
351 }
352 }
353
354 /**
355 * Computes the offset of the timestamp in the histogram
356 *
357 * @param timestamp the timestamp
358 * @return the offset of the corresponding bucket (-1 if invalid)
359 */
360 public synchronized int getOffset(final long timestamp) {
361 if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime())
362 return -1;
363 return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration);
364 }
365
366 /**
367 * Move the currently selected bar cursor to a non-empty bucket.
368 *
369 * @param keyCode the SWT key code
370 */
371 protected void moveCursor(final int keyCode) {
372
373 if (fScaledData.fCurrentBucket == HistogramScaledData.OUT_OF_RANGE_BUCKET)
374 return;
375
376 int index;
377 switch (keyCode) {
378
379 case SWT.HOME:
380 index = 0;
381 while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0)
382 index++;
383 if (index < fScaledData.fLastBucket)
384 fScaledData.fCurrentBucket = index;
385 break;
386
387 case SWT.ARROW_RIGHT:
388 index = fScaledData.fCurrentBucket + 1;
389 while (index < fScaledData.fWidth && fScaledData.fData[index] == 0)
390 index++;
391 if (index < fScaledData.fLastBucket)
392 fScaledData.fCurrentBucket = index;
393 break;
394
395 case SWT.END:
396 index = fScaledData.fLastBucket;
397 while (index >= 0 && fScaledData.fData[index] == 0)
398 index--;
399 if (index >= 0)
400 fScaledData.fCurrentBucket = index;
401 break;
402
403 case SWT.ARROW_LEFT:
404 index = fScaledData.fCurrentBucket - 1;
405 while (index >= 0 && fScaledData.fData[index] == 0)
406 index--;
407 if (index >= 0)
408 fScaledData.fCurrentBucket = index;
409 break;
410
411 default:
412 return;
413 }
414
415 updateCurrentEventTime();
416 }
417
418 /**
419 * Refresh the histogram display
420 */
421 @Override
422 public void modelUpdated() {
423 if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null)
424 fCanvas.getDisplay().asyncExec(new Runnable() {
425 @Override
426 public void run() {
427 if (!fCanvas.isDisposed()) {
428 // Retrieve and normalize the data
429 final int canvasWidth = fCanvas.getBounds().width;
430 final int canvasHeight = fCanvas.getBounds().height;
431 if (canvasWidth <= 0 || canvasHeight <= 0)
432 return;
433 fDataModel.setCurrentEvent(fCurrentEventTime);
434 fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, HISTOGRAM_BAR_WIDTH);
435 synchronized(fScaledData) {
436 if (fScaledData != null) {
437 fCanvas.redraw();
438 // Display histogram and update X-,Y-axis labels
439 fTimeRangeStartText.setText(HistogramUtils.nanosecondsToString(fDataModel.getFirstBucketTime()));
440 fTimeRangeEndText.setText(HistogramUtils.nanosecondsToString(fDataModel.getEndTime()));
441 fMaxNbEventsText.setText(Long.toString(fScaledData.fMaxValue));
442 // The Y-axis area might need to be re-sized
443 fMaxNbEventsText.getParent().layout();
444 }
445 }
446 }
447 }
448 });
449 }
450
451 // ------------------------------------------------------------------------
452 // Helper functions
453 // ------------------------------------------------------------------------
454
455 private void updateCurrentEventTime() {
456 final long bucketStartTime = getTimestamp(fScaledData.fCurrentBucket);
457 ((HistogramView) fParentView).updateCurrentEventTime(bucketStartTime);
458 }
459
460 // ------------------------------------------------------------------------
461 // PaintListener
462 // ------------------------------------------------------------------------
463 /**
464 * Image key string for the canvas.
465 */
466 protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$
467
468 @Override
469 public void paintControl(final PaintEvent event) {
470
471 // Get the geometry
472 final int canvasWidth = fCanvas.getBounds().width;
473 final int canvasHeight = fCanvas.getBounds().height;
474
475 // Make sure we have something to draw upon
476 if (canvasWidth <= 0 || canvasHeight <= 0)
477 return;
478
479 // Retrieve image; re-create only if necessary
480 Image image = (Image) fCanvas.getData(IMAGE_KEY);
481 if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) {
482 image = new Image(event.display, canvasWidth, canvasHeight);
483 fCanvas.setData(IMAGE_KEY, image);
484 }
485
486 // Draw the histogram on its canvas
487 final GC imageGC = new GC(image);
488 formatImage(imageGC, image);
489 event.gc.drawImage(image, 0, 0);
490 imageGC.dispose();
491 }
492
493 private void formatImage(final GC imageGC, final Image image) {
494
495 if (fScaledData == null)
496 return;
497
498 final HistogramScaledData scaledData = new HistogramScaledData(fScaledData);
499
500 try {
501 // Get drawing boundaries
502 final int width = image.getBounds().width;
503 final int height = image.getBounds().height;
504
505 // Clear the drawing area
506 imageGC.setBackground(fBackgroundColor);
507 imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1);
508
509 // Draw the histogram bars
510 imageGC.setBackground(fHistoBarColor);
511 final int limit = width < scaledData.fWidth ? width : scaledData.fWidth;
512 for (int i = 1; i < limit; i++) {
513 final int value = (int) (scaledData.fData[i] * scaledData.fScalingFactor);
514 imageGC.fillRectangle(i, height - value, 1, value);
515 }
516
517 // Draw the current event bar
518 final int currentBucket = scaledData.fCurrentBucket;
519 if (currentBucket >= 0 && currentBucket < limit)
520 drawDelimiter(imageGC, fCurrentEventColor, height, currentBucket);
521
522 // Add a dashed line as a delimiter (at the right of the last bar)
523 int lastEventIndex = limit - 1;
524 while (lastEventIndex >= 0 && scaledData.fData[lastEventIndex] == 0)
525 lastEventIndex--;
526 lastEventIndex += (lastEventIndex < limit - 1) ? 1 : 0;
527 drawDelimiter(imageGC, fLastEventColor, height, lastEventIndex);
528 } catch (final Exception e) {
529 // Do nothing
530 }
531 }
532
533 private void drawDelimiter(final GC imageGC, final Color color, final int height, final int index) {
534 imageGC.setBackground(color);
535 final int dash = height / 4;
536 imageGC.fillRectangle(index, 0 * dash, 1, dash - 1);
537 imageGC.fillRectangle(index, 1 * dash, 1, dash - 1);
538 imageGC.fillRectangle(index, 2 * dash, 1, dash - 1);
539 imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash);
540 }
541
542 // ------------------------------------------------------------------------
543 // KeyListener
544 // ------------------------------------------------------------------------
545
546 @Override
547 public void keyPressed(final KeyEvent event) {
548 moveCursor(event.keyCode);
549 }
550
551 @Override
552 public void keyReleased(final KeyEvent event) {
553 }
554
555 // ------------------------------------------------------------------------
556 // MouseListener
557 // ------------------------------------------------------------------------
558
559 @Override
560 public void mouseDoubleClick(final MouseEvent event) {
561 }
562
563 @Override
564 public void mouseDown(final MouseEvent event) {
565 if (fDataModel.getNbEvents() > 0 && fScaledData.fLastBucket >= event.x) {
566 fScaledData.fCurrentBucket = event.x;
567 updateCurrentEventTime();
568 }
569 }
570
571 @Override
572 public void mouseUp(final MouseEvent event) {
573 }
574
575 // ------------------------------------------------------------------------
576 // MouseTrackListener
577 // ------------------------------------------------------------------------
578
579 @Override
580 public void mouseEnter(final MouseEvent event) {
581 }
582
583 @Override
584 public void mouseExit(final MouseEvent event) {
585 }
586
587 @Override
588 public void mouseHover(final MouseEvent event) {
589 if (fDataModel.getNbEvents() > 0 && fScaledData != null && fScaledData.fLastBucket >= event.x) {
590 final String tooltip = formatToolTipLabel(event.x);
591 fCanvas.setToolTipText(tooltip);
592 }
593 }
594
595 private String formatToolTipLabel(final int index) {
596 long startTime = fScaledData.getBucketStartTime(fScaledData.fCurrentBucket);
597 // negative values are possible if time values came into the model in decreasing order
598 if (startTime < 0)
599 startTime = 0;
600 final long endTime = fScaledData.getBucketEndTime(fScaledData.fCurrentBucket);
601 final int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0;
602
603 final StringBuffer buffer = new StringBuffer();
604 buffer.append("Range = ["); //$NON-NLS-1$
605 buffer.append(HistogramUtils.nanosecondsToString(startTime));
606 buffer.append(","); //$NON-NLS-1$
607 buffer.append(HistogramUtils.nanosecondsToString(endTime));
608 buffer.append(")\n"); //$NON-NLS-1$
609 buffer.append("Event count = "); //$NON-NLS-1$
610 buffer.append(nbEvents);
611 return buffer.toString();
612 }
613
614 // ------------------------------------------------------------------------
615 // ControlListener
616 // ------------------------------------------------------------------------
617
618 @Override
619 public void controlMoved(final ControlEvent event) {
620 fDataModel.complete();
621 }
622
623 @Override
624 public void controlResized(final ControlEvent event) {
625 fDataModel.complete();
626 }
627 }
This page took 0.055887 seconds and 6 git commands to generate.