1 /*******************************************************************************
2 * Copyright (c) 2011, 2014 Ericsson
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
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 * Xavier Raynaud - Support multi-trace coloring
15 *******************************************************************************/
17 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.histogram
;
19 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfSignalHandler
;
20 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfSignalManager
;
21 import org
.eclipse
.linuxtools
.tmf
.core
.signal
.TmfTimestampFormatUpdateSignal
;
22 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.ITmfTimestamp
;
23 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.TmfTimestamp
;
24 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.TmfTimestampDelta
;
25 import org
.eclipse
.linuxtools
.tmf
.core
.timestamp
.TmfTimestampFormat
;
26 import org
.eclipse
.linuxtools
.tmf
.ui
.views
.TmfView
;
27 import org
.eclipse
.osgi
.util
.NLS
;
28 import org
.eclipse
.swt
.SWT
;
29 import org
.eclipse
.swt
.events
.ControlEvent
;
30 import org
.eclipse
.swt
.events
.ControlListener
;
31 import org
.eclipse
.swt
.events
.DisposeEvent
;
32 import org
.eclipse
.swt
.events
.DisposeListener
;
33 import org
.eclipse
.swt
.events
.FocusAdapter
;
34 import org
.eclipse
.swt
.events
.FocusEvent
;
35 import org
.eclipse
.swt
.events
.FocusListener
;
36 import org
.eclipse
.swt
.events
.KeyEvent
;
37 import org
.eclipse
.swt
.events
.KeyListener
;
38 import org
.eclipse
.swt
.events
.MouseEvent
;
39 import org
.eclipse
.swt
.events
.MouseListener
;
40 import org
.eclipse
.swt
.events
.MouseMoveListener
;
41 import org
.eclipse
.swt
.events
.MouseTrackListener
;
42 import org
.eclipse
.swt
.events
.MouseWheelListener
;
43 import org
.eclipse
.swt
.events
.PaintEvent
;
44 import org
.eclipse
.swt
.events
.PaintListener
;
45 import org
.eclipse
.swt
.graphics
.Color
;
46 import org
.eclipse
.swt
.graphics
.Font
;
47 import org
.eclipse
.swt
.graphics
.FontData
;
48 import org
.eclipse
.swt
.graphics
.GC
;
49 import org
.eclipse
.swt
.graphics
.Image
;
50 import org
.eclipse
.swt
.layout
.FillLayout
;
51 import org
.eclipse
.swt
.layout
.GridData
;
52 import org
.eclipse
.swt
.layout
.GridLayout
;
53 import org
.eclipse
.swt
.widgets
.Canvas
;
54 import org
.eclipse
.swt
.widgets
.Composite
;
55 import org
.eclipse
.swt
.widgets
.Display
;
56 import org
.eclipse
.swt
.widgets
.Label
;
57 import org
.eclipse
.swt
.widgets
.Text
;
60 * Re-usable histogram widget.
62 * It has the following features:
64 * <li>Y-axis labels displaying min/max count values
65 * <li>X-axis labels displaying time range
66 * <li>a histogram displaying the distribution of values over time (note that
67 * the histogram might not necessarily fill the whole canvas)
69 * The widget also has 2 'markers' to identify:
71 * <li>a red dashed line over the bar that contains the currently selected event
72 * <li>a dark red dashed line that delimits the right end of the histogram (if
73 * it doesn't fill the canvas)
75 * Clicking on the histogram will select the current event at the mouse
78 * Once the histogram is selected, there is some limited keyboard support:
80 * <li>Home: go to the first histogram bar
81 * <li>End: go to the last histogram bar
82 * <li>Left: go to the previous histogram
83 * <li>Right: go to the next histogram bar
85 * Finally, when the mouse hovers over the histogram, a tool tip showing the
86 * following information about the corresponding histogram bar time range:
88 * <li>start of the time range
89 * <li>end of the time range
90 * <li>number of events in that time range
94 * @author Francois Chouinard
96 public abstract class Histogram
implements ControlListener
, PaintListener
, KeyListener
, MouseListener
, MouseMoveListener
, MouseTrackListener
, IHistogramModelListener
{
98 // ------------------------------------------------------------------------
100 // ------------------------------------------------------------------------
104 // System colors, they do not need to be disposed
105 private final Color fBackgroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WHITE
);
106 private final Color fSelectionForegroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_BLUE
);
107 private final Color fSelectionBackgroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WIDGET_BACKGROUND
);
108 private final Color fLastEventColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_DARK_RED
);
109 private final Color fFillColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WIDGET_BACKGROUND
);
111 // Application colors, they need to be disposed
112 private final Color
[] fHistoBarColors
= new Color
[] {new Color(Display
.getDefault(), 90, 90, 255), // blue
113 new Color(Display
.getDefault(), 0, 240, 0), // green
114 new Color(Display
.getDefault(), 255, 0, 0), // red
115 new Color(Display
.getDefault(), 0, 255, 255), // cyan
116 new Color(Display
.getDefault(), 255, 80, 255), // magenta
117 new Color(Display
.getDefault(), 200, 200, 0), // yellow
118 new Color(Display
.getDefault(), 200, 150, 0), // brown
119 new Color(Display
.getDefault(), 150, 255, 150), // light green
120 new Color(Display
.getDefault(), 200, 80, 80), // dark red
121 new Color(Display
.getDefault(), 30, 150, 150), // dark cyan
122 new Color(Display
.getDefault(), 200, 200, 255), // light blue
123 new Color(Display
.getDefault(), 0, 120, 0), // dark green
124 new Color(Display
.getDefault(), 255, 150, 150), // lighter red
125 new Color(Display
.getDefault(), 140, 80, 140), // dark magenta
126 new Color(Display
.getDefault(), 150, 100, 50), // brown
127 new Color(Display
.getDefault(), 255, 80, 80), // light red
128 new Color(Display
.getDefault(), 200, 200, 200), // light grey
129 new Color(Display
.getDefault(), 255, 200, 80), // orange
130 new Color(Display
.getDefault(), 255, 255, 80), // pale yellow
131 new Color(Display
.getDefault(), 255, 200, 200), // pale red
132 new Color(Display
.getDefault(), 255, 200, 255), // pale magenta
133 new Color(Display
.getDefault(), 255, 255, 200), // pale pale yellow
134 new Color(Display
.getDefault(), 200, 255, 255), // pale pale blue
136 private final Color fTimeRangeColor
= new Color(Display
.getCurrent(), 255, 128, 0);
137 private final Color fLostEventColor
= new Color(Display
.getCurrent(), 208, 62, 120);
141 * No drag in progress
144 protected final int DRAG_NONE
= 0;
149 protected final int DRAG_SELECTION
= 1;
151 * Drag the time range
154 protected final int DRAG_RANGE
= 2;
156 * Drag the zoom range
159 protected final int DRAG_ZOOM
= 3;
161 // ------------------------------------------------------------------------
163 // ------------------------------------------------------------------------
166 * The parent TMF view.
168 protected TmfView fParentView
;
170 private Composite fComposite
;
173 // Histogram text fields
174 private Text fMaxNbEventsText
;
175 private Text fMinNbEventsText
;
176 private Text fTimeRangeStartText
;
177 private Text fTimeRangeEndText
;
180 * Histogram drawing area
182 protected Canvas fCanvas
;
185 * The histogram data model.
187 protected final HistogramDataModel fDataModel
;
190 * The histogram data model scaled to current resolution and screen width.
192 protected HistogramScaledData fScaledData
;
195 * The current event value
197 protected long fCurrentEventTime
= 0L;
200 * The current selection begin time
202 private long fSelectionBegin
= 0L;
205 * The current selection end time
207 private long fSelectionEnd
= 0L;
212 * @see #DRAG_SELECTION
217 protected int fDragState
= DRAG_NONE
;
220 * The button that started a mouse drag, or 0 if no drag in progress
223 protected int fDragButton
= 0;
226 * The bucket display offset
228 private int fOffset
= 0;
231 * show the traces or not
234 static boolean showTraces
= true;
236 // ------------------------------------------------------------------------
238 // ------------------------------------------------------------------------
243 * @param view A reference to the parent TMF view.
244 * @param parent A parent composite
246 public Histogram(final TmfView view
, final Composite parent
) {
249 fComposite
= createWidget(parent
);
250 fDataModel
= new HistogramDataModel();
251 fDataModel
.addHistogramListener(this);
254 fCanvas
.addControlListener(this);
255 fCanvas
.addPaintListener(this);
256 fCanvas
.addKeyListener(this);
257 fCanvas
.addMouseListener(this);
258 fCanvas
.addMouseTrackListener(this);
259 fCanvas
.addMouseMoveListener(this);
261 TmfSignalManager
.register(this);
265 * Dispose resources and unregisters listeners.
267 public void dispose() {
268 TmfSignalManager
.deregister(this);
269 fLostEventColor
.dispose();
270 for (Color c
: fHistoBarColors
) {
273 fTimeRangeColor
.dispose();
275 fDataModel
.removeHistogramListener(this);
278 private Composite
createWidget(final Composite parent
) {
280 final Color labelColor
= parent
.getBackground();
281 fFont
= adjustFont(parent
);
283 final int initalWidth
= 10;
285 // --------------------------------------------------------------------
286 // Define the histogram
287 // --------------------------------------------------------------------
289 final GridLayout gridLayout
= new GridLayout();
290 gridLayout
.numColumns
= 3;
291 gridLayout
.marginHeight
= 0;
292 gridLayout
.marginWidth
= 0;
293 gridLayout
.marginTop
= 0;
294 gridLayout
.horizontalSpacing
= 0;
295 gridLayout
.verticalSpacing
= 0;
296 gridLayout
.marginLeft
= 0;
297 gridLayout
.marginRight
= 0;
298 final Composite composite
= new Composite(parent
, SWT
.FILL
);
299 composite
.setLayout(gridLayout
);
301 // Use all the horizontal space
302 GridData gridData
= new GridData();
303 gridData
.horizontalAlignment
= SWT
.FILL
;
304 gridData
.verticalAlignment
= SWT
.FILL
;
305 gridData
.grabExcessHorizontalSpace
= true;
306 gridData
.grabExcessVerticalSpace
= true;
307 composite
.setLayoutData(gridData
);
310 gridData
= new GridData();
311 gridData
.horizontalAlignment
= SWT
.RIGHT
;
312 gridData
.verticalAlignment
= SWT
.TOP
;
313 fMaxNbEventsText
= new Text(composite
, SWT
.READ_ONLY
| SWT
.RIGHT
);
314 fMaxNbEventsText
.setFont(fFont
);
315 fMaxNbEventsText
.setBackground(labelColor
);
316 fMaxNbEventsText
.setEditable(false);
317 fMaxNbEventsText
.setText("0"); //$NON-NLS-1$
318 fMaxNbEventsText
.setLayoutData(gridData
);
321 Composite canvasComposite
= new Composite(composite
, SWT
.BORDER
);
322 gridData
= new GridData();
323 gridData
.horizontalSpan
= 2;
324 gridData
.verticalSpan
= 2;
325 gridData
.horizontalAlignment
= SWT
.FILL
;
326 gridData
.verticalAlignment
= SWT
.FILL
;
327 gridData
.heightHint
= 0;
328 gridData
.widthHint
= 0;
329 gridData
.grabExcessHorizontalSpace
= true;
330 gridData
.grabExcessVerticalSpace
= true;
331 canvasComposite
.setLayoutData(gridData
);
332 canvasComposite
.setLayout(new FillLayout());
333 fCanvas
= new Canvas(canvasComposite
, SWT
.DOUBLE_BUFFERED
);
334 fCanvas
.addDisposeListener(new DisposeListener() {
336 public void widgetDisposed(DisposeEvent e
) {
337 Object image
= fCanvas
.getData(IMAGE_KEY
);
338 if (image
instanceof Image
) {
339 ((Image
) image
).dispose();
344 // Y-axis min event (always 0...)
345 gridData
= new GridData();
346 gridData
.horizontalAlignment
= SWT
.RIGHT
;
347 gridData
.verticalAlignment
= SWT
.BOTTOM
;
348 fMinNbEventsText
= new Text(composite
, SWT
.READ_ONLY
| SWT
.RIGHT
);
349 fMinNbEventsText
.setFont(fFont
);
350 fMinNbEventsText
.setBackground(labelColor
);
351 fMinNbEventsText
.setEditable(false);
352 fMinNbEventsText
.setText("0"); //$NON-NLS-1$
353 fMinNbEventsText
.setLayoutData(gridData
);
356 gridData
= new GridData(initalWidth
, SWT
.DEFAULT
);
357 gridData
.horizontalAlignment
= SWT
.RIGHT
;
358 gridData
.verticalAlignment
= SWT
.BOTTOM
;
359 final Label dummyLabel
= new Label(composite
, SWT
.NONE
);
360 dummyLabel
.setLayoutData(gridData
);
362 // Window range start time
363 gridData
= new GridData();
364 gridData
.horizontalAlignment
= SWT
.LEFT
;
365 gridData
.verticalAlignment
= SWT
.BOTTOM
;
366 fTimeRangeStartText
= new Text(composite
, SWT
.READ_ONLY
);
367 fTimeRangeStartText
.setFont(fFont
);
368 fTimeRangeStartText
.setBackground(labelColor
);
369 fTimeRangeStartText
.setLayoutData(gridData
);
371 // Window range end time
372 gridData
= new GridData();
373 gridData
.horizontalAlignment
= SWT
.RIGHT
;
374 gridData
.verticalAlignment
= SWT
.BOTTOM
;
375 fTimeRangeEndText
= new Text(composite
, SWT
.READ_ONLY
);
376 fTimeRangeEndText
.setFont(fFont
);
377 fTimeRangeEndText
.setBackground(labelColor
);
378 fTimeRangeEndText
.setLayoutData(gridData
);
380 FocusListener listener
= new FocusAdapter() {
382 public void focusGained(FocusEvent e
) {
386 fMaxNbEventsText
.addFocusListener(listener
);
387 fMinNbEventsText
.addFocusListener(listener
);
388 fTimeRangeStartText
.addFocusListener(listener
);
389 fTimeRangeEndText
.addFocusListener(listener
);
394 private static Font
adjustFont(final Composite composite
) {
395 // Reduce font size for a more pleasing rendering
396 final int fontSizeAdjustment
= -2;
397 final Font font
= composite
.getFont();
398 final FontData fontData
= font
.getFontData()[0];
399 return new Font(font
.getDevice(), fontData
.getName(), fontData
.getHeight() + fontSizeAdjustment
, fontData
.getStyle());
402 // ------------------------------------------------------------------------
404 // ------------------------------------------------------------------------
407 * Returns the start time (equal first bucket time)
408 * @return the start time.
410 public long getStartTime() {
411 return fDataModel
.getFirstBucketTime();
415 * Returns the end time.
416 * @return the end time.
418 public long getEndTime() {
419 return fDataModel
.getEndTime();
423 * Returns the time limit (end of last bucket)
424 * @return the time limit.
426 public long getTimeLimit() {
427 return fDataModel
.getTimeLimit();
431 * Returns a data model reference.
432 * @return data model.
434 public HistogramDataModel
getDataModel() {
439 * Returns the text control for the maximum of events in one bar
441 * @return the text control
444 public Text
getMaxNbEventsText() {
445 return fMaxNbEventsText
;
449 * Return <code>true</code> if the traces must be displayed in the histogram,
450 * <code>false</code> otherwise.
451 * @return whether the traces should be displayed
454 public boolean showTraces() {
455 return showTraces
&& fDataModel
.getNbTraces() < getMaxNbTraces();
459 * Returns the maximum number of traces the histogram can display with separate colors.
460 * If there is more traces, histogram will use only one color to display them.
461 * @return the maximum number of traces the histogram can display.
464 public int getMaxNbTraces() {
465 return fHistoBarColors
.length
;
469 * Returns the color used to display the trace at the given index.
470 * @param traceIndex a trace index
471 * @return a {@link Color}
474 public Color
getTraceColor(int traceIndex
) {
475 return fHistoBarColors
[traceIndex
% fHistoBarColors
.length
];
478 // ------------------------------------------------------------------------
480 // ------------------------------------------------------------------------
482 * Updates the time range.
483 * @param startTime A start time
484 * @param endTime A end time.
486 public void updateTimeRange(long startTime
, long endTime
) {
487 if (fDragState
== DRAG_NONE
) {
488 ((HistogramView
) fParentView
).updateTimeRange(startTime
, endTime
);
493 * Clear the histogram and reset the data
495 public void clear() {
497 if (fDragState
== DRAG_SELECTION
) {
498 updateSelectionTime();
500 fDragState
= DRAG_NONE
;
502 synchronized (fDataModel
) {
508 * Sets the current selection time range and refresh the display
510 * @param beginTime The begin time of the current selection
511 * @param endTime The end time of the current selection
514 public void setSelection(final long beginTime
, final long endTime
) {
515 fSelectionBegin
= (beginTime
> 0) ? beginTime
: 0;
516 fSelectionEnd
= (endTime
> 0) ? endTime
: 0;
517 fDataModel
.setSelectionNotifyListeners(beginTime
, endTime
);
521 * Computes the timestamp of the bucket at [offset]
523 * @param offset offset from the left on the histogram
524 * @return the start timestamp of the corresponding bucket
526 public synchronized long getTimestamp(final int offset
) {
527 assert offset
> 0 && offset
< fScaledData
.fWidth
;
529 return fScaledData
.fFirstBucketTime
+ fScaledData
.fBucketDuration
* offset
;
530 } catch (final Exception e
) {
531 return 0; // TODO: Fix that racing condition (NPE)
536 * Computes the offset of the timestamp in the histogram
538 * @param timestamp the timestamp
539 * @return the offset of the corresponding bucket (-1 if invalid)
541 public synchronized int getOffset(final long timestamp
) {
542 if (timestamp
< fDataModel
.getFirstBucketTime() || timestamp
> fDataModel
.getEndTime()) {
545 return (int) ((timestamp
- fDataModel
.getFirstBucketTime()) / fScaledData
.fBucketDuration
);
549 * Set the bucket display offset
552 * the bucket display offset
555 protected void setOffset(final int offset
) {
560 * Move the currently selected bar cursor to a non-empty bucket.
562 * @param keyCode the SWT key code
564 protected void moveCursor(final int keyCode
) {
571 while (index
< fScaledData
.fLastBucket
&& fScaledData
.fData
[index
].isEmpty()) {
574 if (index
< fScaledData
.fLastBucket
) {
575 fScaledData
.fSelectionBeginBucket
= index
;
579 case SWT
.ARROW_RIGHT
:
580 index
= Math
.max(0, fScaledData
.fSelectionBeginBucket
+ 1);
581 while (index
< fScaledData
.fWidth
&& fScaledData
.fData
[index
].isEmpty()) {
584 if (index
< fScaledData
.fLastBucket
) {
585 fScaledData
.fSelectionBeginBucket
= index
;
590 index
= fScaledData
.fLastBucket
;
591 while (index
>= 0 && fScaledData
.fData
[index
].isEmpty()) {
595 fScaledData
.fSelectionBeginBucket
= index
;
600 index
= Math
.min(fScaledData
.fLastBucket
- 1, fScaledData
.fSelectionBeginBucket
- 1);
601 while (index
>= 0 && fScaledData
.fData
[index
].isEmpty()) {
605 fScaledData
.fSelectionBeginBucket
= index
;
613 fScaledData
.fSelectionEndBucket
= fScaledData
.fSelectionBeginBucket
;
614 fSelectionBegin
= getTimestamp(fScaledData
.fSelectionBeginBucket
);
615 fSelectionEnd
= fSelectionBegin
;
616 updateSelectionTime();
620 * Refresh the histogram display
623 public void modelUpdated() {
624 if (!fCanvas
.isDisposed() && fCanvas
.getDisplay() != null) {
625 fCanvas
.getDisplay().asyncExec(new Runnable() {
628 if (!fCanvas
.isDisposed()) {
629 // Retrieve and normalize the data
630 final int canvasWidth
= fCanvas
.getBounds().width
;
631 final int canvasHeight
= fCanvas
.getBounds().height
;
632 if (canvasWidth
<= 0 || canvasHeight
<= 0) {
635 fDataModel
.setSelection(fSelectionBegin
, fSelectionEnd
);
636 fScaledData
= fDataModel
.scaleTo(canvasWidth
, canvasHeight
, 1);
637 synchronized (fDataModel
) {
638 if (fScaledData
!= null) {
640 // Display histogram and update X-,Y-axis labels
641 updateRangeTextControls();
642 long maxNbEvents
= HistogramScaledData
.hideLostEvents ? fScaledData
.fMaxValue
: fScaledData
.fMaxCombinedValue
;
643 fMaxNbEventsText
.setText(Long
.toString(maxNbEvents
));
644 // The Y-axis area might need to be re-sized
645 GridData gd
= (GridData
) fMaxNbEventsText
.getLayoutData();
646 gd
.widthHint
= Math
.max(gd
.widthHint
, fMaxNbEventsText
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
).x
);
647 fMaxNbEventsText
.getParent().layout();
657 * Add a mouse wheel listener to the histogram
658 * @param listener the mouse wheel listener
661 public void addMouseWheelListener(MouseWheelListener listener
) {
662 fCanvas
.addMouseWheelListener(listener
);
666 * Remove a mouse wheel listener from the histogram
667 * @param listener the mouse wheel listener
670 public void removeMouseWheelListener(MouseWheelListener listener
) {
671 fCanvas
.removeMouseWheelListener(listener
);
674 // ------------------------------------------------------------------------
676 // ------------------------------------------------------------------------
678 private void updateSelectionTime() {
679 if (fSelectionBegin
> fSelectionEnd
) {
680 long end
= fSelectionBegin
;
681 fSelectionBegin
= fSelectionEnd
;
684 ((HistogramView
) fParentView
).updateSelectionTime(fSelectionBegin
, fSelectionEnd
);
688 * Update the range text controls
690 private void updateRangeTextControls() {
691 if (fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
692 fTimeRangeStartText
.setText(TmfTimestampFormat
.getDefaulTimeFormat().format(fDataModel
.getStartTime()));
693 fTimeRangeEndText
.setText(TmfTimestampFormat
.getDefaulTimeFormat().format(fDataModel
.getEndTime()));
695 fTimeRangeStartText
.setText(""); //$NON-NLS-1$
696 fTimeRangeEndText
.setText(""); //$NON-NLS-1$
700 // ------------------------------------------------------------------------
702 // ------------------------------------------------------------------------
704 * Image key string for the canvas.
706 protected final String IMAGE_KEY
= "double-buffer-image"; //$NON-NLS-1$
709 public void paintControl(final PaintEvent event
) {
712 final int canvasWidth
= fCanvas
.getBounds().width
;
713 final int canvasHeight
= fCanvas
.getBounds().height
;
715 // Make sure we have something to draw upon
716 if (canvasWidth
<= 0 || canvasHeight
<= 0) {
720 // Retrieve image; re-create only if necessary
721 Image image
= (Image
) fCanvas
.getData(IMAGE_KEY
);
722 if (image
== null || image
.getBounds().width
!= canvasWidth
|| image
.getBounds().height
!= canvasHeight
) {
726 image
= new Image(event
.display
, canvasWidth
, canvasHeight
);
727 fCanvas
.setData(IMAGE_KEY
, image
);
730 // Draw the histogram on its canvas
731 final GC imageGC
= new GC(image
);
732 formatImage(imageGC
, image
);
733 event
.gc
.drawImage(image
, 0, 0);
737 private void formatImage(final GC imageGC
, final Image image
) {
739 if (fScaledData
== null) {
743 final HistogramScaledData scaledData
= new HistogramScaledData(fScaledData
);
746 // Get drawing boundaries
747 final int width
= image
.getBounds().width
;
748 final int height
= image
.getBounds().height
;
750 // Clear the drawing area
751 imageGC
.setBackground(fBackgroundColor
);
752 imageGC
.fillRectangle(0, 0, image
.getBounds().width
+ 1, image
.getBounds().height
+ 1);
754 // Draw the histogram bars
755 final int limit
= width
< scaledData
.fWidth ? width
: scaledData
.fWidth
;
756 double factor
= HistogramScaledData
.hideLostEvents ? scaledData
.fScalingFactor
: scaledData
.fScalingFactorCombined
;
757 final boolean showTracesColors
= showTraces();
758 for (int i
= 0; i
< limit
; i
++) {
759 HistogramBucket hb
= scaledData
.fData
[i
];
760 int totalNbEvents
= hb
.getNbEvents();
761 int value
= (int) Math
.ceil(totalNbEvents
* factor
);
764 // in Linux, the last pixel in a line is not drawn,
765 // so draw lost events first, one pixel too far
766 if (!HistogramScaledData
.hideLostEvents
) {
767 imageGC
.setForeground(fLostEventColor
);
768 final int lostEventValue
= (int) Math
.ceil(scaledData
.fLostEventsData
[i
] * factor
);
769 if (lostEventValue
!= 0) {
770 // drawing a line is inclusive, so we should remove 1 from y2
771 // but we don't because Linux
772 imageGC
.drawLine(x
, height
- value
- lostEventValue
, x
, height
- value
);
776 // then draw normal events second, to overwrite that extra pixel
778 if (showTracesColors
) {
779 for (int traceIndex
= 0; traceIndex
< hb
.getNbTraces(); traceIndex
++) {
780 int nbEventsForTrace
= hb
.getNbEvent(traceIndex
);
781 if (nbEventsForTrace
> 0) {
782 Color c
= fHistoBarColors
[traceIndex
% fHistoBarColors
.length
];
783 imageGC
.setForeground(c
);
784 imageGC
.drawLine(x
, height
- value
, x
, height
);
785 totalNbEvents
-= nbEventsForTrace
;
786 value
= (int) Math
.ceil(totalNbEvents
* scaledData
.fScalingFactor
);
790 Color c
= fHistoBarColors
[0];
791 imageGC
.setForeground(c
);
792 imageGC
.drawLine(x
, height
- value
, x
, height
);
797 // Draw the selection bars
798 int alpha
= imageGC
.getAlpha();
799 imageGC
.setAlpha(100);
800 imageGC
.setForeground(fSelectionForegroundColor
);
801 imageGC
.setBackground(fSelectionBackgroundColor
);
802 final int beginBucket
= scaledData
.fSelectionBeginBucket
+ fOffset
;
803 if (beginBucket
>= 0 && beginBucket
< limit
) {
804 imageGC
.drawLine(beginBucket
, 0, beginBucket
, height
);
806 final int endBucket
= scaledData
.fSelectionEndBucket
+ fOffset
;
807 if (endBucket
>= 0 && endBucket
< limit
&& endBucket
!= beginBucket
) {
808 imageGC
.drawLine(endBucket
, 0, endBucket
, height
);
810 if (Math
.abs(endBucket
- beginBucket
) > 1) {
811 if (endBucket
> beginBucket
) {
812 imageGC
.fillRectangle(beginBucket
+ 1, 0, endBucket
- beginBucket
- 1, height
);
814 imageGC
.fillRectangle(endBucket
+ 1, 0, beginBucket
- endBucket
- 1, height
);
817 imageGC
.setAlpha(alpha
);
819 // Add a dashed line as a delimiter
820 int delimiterIndex
= (int) ((getDataModel().getEndTime() - scaledData
.getFirstBucketTime()) / scaledData
.fBucketDuration
) + 1;
821 drawDelimiter(imageGC
, fLastEventColor
, height
, delimiterIndex
);
823 // Fill the area to the right of delimiter with background color
824 imageGC
.setBackground(fFillColor
);
825 imageGC
.fillRectangle(delimiterIndex
+ 1, 0, width
- (delimiterIndex
+ 1), height
);
827 } catch (final Exception e
) {
832 private static void drawDelimiter(final GC imageGC
, final Color color
,
833 final int height
, final int index
) {
834 imageGC
.setBackground(color
);
835 final int dash
= height
/ 4;
836 imageGC
.fillRectangle(index
, 0 * dash
, 1, dash
- 1);
837 imageGC
.fillRectangle(index
, 1 * dash
, 1, dash
- 1);
838 imageGC
.fillRectangle(index
, 2 * dash
, 1, dash
- 1);
839 imageGC
.fillRectangle(index
, 3 * dash
, 1, height
- 3 * dash
);
843 * Draw a time range window
847 * @param rangeStartTime
848 * the range start time
849 * @param rangeDuration
853 protected void drawTimeRangeWindow(GC imageGC
, long rangeStartTime
, long rangeDuration
) {
855 if (fScaledData
== null) {
859 // Map times to histogram coordinates
860 long bucketSpan
= Math
.max(fScaledData
.fBucketDuration
, 1);
861 long startTime
= Math
.min(rangeStartTime
, rangeStartTime
+ rangeDuration
);
862 int rangeWidth
= (int) (Math
.abs(rangeDuration
) / bucketSpan
);
864 int left
= (int) ((startTime
- fDataModel
.getFirstBucketTime()) / bucketSpan
);
865 int right
= left
+ rangeWidth
;
866 int center
= (left
+ right
) / 2;
867 int height
= fCanvas
.getSize().y
;
868 int arc
= Math
.min(15, rangeWidth
);
870 // Draw the selection window
871 imageGC
.setForeground(fTimeRangeColor
);
872 imageGC
.setLineWidth(1);
873 imageGC
.setLineStyle(SWT
.LINE_SOLID
);
874 imageGC
.drawRoundRectangle(left
, 0, rangeWidth
, height
- 1, arc
, arc
);
876 // Fill the selection window
877 imageGC
.setBackground(fTimeRangeColor
);
878 imageGC
.setAlpha(35);
879 imageGC
.fillRoundRectangle(left
+ 1, 1, rangeWidth
- 1, height
- 2, arc
, arc
);
880 imageGC
.setAlpha(255);
882 // Draw the cross hair
883 imageGC
.setForeground(fTimeRangeColor
);
884 imageGC
.setLineWidth(1);
885 imageGC
.setLineStyle(SWT
.LINE_SOLID
);
887 int chHalfWidth
= ((rangeWidth
< 60) ?
(rangeWidth
* 2) / 3 : 40) / 2;
888 imageGC
.drawLine(center
- chHalfWidth
, height
/ 2, center
+ chHalfWidth
, height
/ 2);
889 imageGC
.drawLine(center
, (height
/ 2) - chHalfWidth
, center
, (height
/ 2) + chHalfWidth
);
892 // ------------------------------------------------------------------------
894 // ------------------------------------------------------------------------
897 public void keyPressed(final KeyEvent event
) {
898 moveCursor(event
.keyCode
);
902 public void keyReleased(final KeyEvent event
) {
905 // ------------------------------------------------------------------------
907 // ------------------------------------------------------------------------
910 public void mouseDoubleClick(final MouseEvent event
) {
914 public void mouseDown(final MouseEvent event
) {
915 if (fScaledData
!= null && event
.button
== 1 && fDragState
== DRAG_NONE
&& fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
916 fDragState
= DRAG_SELECTION
;
917 fDragButton
= event
.button
;
918 if ((event
.stateMask
& SWT
.MODIFIER_MASK
) == SWT
.SHIFT
) {
919 if (Math
.abs(event
.x
- fScaledData
.fSelectionBeginBucket
) < Math
.abs(event
.x
- fScaledData
.fSelectionEndBucket
)) {
920 fScaledData
.fSelectionBeginBucket
= fScaledData
.fSelectionEndBucket
;
921 fSelectionBegin
= fSelectionEnd
;
923 fSelectionEnd
= Math
.min(getTimestamp(event
.x
), getEndTime());
924 fScaledData
.fSelectionEndBucket
= (int) ((fSelectionEnd
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
926 fSelectionBegin
= Math
.min(getTimestamp(event
.x
), getEndTime());
927 fScaledData
.fSelectionBeginBucket
= (int) ((fSelectionBegin
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
928 fSelectionEnd
= fSelectionBegin
;
929 fScaledData
.fSelectionEndBucket
= fScaledData
.fSelectionBeginBucket
;
936 public void mouseUp(final MouseEvent event
) {
937 if (fDragState
== DRAG_SELECTION
&& event
.button
== fDragButton
) {
938 fDragState
= DRAG_NONE
;
940 updateSelectionTime();
944 // ------------------------------------------------------------------------
946 // ------------------------------------------------------------------------
952 public void mouseMove(MouseEvent event
) {
953 if (fDragState
== DRAG_SELECTION
&& fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
954 fSelectionEnd
= Math
.max(getStartTime(), Math
.min(getEndTime(), getTimestamp(event
.x
)));
955 fScaledData
.fSelectionEndBucket
= (int) ((fSelectionEnd
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
960 // ------------------------------------------------------------------------
961 // MouseTrackListener
962 // ------------------------------------------------------------------------
965 public void mouseEnter(final MouseEvent event
) {
969 public void mouseExit(final MouseEvent event
) {
973 public void mouseHover(final MouseEvent event
) {
974 if (fDataModel
.getStartTime() < fDataModel
.getEndTime() && fScaledData
!= null) {
975 int delimiterIndex
= (int) ((fDataModel
.getEndTime() - fScaledData
.getFirstBucketTime()) / fScaledData
.fBucketDuration
) + 1;
976 if (event
.x
< delimiterIndex
) {
977 final String tooltip
= formatToolTipLabel(event
.x
- fOffset
);
978 fCanvas
.setToolTipText(tooltip
);
982 fCanvas
.setToolTipText(null);
985 private String
formatToolTipLabel(final int index
) {
986 long startTime
= fScaledData
.getBucketStartTime(index
);
987 // negative values are possible if time values came into the model in decreasing order
991 final long endTime
= fScaledData
.getBucketEndTime(index
);
992 final int nbEvents
= (index
>= 0) ? fScaledData
.fData
[index
].getNbEvents() : 0;
993 final String newLine
= System
.getProperty("line.separator"); //$NON-NLS-1$
994 final StringBuffer buffer
= new StringBuffer();
995 int selectionBeginBucket
= Math
.min(fScaledData
.fSelectionBeginBucket
, fScaledData
.fSelectionEndBucket
);
996 int selectionEndBucket
= Math
.max(fScaledData
.fSelectionBeginBucket
, fScaledData
.fSelectionEndBucket
);
997 if (selectionBeginBucket
<= index
&& index
<= selectionEndBucket
&& fSelectionBegin
!= fSelectionEnd
) {
998 TmfTimestampDelta delta
= new TmfTimestampDelta(Math
.abs(fSelectionEnd
- fSelectionBegin
), ITmfTimestamp
.NANOSECOND_SCALE
);
999 buffer
.append(NLS
.bind(Messages
.Histogram_selectionSpanToolTip
, delta
.toString()));
1000 buffer
.append(newLine
);
1002 buffer
.append(NLS
.bind(Messages
.Histogram_bucketRangeToolTip
,
1003 new TmfTimestamp(startTime
, ITmfTimestamp
.NANOSECOND_SCALE
).toString(),
1004 new TmfTimestamp(endTime
, ITmfTimestamp
.NANOSECOND_SCALE
).toString()));
1005 buffer
.append(newLine
);
1006 buffer
.append(NLS
.bind(Messages
.Histogram_eventCountToolTip
, nbEvents
));
1007 if (!HistogramScaledData
.hideLostEvents
) {
1008 final int nbLostEvents
= (index
>= 0) ? fScaledData
.fLostEventsData
[index
] : 0;
1009 buffer
.append(newLine
);
1010 buffer
.append(NLS
.bind(Messages
.Histogram_lostEventCountToolTip
, nbLostEvents
));
1012 return buffer
.toString();
1015 // ------------------------------------------------------------------------
1017 // ------------------------------------------------------------------------
1020 public void controlMoved(final ControlEvent event
) {
1021 fDataModel
.complete();
1025 public void controlResized(final ControlEvent event
) {
1026 fDataModel
.complete();
1029 // ------------------------------------------------------------------------
1031 // ------------------------------------------------------------------------
1034 * Format the timestamp and update the display
1037 * the incoming signal
1041 public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal
) {
1042 updateRangeTextControls();
1044 fComposite
.layout();