33e5136b3877d722a60fe50b619b99dbef22a75c
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / histogram / Histogram.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2013 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 - Reformat histogram labels on format change
13 * Patrick Tasse - Support selection range
14 *******************************************************************************/
15
16 package org.eclipse.linuxtools.tmf.ui.views.histogram;
17
18 import org.eclipse.linuxtools.tmf.core.signal.TmfSignalHandler;
19 import org.eclipse.linuxtools.tmf.core.signal.TmfSignalManager;
20 import org.eclipse.linuxtools.tmf.core.signal.TmfTimestampFormatUpdateSignal;
21 import org.eclipse.linuxtools.tmf.core.timestamp.ITmfTimestamp;
22 import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestamp;
23 import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestampDelta;
24 import org.eclipse.linuxtools.tmf.core.timestamp.TmfTimestampFormat;
25 import org.eclipse.linuxtools.tmf.ui.views.TmfView;
26 import org.eclipse.osgi.util.NLS;
27 import org.eclipse.swt.SWT;
28 import org.eclipse.swt.events.ControlEvent;
29 import org.eclipse.swt.events.ControlListener;
30 import org.eclipse.swt.events.FocusAdapter;
31 import org.eclipse.swt.events.FocusEvent;
32 import org.eclipse.swt.events.FocusListener;
33 import org.eclipse.swt.events.KeyEvent;
34 import org.eclipse.swt.events.KeyListener;
35 import org.eclipse.swt.events.MouseEvent;
36 import org.eclipse.swt.events.MouseListener;
37 import org.eclipse.swt.events.MouseMoveListener;
38 import org.eclipse.swt.events.MouseTrackListener;
39 import org.eclipse.swt.events.MouseWheelListener;
40 import org.eclipse.swt.events.PaintEvent;
41 import org.eclipse.swt.events.PaintListener;
42 import org.eclipse.swt.graphics.Color;
43 import org.eclipse.swt.graphics.Font;
44 import org.eclipse.swt.graphics.FontData;
45 import org.eclipse.swt.graphics.GC;
46 import org.eclipse.swt.graphics.Image;
47 import org.eclipse.swt.layout.FillLayout;
48 import org.eclipse.swt.layout.GridData;
49 import org.eclipse.swt.layout.GridLayout;
50 import org.eclipse.swt.widgets.Canvas;
51 import org.eclipse.swt.widgets.Composite;
52 import org.eclipse.swt.widgets.Display;
53 import org.eclipse.swt.widgets.Label;
54 import org.eclipse.swt.widgets.Text;
55
56 /**
57 * Re-usable histogram widget.
58 *
59 * It has the following features:
60 * <ul>
61 * <li>Y-axis labels displaying min/max count values
62 * <li>X-axis labels displaying time range
63 * <li>a histogram displaying the distribution of values over time (note that
64 * the histogram might not necessarily fill the whole canvas)
65 * </ul>
66 * The widget also has 2 'markers' to identify:
67 * <ul>
68 * <li>a red dashed line over the bar that contains the currently selected event
69 * <li>a dark red dashed line that delimits the right end of the histogram (if
70 * it doesn't fill the canvas)
71 * </ul>
72 * Clicking on the histogram will select the current event at the mouse
73 * location.
74 * <p>
75 * Once the histogram is selected, there is some limited keyboard support:
76 * <ul>
77 * <li>Home: go to the first histogram bar
78 * <li>End: go to the last histogram bar
79 * <li>Left: go to the previous histogram
80 * <li>Right: go to the next histogram bar
81 * </ul>
82 * Finally, when the mouse hovers over the histogram, a tool tip showing the
83 * following information about the corresponding histogram bar time range:
84 * <ul>
85 * <li>start of the time range
86 * <li>end of the time range
87 * <li>number of events in that time range
88 * </ul>
89 *
90 * @version 1.1
91 * @author Francois Chouinard
92 */
93 public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseMoveListener, MouseTrackListener, IHistogramModelListener {
94
95 // ------------------------------------------------------------------------
96 // Constants
97 // ------------------------------------------------------------------------
98
99 // Histogram colors
100 private final Color fBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE);
101 private final Color fSelectionForegroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_BLUE);
102 private final Color fSelectionBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
103 private final Color fLastEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED);
104 private final Color fHistoBarColor = new Color(Display.getDefault(), 74, 112, 139);
105 private final Color fLostEventColor = new Color(Display.getCurrent(), 208, 62, 120);
106 private final Color fFillColor = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
107 private final Color fTimeRangeColor = new Color(Display.getCurrent(), 255, 128, 0);
108
109 // Drag states
110 /**
111 * No drag in progress
112 * @since 2.2
113 */
114 protected final int DRAG_NONE = 0;
115 /**
116 * Drag the selection
117 * @since 2.2
118 */
119 protected final int DRAG_SELECTION = 1;
120 /**
121 * Drag the time range
122 * @since 2.2
123 */
124 protected final int DRAG_RANGE = 2;
125 /**
126 * Drag the zoom range
127 * @since 2.2
128 */
129 protected final int DRAG_ZOOM = 3;
130
131 // ------------------------------------------------------------------------
132 // Attributes
133 // ------------------------------------------------------------------------
134
135 /**
136 * The parent TMF view.
137 */
138 protected TmfView fParentView;
139
140 private Composite fComposite;
141 private Font fFont;
142
143 // Histogram text fields
144 private Text fMaxNbEventsText;
145 private Text fMinNbEventsText;
146 private Text fTimeRangeStartText;
147 private Text fTimeRangeEndText;
148
149 /**
150 * Histogram drawing area
151 */
152 protected Canvas fCanvas;
153
154 /**
155 * The histogram data model.
156 */
157 protected final HistogramDataModel fDataModel;
158
159 /**
160 * The histogram data model scaled to current resolution and screen width.
161 */
162 protected HistogramScaledData fScaledData;
163
164 /**
165 * The current event value
166 */
167 protected long fCurrentEventTime = 0L;
168
169 /**
170 * The current selection begin time
171 */
172 private long fSelectionBegin = 0L;
173
174 /**
175 * The current selection end time
176 */
177 private long fSelectionEnd = 0L;
178
179 /**
180 * The drag state
181 * @see #DRAG_NONE
182 * @see #DRAG_SELECTION
183 * @see #DRAG_RANGE
184 * @see #DRAG_ZOOM
185 * @since 2.2
186 */
187 protected int fDragState = DRAG_NONE;
188
189 /**
190 * The button that started a mouse drag, or 0 if no drag in progress
191 * @since 2.2
192 */
193 protected int fDragButton = 0;
194
195 /**
196 * The bucket display offset
197 */
198 private int fOffset = 0;
199
200 // ------------------------------------------------------------------------
201 // Construction
202 // ------------------------------------------------------------------------
203
204 /**
205 * Full constructor.
206 *
207 * @param view A reference to the parent TMF view.
208 * @param parent A parent composite
209 */
210 public Histogram(final TmfView view, final Composite parent) {
211 fParentView = view;
212
213 fComposite = createWidget(parent);
214 fDataModel = new HistogramDataModel();
215 fDataModel.addHistogramListener(this);
216 clear();
217
218 fCanvas.addControlListener(this);
219 fCanvas.addPaintListener(this);
220 fCanvas.addKeyListener(this);
221 fCanvas.addMouseListener(this);
222 fCanvas.addMouseTrackListener(this);
223 fCanvas.addMouseMoveListener(this);
224
225 TmfSignalManager.register(this);
226 }
227
228 /**
229 * Dispose resources and unregisters listeners.
230 */
231 public void dispose() {
232 TmfSignalManager.deregister(this);
233
234 fHistoBarColor.dispose();
235 fLastEventColor.dispose();
236 fTimeRangeColor.dispose();
237 fDataModel.removeHistogramListener(this);
238 }
239
240 private Composite createWidget(final Composite parent) {
241
242 final Color labelColor = parent.getBackground();
243 fFont = adjustFont(parent);
244
245 final int initalWidth = 10;
246
247 // --------------------------------------------------------------------
248 // Define the histogram
249 // --------------------------------------------------------------------
250
251 final GridLayout gridLayout = new GridLayout();
252 gridLayout.numColumns = 3;
253 gridLayout.marginHeight = 0;
254 gridLayout.marginWidth = 0;
255 gridLayout.marginTop = 0;
256 gridLayout.horizontalSpacing = 0;
257 gridLayout.verticalSpacing = 0;
258 gridLayout.marginLeft = 0;
259 gridLayout.marginRight = 0;
260 final Composite composite = new Composite(parent, SWT.FILL);
261 composite.setLayout(gridLayout);
262
263 // Use all the horizontal space
264 GridData gridData = new GridData();
265 gridData.horizontalAlignment = SWT.FILL;
266 gridData.verticalAlignment = SWT.FILL;
267 gridData.grabExcessHorizontalSpace = true;
268 gridData.grabExcessVerticalSpace = true;
269 composite.setLayoutData(gridData);
270
271 // Y-axis max event
272 gridData = new GridData();
273 gridData.horizontalAlignment = SWT.RIGHT;
274 gridData.verticalAlignment = SWT.TOP;
275 fMaxNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT);
276 fMaxNbEventsText.setFont(fFont);
277 fMaxNbEventsText.setBackground(labelColor);
278 fMaxNbEventsText.setEditable(false);
279 fMaxNbEventsText.setText("0"); //$NON-NLS-1$
280 fMaxNbEventsText.setLayoutData(gridData);
281
282 // Histogram itself
283 Composite canvasComposite = new Composite(composite, SWT.BORDER);
284 gridData = new GridData();
285 gridData.horizontalSpan = 2;
286 gridData.verticalSpan = 2;
287 gridData.horizontalAlignment = SWT.FILL;
288 gridData.verticalAlignment = SWT.FILL;
289 gridData.heightHint = 0;
290 gridData.widthHint = 0;
291 gridData.grabExcessHorizontalSpace = true;
292 gridData.grabExcessVerticalSpace = true;
293 canvasComposite.setLayoutData(gridData);
294 canvasComposite.setLayout(new FillLayout());
295 fCanvas = new Canvas(canvasComposite, SWT.DOUBLE_BUFFERED);
296
297 // Y-axis min event (always 0...)
298 gridData = new GridData();
299 gridData.horizontalAlignment = SWT.RIGHT;
300 gridData.verticalAlignment = SWT.BOTTOM;
301 fMinNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT);
302 fMinNbEventsText.setFont(fFont);
303 fMinNbEventsText.setBackground(labelColor);
304 fMinNbEventsText.setEditable(false);
305 fMinNbEventsText.setText("0"); //$NON-NLS-1$
306 fMinNbEventsText.setLayoutData(gridData);
307
308 // Dummy cell
309 gridData = new GridData(initalWidth, SWT.DEFAULT);
310 gridData.horizontalAlignment = SWT.RIGHT;
311 gridData.verticalAlignment = SWT.BOTTOM;
312 final Label dummyLabel = new Label(composite, SWT.NONE);
313 dummyLabel.setLayoutData(gridData);
314
315 // Window range start time
316 gridData = new GridData();
317 gridData.horizontalAlignment = SWT.LEFT;
318 gridData.verticalAlignment = SWT.BOTTOM;
319 fTimeRangeStartText = new Text(composite, SWT.READ_ONLY);
320 fTimeRangeStartText.setFont(fFont);
321 fTimeRangeStartText.setBackground(labelColor);
322 fTimeRangeStartText.setLayoutData(gridData);
323
324 // Window range end time
325 gridData = new GridData();
326 gridData.horizontalAlignment = SWT.RIGHT;
327 gridData.verticalAlignment = SWT.BOTTOM;
328 fTimeRangeEndText = new Text(composite, SWT.READ_ONLY);
329 fTimeRangeEndText.setFont(fFont);
330 fTimeRangeEndText.setBackground(labelColor);
331 fTimeRangeEndText.setLayoutData(gridData);
332
333 FocusListener listener = new FocusAdapter() {
334 @Override
335 public void focusGained(FocusEvent e) {
336 fCanvas.setFocus();
337 }
338 };
339 fMaxNbEventsText.addFocusListener(listener);
340 fMinNbEventsText.addFocusListener(listener);
341 fTimeRangeStartText.addFocusListener(listener);
342 fTimeRangeEndText.addFocusListener(listener);
343
344 return composite;
345 }
346
347 private static Font adjustFont(final Composite composite) {
348 // Reduce font size for a more pleasing rendering
349 final int fontSizeAdjustment = -2;
350 final Font font = composite.getFont();
351 final FontData fontData = font.getFontData()[0];
352 return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle());
353 }
354
355 // ------------------------------------------------------------------------
356 // Accessors
357 // ------------------------------------------------------------------------
358
359 /**
360 * Returns the start time (equal first bucket time)
361 * @return the start time.
362 */
363 public long getStartTime() {
364 return fDataModel.getFirstBucketTime();
365 }
366
367 /**
368 * Returns the end time.
369 * @return the end time.
370 */
371 public long getEndTime() {
372 return fDataModel.getEndTime();
373 }
374
375 /**
376 * Returns the time limit (end of last bucket)
377 * @return the time limit.
378 */
379 public long getTimeLimit() {
380 return fDataModel.getTimeLimit();
381 }
382
383 /**
384 * Returns a data model reference.
385 * @return data model.
386 */
387 public HistogramDataModel getDataModel() {
388 return fDataModel;
389 }
390
391 /**
392 * Returns the text control for the maximum of events in one bar
393 *
394 * @return the text control
395 * @since 2.2
396 */
397 public Text getMaxNbEventsText() {
398 return fMaxNbEventsText;
399 }
400
401 // ------------------------------------------------------------------------
402 // Operations
403 // ------------------------------------------------------------------------
404 /**
405 * Updates the time range.
406 * @param startTime A start time
407 * @param endTime A end time.
408 */
409 public void updateTimeRange(long startTime, long endTime) {
410 if (fDragState == DRAG_NONE) {
411 ((HistogramView) fParentView).updateTimeRange(startTime, endTime);
412 }
413 }
414
415 /**
416 * Clear the histogram and reset the data
417 */
418 public void clear() {
419 fDataModel.clear();
420 fDragState = DRAG_NONE;
421 fDragButton = 0;
422 synchronized (fDataModel) {
423 fScaledData = null;
424 }
425 }
426
427 /**
428 * Increase the histogram bucket corresponding to [timestamp]
429 *
430 * @param eventCount The new event count
431 * @param timestamp The latest timestamp
432 */
433 public void countEvent(final long eventCount, final long timestamp) {
434 fDataModel.countEvent(eventCount, timestamp);
435 }
436
437 /**
438 * Sets the current event time and refresh the display
439 *
440 * @param timestamp The time of the current event
441 * @deprecated As of 2.1, use {@link #setSelection(long, long)}
442 */
443 @Deprecated
444 public void setCurrentEvent(final long timestamp) {
445 fSelectionBegin = (timestamp > 0) ? timestamp : 0;
446 fSelectionEnd = (timestamp > 0) ? timestamp : 0;
447 fDataModel.setSelectionNotifyListeners(timestamp, timestamp);
448 }
449
450 /**
451 * Sets the current selection time range and refresh the display
452 *
453 * @param beginTime The begin time of the current selection
454 * @param endTime The end time of the current selection
455 * @since 2.1
456 */
457 public void setSelection(final long beginTime, final long endTime) {
458 fSelectionBegin = (beginTime > 0) ? beginTime : 0;
459 fSelectionEnd = (endTime > 0) ? endTime : 0;
460 fDataModel.setSelectionNotifyListeners(beginTime, endTime);
461 }
462
463 /**
464 * Computes the timestamp of the bucket at [offset]
465 *
466 * @param offset offset from the left on the histogram
467 * @return the start timestamp of the corresponding bucket
468 */
469 public synchronized long getTimestamp(final int offset) {
470 assert offset > 0 && offset < fScaledData.fWidth;
471 try {
472 return fScaledData.fFirstBucketTime + fScaledData.fBucketDuration * offset;
473 } catch (final Exception e) {
474 return 0; // TODO: Fix that racing condition (NPE)
475 }
476 }
477
478 /**
479 * Computes the offset of the timestamp in the histogram
480 *
481 * @param timestamp the timestamp
482 * @return the offset of the corresponding bucket (-1 if invalid)
483 */
484 public synchronized int getOffset(final long timestamp) {
485 if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) {
486 return -1;
487 }
488 return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration);
489 }
490
491 /**
492 * Set the bucket display offset
493 *
494 * @param offset
495 * the bucket display offset
496 * @since 2.2
497 */
498 protected void setOffset(final int offset) {
499 fOffset = offset;
500 }
501
502 /**
503 * Move the currently selected bar cursor to a non-empty bucket.
504 *
505 * @param keyCode the SWT key code
506 */
507 protected void moveCursor(final int keyCode) {
508
509 int index;
510 switch (keyCode) {
511
512 case SWT.HOME:
513 index = 0;
514 while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0) {
515 index++;
516 }
517 if (index < fScaledData.fLastBucket) {
518 fScaledData.fSelectionBeginBucket = index;
519 }
520 break;
521
522 case SWT.ARROW_RIGHT:
523 index = Math.max(0, fScaledData.fSelectionBeginBucket + 1);
524 while (index < fScaledData.fWidth && fScaledData.fData[index] == 0) {
525 index++;
526 }
527 if (index < fScaledData.fLastBucket) {
528 fScaledData.fSelectionBeginBucket = index;
529 }
530 break;
531
532 case SWT.END:
533 index = fScaledData.fLastBucket;
534 while (index >= 0 && fScaledData.fData[index] == 0) {
535 index--;
536 }
537 if (index >= 0) {
538 fScaledData.fSelectionBeginBucket = index;
539 }
540 break;
541
542 case SWT.ARROW_LEFT:
543 index = Math.min(fScaledData.fLastBucket - 1, fScaledData.fSelectionBeginBucket - 1);
544 while (index >= 0 && fScaledData.fData[index] == 0) {
545 index--;
546 }
547 if (index >= 0) {
548 fScaledData.fSelectionBeginBucket = index;
549 }
550 break;
551
552 default:
553 return;
554 }
555
556 fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket;
557 fSelectionBegin = getTimestamp(fScaledData.fSelectionBeginBucket);
558 fSelectionEnd = fSelectionBegin;
559 updateSelectionTime();
560 }
561
562 /**
563 * Refresh the histogram display
564 */
565 @Override
566 public void modelUpdated() {
567 if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) {
568 fCanvas.getDisplay().asyncExec(new Runnable() {
569 @Override
570 public void run() {
571 if (!fCanvas.isDisposed()) {
572 // Retrieve and normalize the data
573 final int canvasWidth = fCanvas.getBounds().width;
574 final int canvasHeight = fCanvas.getBounds().height;
575 if (canvasWidth <= 0 || canvasHeight <= 0) {
576 return;
577 }
578 fDataModel.setSelection(fSelectionBegin, fSelectionEnd);
579 fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, 1);
580 synchronized (fDataModel) {
581 if (fScaledData != null) {
582 fCanvas.redraw();
583 // Display histogram and update X-,Y-axis labels
584 updateRangeTextControls();
585 long maxNbEvents = HistogramScaledData.hideLostEvents ? fScaledData.fMaxValue : fScaledData.fMaxCombinedValue;
586 fMaxNbEventsText.setText(Long.toString(maxNbEvents));
587 // The Y-axis area might need to be re-sized
588 GridData gd = (GridData) fMaxNbEventsText.getLayoutData();
589 gd.widthHint = Math.max(gd.widthHint, fMaxNbEventsText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x);
590 fMaxNbEventsText.getParent().layout();
591 }
592 }
593 }
594 }
595 });
596 }
597 }
598
599 /**
600 * Add a mouse wheel listener to the histogram
601 * @param listener the mouse wheel listener
602 * @since 2.0
603 */
604 public void addMouseWheelListener(MouseWheelListener listener) {
605 fCanvas.addMouseWheelListener(listener);
606 }
607
608 /**
609 * Remove a mouse wheel listener from the histogram
610 * @param listener the mouse wheel listener
611 * @since 2.0
612 */
613 public void removeMouseWheelListener(MouseWheelListener listener) {
614 fCanvas.removeMouseWheelListener(listener);
615 }
616
617 // ------------------------------------------------------------------------
618 // Helper functions
619 // ------------------------------------------------------------------------
620
621 private void updateSelectionTime() {
622 if (fSelectionBegin > fSelectionEnd) {
623 long end = fSelectionBegin;
624 fSelectionBegin = fSelectionEnd;
625 fSelectionEnd = end;
626 }
627 ((HistogramView) fParentView).updateSelectionTime(fSelectionBegin, fSelectionEnd);
628 }
629
630 /**
631 * Update the range text controls
632 */
633 private void updateRangeTextControls() {
634 if (fDataModel.getStartTime() < fDataModel.getEndTime()) {
635 fTimeRangeStartText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getStartTime()));
636 fTimeRangeEndText.setText(TmfTimestampFormat.getDefaulTimeFormat().format(fDataModel.getEndTime()));
637 } else {
638 fTimeRangeStartText.setText(""); //$NON-NLS-1$
639 fTimeRangeEndText.setText(""); //$NON-NLS-1$
640 }
641 }
642
643 // ------------------------------------------------------------------------
644 // PaintListener
645 // ------------------------------------------------------------------------
646 /**
647 * Image key string for the canvas.
648 */
649 protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$
650
651 @Override
652 public void paintControl(final PaintEvent event) {
653
654 // Get the geometry
655 final int canvasWidth = fCanvas.getBounds().width;
656 final int canvasHeight = fCanvas.getBounds().height;
657
658 // Make sure we have something to draw upon
659 if (canvasWidth <= 0 || canvasHeight <= 0) {
660 return;
661 }
662
663 // Retrieve image; re-create only if necessary
664 Image image = (Image) fCanvas.getData(IMAGE_KEY);
665 if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) {
666 image = new Image(event.display, canvasWidth, canvasHeight);
667 fCanvas.setData(IMAGE_KEY, image);
668 }
669
670 // Draw the histogram on its canvas
671 final GC imageGC = new GC(image);
672 formatImage(imageGC, image);
673 event.gc.drawImage(image, 0, 0);
674 imageGC.dispose();
675 }
676
677 private void formatImage(final GC imageGC, final Image image) {
678
679 if (fScaledData == null) {
680 return;
681 }
682
683 final HistogramScaledData scaledData = new HistogramScaledData(fScaledData);
684
685 try {
686 // Get drawing boundaries
687 final int width = image.getBounds().width;
688 final int height = image.getBounds().height;
689
690 // Turn off anti-aliasing
691 int aliasing = imageGC.getAntialias();
692 imageGC.setAntialias(SWT.OFF);
693
694 // Clear the drawing area
695 imageGC.setBackground(fBackgroundColor);
696 imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1);
697
698 // Draw the histogram bars
699 final int limit = width < scaledData.fWidth ? width : scaledData.fWidth;
700 double factor = HistogramScaledData.hideLostEvents ? scaledData.fScalingFactor : scaledData.fScalingFactorCombined;
701 for (int i = 0; i < limit; i++) {
702 final int value = (int) Math.ceil(scaledData.fData[i] * factor);
703 int x = i + fOffset;
704
705 // in Linux, the last pixel in a line is not drawn,
706 // so draw lost events first, one pixel too far
707 if (!HistogramScaledData.hideLostEvents) {
708 imageGC.setForeground(fLostEventColor);
709 final int lostEventValue = (int) Math.ceil(scaledData.fLostEventsData[i] * factor);
710 if (lostEventValue != 0) {
711 // drawing a line is inclusive, so we should remove 1 from y2
712 // but we don't because Linux
713 imageGC.drawLine(x, height - value - lostEventValue, x, height - value);
714 }
715 }
716
717 // then draw normal events second, to overwrite that extra pixel
718 imageGC.setForeground(fHistoBarColor);
719 imageGC.drawLine(x, height - value, x, height);
720 }
721
722 // Draw the selection bars
723 int alpha = imageGC.getAlpha();
724 imageGC.setAlpha(100);
725 imageGC.setForeground(fSelectionForegroundColor);
726 imageGC.setBackground(fSelectionBackgroundColor);
727 final int beginBucket = scaledData.fSelectionBeginBucket + fOffset;
728 if (beginBucket >= 0 && beginBucket < limit) {
729 imageGC.drawLine(beginBucket, 0, beginBucket, height);
730 }
731 final int endBucket = scaledData.fSelectionEndBucket + fOffset;
732 if (endBucket >= 0 && endBucket < limit && endBucket != beginBucket) {
733 imageGC.drawLine(endBucket, 0, endBucket, height);
734 }
735 if (Math.abs(endBucket - beginBucket) > 1) {
736 if (endBucket > beginBucket) {
737 imageGC.fillRectangle(beginBucket + 1, 0, endBucket - beginBucket - 1, height);
738 } else {
739 imageGC.fillRectangle(endBucket + 1, 0, beginBucket - endBucket - 1, height);
740 }
741 }
742 imageGC.setAlpha(alpha);
743
744 // Add a dashed line as a delimiter
745 int delimiterIndex = (int) ((getDataModel().getEndTime() - scaledData.getFirstBucketTime()) / scaledData.fBucketDuration) + 1;
746 drawDelimiter(imageGC, fLastEventColor, height, delimiterIndex);
747
748 // Fill the area to the right of delimiter with background color
749 imageGC.setBackground(fFillColor);
750 imageGC.fillRectangle(delimiterIndex + 1, 0, width - (delimiterIndex + 1), height);
751
752 // Restore anti-aliasing
753 imageGC.setAntialias(aliasing);
754
755 } catch (final Exception e) {
756 // Do nothing
757 }
758 }
759
760 private static void drawDelimiter(final GC imageGC, final Color color,
761 final int height, final int index) {
762 imageGC.setBackground(color);
763 final int dash = height / 4;
764 imageGC.fillRectangle(index, 0 * dash, 1, dash - 1);
765 imageGC.fillRectangle(index, 1 * dash, 1, dash - 1);
766 imageGC.fillRectangle(index, 2 * dash, 1, dash - 1);
767 imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash);
768 }
769
770 /**
771 * Draw a time range window
772 *
773 * @param imageGC
774 * the GC
775 * @param rangeStartTime
776 * the range start time
777 * @param rangeDuration
778 * the range duration
779 * @since 2.2
780 */
781 protected void drawTimeRangeWindow(GC imageGC, long rangeStartTime, long rangeDuration) {
782
783 if (fScaledData == null) {
784 return;
785 }
786
787 // Map times to histogram coordinates
788 long bucketSpan = Math.max(fScaledData.fBucketDuration, 1);
789 long startTime = Math.min(rangeStartTime, rangeStartTime + rangeDuration);
790 int rangeWidth = (int) (Math.abs(rangeDuration) / bucketSpan);
791
792 int left = (int) ((startTime - fDataModel.getFirstBucketTime()) / bucketSpan);
793 int right = left + rangeWidth;
794 int center = (left + right) / 2;
795 int height = fCanvas.getSize().y;
796 int arc = Math.min(15, rangeWidth);
797
798 // Draw the selection window
799 imageGC.setForeground(fTimeRangeColor);
800 imageGC.setLineWidth(1);
801 imageGC.setLineStyle(SWT.LINE_SOLID);
802 imageGC.drawRoundRectangle(left, 0, rangeWidth, height - 1, arc, arc);
803
804 // Fill the selection window
805 imageGC.setBackground(fTimeRangeColor);
806 imageGC.setAlpha(35);
807 imageGC.fillRoundRectangle(left + 1, 1, rangeWidth - 1, height - 2, arc, arc);
808 imageGC.setAlpha(255);
809
810 // Draw the cross hair
811 imageGC.setForeground(fTimeRangeColor);
812 imageGC.setLineWidth(1);
813 imageGC.setLineStyle(SWT.LINE_SOLID);
814
815 int chHalfWidth = ((rangeWidth < 60) ? (rangeWidth * 2) / 3 : 40) / 2;
816 imageGC.drawLine(center - chHalfWidth, height / 2, center + chHalfWidth, height / 2);
817 imageGC.drawLine(center, (height / 2) - chHalfWidth, center, (height / 2) + chHalfWidth);
818 }
819
820 // ------------------------------------------------------------------------
821 // KeyListener
822 // ------------------------------------------------------------------------
823
824 @Override
825 public void keyPressed(final KeyEvent event) {
826 moveCursor(event.keyCode);
827 }
828
829 @Override
830 public void keyReleased(final KeyEvent event) {
831 }
832
833 // ------------------------------------------------------------------------
834 // MouseListener
835 // ------------------------------------------------------------------------
836
837 @Override
838 public void mouseDoubleClick(final MouseEvent event) {
839 }
840
841 @Override
842 public void mouseDown(final MouseEvent event) {
843 if (fScaledData != null && event.button == 1 && fDragState == DRAG_NONE && fDataModel.getStartTime() < fDataModel.getEndTime()) {
844 fDragState = DRAG_SELECTION;
845 fDragButton = event.button;
846 if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) {
847 if (Math.abs(event.x - fScaledData.fSelectionBeginBucket) < Math.abs(event.x - fScaledData.fSelectionEndBucket)) {
848 fScaledData.fSelectionBeginBucket = fScaledData.fSelectionEndBucket;
849 fSelectionBegin = fSelectionEnd;
850 }
851 fSelectionEnd = Math.min(getTimestamp(event.x), getEndTime());
852 fScaledData.fSelectionEndBucket = (int) ((fSelectionEnd - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration);
853 } else {
854 fSelectionBegin = Math.min(getTimestamp(event.x), getEndTime());
855 fScaledData.fSelectionBeginBucket = (int) ((fSelectionBegin - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration);
856 fSelectionEnd = fSelectionBegin;
857 fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket;
858 }
859 fCanvas.redraw();
860 }
861 }
862
863 @Override
864 public void mouseUp(final MouseEvent event) {
865 if (fDragState == DRAG_SELECTION && event.button == fDragButton) {
866 fDragState = DRAG_NONE;
867 fDragButton = 0;
868 updateSelectionTime();
869 }
870 }
871
872 // ------------------------------------------------------------------------
873 // MouseMoveListener
874 // ------------------------------------------------------------------------
875
876 /**
877 * @since 2.2
878 */
879 @Override
880 public void mouseMove(MouseEvent event) {
881 if (fDragState == DRAG_SELECTION && fDataModel.getStartTime() < fDataModel.getEndTime()) {
882 fSelectionEnd = Math.max(getStartTime(), Math.min(getEndTime(), getTimestamp(event.x)));
883 fScaledData.fSelectionEndBucket = (int) ((fSelectionEnd - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration);
884 fCanvas.redraw();
885 }
886 }
887
888 // ------------------------------------------------------------------------
889 // MouseTrackListener
890 // ------------------------------------------------------------------------
891
892 @Override
893 public void mouseEnter(final MouseEvent event) {
894 }
895
896 @Override
897 public void mouseExit(final MouseEvent event) {
898 }
899
900 @Override
901 public void mouseHover(final MouseEvent event) {
902 if (fDataModel.getStartTime() < fDataModel.getEndTime() && fScaledData != null) {
903 int delimiterIndex = (int) ((fDataModel.getEndTime() - fScaledData.getFirstBucketTime()) / fScaledData.fBucketDuration) + 1;
904 if (event.x < delimiterIndex) {
905 final String tooltip = formatToolTipLabel(event.x - fOffset);
906 fCanvas.setToolTipText(tooltip);
907 return;
908 }
909 }
910 fCanvas.setToolTipText(null);
911 }
912
913 private String formatToolTipLabel(final int index) {
914 long startTime = fScaledData.getBucketStartTime(index);
915 // negative values are possible if time values came into the model in decreasing order
916 if (startTime < 0) {
917 startTime = 0;
918 }
919 final long endTime = fScaledData.getBucketEndTime(index);
920 final int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0;
921 final String newLine = System.getProperty("line.separator"); //$NON-NLS-1$
922 final StringBuffer buffer = new StringBuffer();
923 int selectionBeginBucket = Math.min(fScaledData.fSelectionBeginBucket, fScaledData.fSelectionEndBucket);
924 int selectionEndBucket = Math.max(fScaledData.fSelectionBeginBucket, fScaledData.fSelectionEndBucket);
925 if (selectionBeginBucket <= index && index <= selectionEndBucket && fSelectionBegin != fSelectionEnd) {
926 TmfTimestampDelta delta = new TmfTimestampDelta(Math.abs(fSelectionEnd - fSelectionBegin), ITmfTimestamp.NANOSECOND_SCALE);
927 buffer.append(NLS.bind(Messages.Histogram_selectionSpanToolTip, delta.toString()));
928 buffer.append(newLine);
929 }
930 buffer.append(NLS.bind(Messages.Histogram_bucketRangeToolTip,
931 new TmfTimestamp(startTime, ITmfTimestamp.NANOSECOND_SCALE).toString(),
932 new TmfTimestamp(endTime, ITmfTimestamp.NANOSECOND_SCALE).toString()));
933 buffer.append(newLine);
934 buffer.append(NLS.bind(Messages.Histogram_eventCountToolTip, nbEvents));
935 if (!HistogramScaledData.hideLostEvents) {
936 final int nbLostEvents = (index >= 0) ? fScaledData.fLostEventsData[index] : 0;
937 buffer.append(newLine);
938 buffer.append(NLS.bind(Messages.Histogram_lostEventCountToolTip, nbLostEvents));
939 }
940 return buffer.toString();
941 }
942
943 // ------------------------------------------------------------------------
944 // ControlListener
945 // ------------------------------------------------------------------------
946
947 @Override
948 public void controlMoved(final ControlEvent event) {
949 fDataModel.complete();
950 }
951
952 @Override
953 public void controlResized(final ControlEvent event) {
954 fDataModel.complete();
955 }
956
957 // ------------------------------------------------------------------------
958 // Signal Handlers
959 // ------------------------------------------------------------------------
960
961 /**
962 * Format the timestamp and update the display
963 *
964 * @param signal
965 * the incoming signal
966 * @since 2.0
967 */
968 @TmfSignalHandler
969 public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) {
970 updateRangeTextControls();
971
972 fComposite.layout();
973 }
974
975 }
This page took 0.04984 seconds and 4 git commands to generate.