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
.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
;
56 * Re-usable histogram widget.
58 * It has the following features:
60 * <li>Y-axis labels displaying min/max count values
61 * <li>X-axis labels displaying time range
62 * <li>a histogram displaying the distribution of values over time (note that
63 * the histogram might not necessarily fill the whole canvas)
65 * The widget also has 2 'markers' to identify:
67 * <li>a red dashed line over the bar that contains the currently selected event
68 * <li>a dark red dashed line that delimits the right end of the histogram (if
69 * it doesn't fill the canvas)
71 * Clicking on the histogram will select the current event at the mouse
74 * Once the histogram is selected, there is some limited keyboard support:
76 * <li>Home: go to the first histogram bar
77 * <li>End: go to the last histogram bar
78 * <li>Left: go to the previous histogram
79 * <li>Right: go to the next histogram bar
81 * Finally, when the mouse hovers over the histogram, a tool tip showing the
82 * following information about the corresponding histogram bar time range:
84 * <li>start of the time range
85 * <li>end of the time range
86 * <li>number of events in that time range
90 * @author Francois Chouinard
92 public abstract class Histogram
implements ControlListener
, PaintListener
, KeyListener
, MouseListener
, MouseMoveListener
, MouseTrackListener
, IHistogramModelListener
{
94 // ------------------------------------------------------------------------
96 // ------------------------------------------------------------------------
100 // System colors, they do not need to be disposed
101 private final Color fBackgroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WHITE
);
102 private final Color fSelectionForegroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_BLUE
);
103 private final Color fSelectionBackgroundColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_WIDGET_BACKGROUND
);
104 private final Color fLastEventColor
= Display
.getCurrent().getSystemColor(SWT
.COLOR_DARK_RED
);
106 // Application colors, they need to be disposed
107 private final Color
[] fHistoBarColors
= new Color
[] {new Color(Display
.getDefault(), 90, 90, 255), // blue
108 new Color(Display
.getDefault(), 0, 240, 0), // green
109 new Color(Display
.getDefault(), 255, 0, 0), // red
110 new Color(Display
.getDefault(), 0, 255, 255), // cyan
111 new Color(Display
.getDefault(), 255, 80, 255), // magenta
112 new Color(Display
.getDefault(), 200, 200, 0), // yellow
113 new Color(Display
.getDefault(), 200, 150, 0), // brown
114 new Color(Display
.getDefault(), 150, 255, 150), // light green
115 new Color(Display
.getDefault(), 200, 80, 80), // dark red
116 new Color(Display
.getDefault(), 30, 150, 150), // dark cyan
117 new Color(Display
.getDefault(), 200, 200, 255), // light blue
118 new Color(Display
.getDefault(), 0, 120, 0), // dark green
119 new Color(Display
.getDefault(), 255, 150, 150), // lighter red
120 new Color(Display
.getDefault(), 140, 80, 140), // dark magenta
121 new Color(Display
.getDefault(), 150, 100, 50), // brown
122 new Color(Display
.getDefault(), 255, 80, 80), // light red
123 new Color(Display
.getDefault(), 200, 200, 200), // light grey
124 new Color(Display
.getDefault(), 255, 200, 80), // orange
125 new Color(Display
.getDefault(), 255, 255, 80), // pale yellow
126 new Color(Display
.getDefault(), 255, 200, 200), // pale red
127 new Color(Display
.getDefault(), 255, 200, 255), // pale magenta
128 new Color(Display
.getDefault(), 255, 255, 200), // pale pale yellow
129 new Color(Display
.getDefault(), 200, 255, 255), // pale pale blue
131 private final Color fTimeRangeColor
= new Color(Display
.getCurrent(), 255, 128, 0);
132 private final Color fLostEventColor
= new Color(Display
.getCurrent(), 208, 62, 120);
136 * No drag in progress
139 protected final int DRAG_NONE
= 0;
144 protected final int DRAG_SELECTION
= 1;
146 * Drag the time range
149 protected final int DRAG_RANGE
= 2;
151 * Drag the zoom range
154 protected final int DRAG_ZOOM
= 3;
156 // ------------------------------------------------------------------------
158 // ------------------------------------------------------------------------
161 * The parent TMF view.
163 protected TmfView fParentView
;
165 private Composite fComposite
;
168 // Histogram text fields
169 private Label fMaxNbEventsLabel
;
170 private Label fMinNbEventsLabel
;
171 private Label fTimeRangeStartLabel
;
172 private Label fTimeRangeEndLabel
;
175 * Histogram drawing area
177 protected Canvas fCanvas
;
180 * The histogram data model.
182 protected final HistogramDataModel fDataModel
;
185 * The histogram data model scaled to current resolution and screen width.
187 protected HistogramScaledData fScaledData
;
190 * The current event value
192 protected long fCurrentEventTime
= 0L;
195 * The current selection begin time
197 private long fSelectionBegin
= 0L;
200 * The current selection end time
202 private long fSelectionEnd
= 0L;
207 * @see #DRAG_SELECTION
212 protected int fDragState
= DRAG_NONE
;
215 * The button that started a mouse drag, or 0 if no drag in progress
218 protected int fDragButton
= 0;
221 * The bucket display offset
223 private int fOffset
= 0;
226 * show the traces or not
229 static boolean showTraces
= true;
231 // ------------------------------------------------------------------------
233 // ------------------------------------------------------------------------
238 * @param view A reference to the parent TMF view.
239 * @param parent A parent composite
241 public Histogram(final TmfView view
, final Composite parent
) {
244 fComposite
= createWidget(parent
);
245 fDataModel
= new HistogramDataModel();
246 fDataModel
.addHistogramListener(this);
249 fCanvas
.addControlListener(this);
250 fCanvas
.addPaintListener(this);
251 fCanvas
.addKeyListener(this);
252 fCanvas
.addMouseListener(this);
253 fCanvas
.addMouseTrackListener(this);
254 fCanvas
.addMouseMoveListener(this);
256 TmfSignalManager
.register(this);
260 * Dispose resources and unregisters listeners.
262 public void dispose() {
263 TmfSignalManager
.deregister(this);
264 fLostEventColor
.dispose();
265 for (Color c
: fHistoBarColors
) {
268 fTimeRangeColor
.dispose();
270 fDataModel
.removeHistogramListener(this);
271 fDataModel
.dispose();
274 private Composite
createWidget(final Composite parent
) {
276 fFont
= adjustFont(parent
);
278 final int initalWidth
= 10;
280 // --------------------------------------------------------------------
281 // Define the histogram
282 // --------------------------------------------------------------------
284 final GridLayout gridLayout
= new GridLayout();
285 gridLayout
.numColumns
= 3;
286 gridLayout
.marginHeight
= 0;
287 gridLayout
.marginWidth
= 0;
288 gridLayout
.marginTop
= 0;
289 gridLayout
.horizontalSpacing
= 0;
290 gridLayout
.verticalSpacing
= 0;
291 gridLayout
.marginLeft
= 0;
292 gridLayout
.marginRight
= 0;
293 final Composite composite
= new Composite(parent
, SWT
.FILL
);
294 composite
.setLayout(gridLayout
);
296 // Use all the horizontal space
297 GridData gridData
= new GridData();
298 gridData
.horizontalAlignment
= SWT
.FILL
;
299 gridData
.verticalAlignment
= SWT
.FILL
;
300 gridData
.grabExcessHorizontalSpace
= true;
301 gridData
.grabExcessVerticalSpace
= true;
302 composite
.setLayoutData(gridData
);
305 gridData
= new GridData();
306 gridData
.horizontalAlignment
= SWT
.RIGHT
;
307 gridData
.verticalAlignment
= SWT
.TOP
;
308 fMaxNbEventsLabel
= new Label(composite
, SWT
.RIGHT
);
309 fMaxNbEventsLabel
.setFont(fFont
);
310 fMaxNbEventsLabel
.setText("0"); //$NON-NLS-1$
311 fMaxNbEventsLabel
.setLayoutData(gridData
);
314 Composite canvasComposite
= new Composite(composite
, SWT
.BORDER
);
315 gridData
= new GridData();
316 gridData
.horizontalSpan
= 2;
317 gridData
.verticalSpan
= 2;
318 gridData
.horizontalAlignment
= SWT
.FILL
;
319 gridData
.verticalAlignment
= SWT
.FILL
;
320 gridData
.heightHint
= 0;
321 gridData
.widthHint
= 0;
322 gridData
.grabExcessHorizontalSpace
= true;
323 gridData
.grabExcessVerticalSpace
= true;
324 canvasComposite
.setLayoutData(gridData
);
325 canvasComposite
.setLayout(new FillLayout());
326 fCanvas
= new Canvas(canvasComposite
, SWT
.DOUBLE_BUFFERED
);
327 fCanvas
.addDisposeListener(new DisposeListener() {
329 public void widgetDisposed(DisposeEvent e
) {
330 Object image
= fCanvas
.getData(IMAGE_KEY
);
331 if (image
instanceof Image
) {
332 ((Image
) image
).dispose();
337 // Y-axis min event (always 0...)
338 gridData
= new GridData();
339 gridData
.horizontalAlignment
= SWT
.RIGHT
;
340 gridData
.verticalAlignment
= SWT
.BOTTOM
;
341 fMinNbEventsLabel
= new Label(composite
, SWT
.RIGHT
);
342 fMinNbEventsLabel
.setFont(fFont
);
343 fMinNbEventsLabel
.setText("0"); //$NON-NLS-1$
344 fMinNbEventsLabel
.setLayoutData(gridData
);
347 gridData
= new GridData(initalWidth
, SWT
.DEFAULT
);
348 gridData
.horizontalAlignment
= SWT
.RIGHT
;
349 gridData
.verticalAlignment
= SWT
.BOTTOM
;
350 final Label dummyLabel
= new Label(composite
, SWT
.NONE
);
351 dummyLabel
.setLayoutData(gridData
);
353 // Window range start time
354 gridData
= new GridData();
355 gridData
.horizontalAlignment
= SWT
.LEFT
;
356 gridData
.verticalAlignment
= SWT
.BOTTOM
;
357 fTimeRangeStartLabel
= new Label(composite
, SWT
.NONE
);
358 fTimeRangeStartLabel
.setFont(fFont
);
359 fTimeRangeStartLabel
.setLayoutData(gridData
);
361 // Window range end time
362 gridData
= new GridData();
363 gridData
.horizontalAlignment
= SWT
.RIGHT
;
364 gridData
.verticalAlignment
= SWT
.BOTTOM
;
365 fTimeRangeEndLabel
= new Label(composite
, SWT
.NONE
);
366 fTimeRangeEndLabel
.setFont(fFont
);
367 fTimeRangeEndLabel
.setLayoutData(gridData
);
372 private static Font
adjustFont(final Composite composite
) {
373 // Reduce font size for a more pleasing rendering
374 final int fontSizeAdjustment
= -2;
375 final Font font
= composite
.getFont();
376 final FontData fontData
= font
.getFontData()[0];
377 return new Font(font
.getDevice(), fontData
.getName(), fontData
.getHeight() + fontSizeAdjustment
, fontData
.getStyle());
380 // ------------------------------------------------------------------------
382 // ------------------------------------------------------------------------
385 * Returns the start time (equal first bucket time)
386 * @return the start time.
388 public long getStartTime() {
389 return fDataModel
.getFirstBucketTime();
393 * Returns the end time.
394 * @return the end time.
396 public long getEndTime() {
397 return fDataModel
.getEndTime();
401 * Returns the time limit (end of last bucket)
402 * @return the time limit.
404 public long getTimeLimit() {
405 return fDataModel
.getTimeLimit();
409 * Returns a data model reference.
410 * @return data model.
412 public HistogramDataModel
getDataModel() {
417 * Set the max number events to be displayed
420 * the maximum number of events
422 void setMaxNbEvents(long maxNbEvents
) {
423 fMaxNbEventsLabel
.setText(Long
.toString(maxNbEvents
));
424 fMaxNbEventsLabel
.getParent().layout();
429 * Return <code>true</code> if the traces must be displayed in the histogram,
430 * <code>false</code> otherwise.
431 * @return whether the traces should be displayed
434 public boolean showTraces() {
435 return showTraces
&& fDataModel
.getNbTraces() < getMaxNbTraces();
439 * Returns the maximum number of traces the histogram can display with separate colors.
440 * If there is more traces, histogram will use only one color to display them.
441 * @return the maximum number of traces the histogram can display.
444 public int getMaxNbTraces() {
445 return fHistoBarColors
.length
;
449 * Returns the color used to display the trace at the given index.
450 * @param traceIndex a trace index
451 * @return a {@link Color}
454 public Color
getTraceColor(int traceIndex
) {
455 return fHistoBarColors
[traceIndex
% fHistoBarColors
.length
];
458 // ------------------------------------------------------------------------
460 // ------------------------------------------------------------------------
462 * Updates the time range.
463 * @param startTime A start time
464 * @param endTime A end time.
466 public void updateTimeRange(long startTime
, long endTime
) {
467 if (fDragState
== DRAG_NONE
) {
468 ((HistogramView
) fParentView
).updateTimeRange(startTime
, endTime
);
473 * Clear the histogram and reset the data
475 public void clear() {
477 if (fDragState
== DRAG_SELECTION
) {
478 updateSelectionTime();
480 fDragState
= DRAG_NONE
;
482 synchronized (fDataModel
) {
488 * Sets the current selection time range and refresh the display
490 * @param beginTime The begin time of the current selection
491 * @param endTime The end time of the current selection
494 public void setSelection(final long beginTime
, final long endTime
) {
495 fSelectionBegin
= (beginTime
> 0) ? beginTime
: 0;
496 fSelectionEnd
= (endTime
> 0) ? endTime
: 0;
497 fDataModel
.setSelectionNotifyListeners(beginTime
, endTime
);
501 * Computes the timestamp of the bucket at [offset]
503 * @param offset offset from the left on the histogram
504 * @return the start timestamp of the corresponding bucket
506 public synchronized long getTimestamp(final int offset
) {
507 assert offset
> 0 && offset
< fScaledData
.fWidth
;
509 return fScaledData
.fFirstBucketTime
+ fScaledData
.fBucketDuration
* offset
;
510 } catch (final Exception e
) {
511 return 0; // TODO: Fix that racing condition (NPE)
516 * Computes the offset of the timestamp in the histogram
518 * @param timestamp the timestamp
519 * @return the offset of the corresponding bucket (-1 if invalid)
521 public synchronized int getOffset(final long timestamp
) {
522 if (timestamp
< fDataModel
.getFirstBucketTime() || timestamp
> fDataModel
.getEndTime()) {
525 return (int) ((timestamp
- fDataModel
.getFirstBucketTime()) / fScaledData
.fBucketDuration
);
529 * Set the bucket display offset
532 * the bucket display offset
535 protected void setOffset(final int offset
) {
540 * Move the currently selected bar cursor to a non-empty bucket.
542 * @param keyCode the SWT key code
544 protected void moveCursor(final int keyCode
) {
551 while (index
< fScaledData
.fLastBucket
&& fScaledData
.fData
[index
].isEmpty()) {
554 if (index
< fScaledData
.fLastBucket
) {
555 fScaledData
.fSelectionBeginBucket
= index
;
559 case SWT
.ARROW_RIGHT
:
560 index
= Math
.max(0, fScaledData
.fSelectionBeginBucket
+ 1);
561 while (index
< fScaledData
.fWidth
&& fScaledData
.fData
[index
].isEmpty()) {
564 if (index
< fScaledData
.fLastBucket
) {
565 fScaledData
.fSelectionBeginBucket
= index
;
570 index
= fScaledData
.fLastBucket
;
571 while (index
>= 0 && fScaledData
.fData
[index
].isEmpty()) {
575 fScaledData
.fSelectionBeginBucket
= index
;
580 index
= Math
.min(fScaledData
.fLastBucket
- 1, fScaledData
.fSelectionBeginBucket
- 1);
581 while (index
>= 0 && fScaledData
.fData
[index
].isEmpty()) {
585 fScaledData
.fSelectionBeginBucket
= index
;
593 fScaledData
.fSelectionEndBucket
= fScaledData
.fSelectionBeginBucket
;
594 fSelectionBegin
= getTimestamp(fScaledData
.fSelectionBeginBucket
);
595 fSelectionEnd
= fSelectionBegin
;
596 updateSelectionTime();
600 * Refresh the histogram display
603 public void modelUpdated() {
604 if (!fCanvas
.isDisposed() && fCanvas
.getDisplay() != null) {
605 fCanvas
.getDisplay().asyncExec(new Runnable() {
608 if (!fCanvas
.isDisposed()) {
609 // Retrieve and normalize the data
610 final int canvasWidth
= fCanvas
.getBounds().width
;
611 final int canvasHeight
= fCanvas
.getBounds().height
;
612 if (canvasWidth
<= 0 || canvasHeight
<= 0) {
615 fDataModel
.setSelection(fSelectionBegin
, fSelectionEnd
);
616 fScaledData
= fDataModel
.scaleTo(canvasWidth
, canvasHeight
, 1);
617 synchronized (fDataModel
) {
618 if (fScaledData
!= null) {
620 // Display histogram and update X-,Y-axis labels
621 updateRangeTextControls();
622 long maxNbEvents
= HistogramScaledData
.hideLostEvents ? fScaledData
.fMaxValue
: fScaledData
.fMaxCombinedValue
;
623 fMaxNbEventsLabel
.setText(Long
.toString(maxNbEvents
));
624 // The Y-axis area might need to be re-sized
625 GridData gd
= (GridData
) fMaxNbEventsLabel
.getLayoutData();
626 gd
.widthHint
= Math
.max(gd
.widthHint
, fMaxNbEventsLabel
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
).x
);
627 fMaxNbEventsLabel
.getParent().layout();
637 * Add a mouse wheel listener to the histogram
638 * @param listener the mouse wheel listener
641 public void addMouseWheelListener(MouseWheelListener listener
) {
642 fCanvas
.addMouseWheelListener(listener
);
646 * Remove a mouse wheel listener from the histogram
647 * @param listener the mouse wheel listener
650 public void removeMouseWheelListener(MouseWheelListener listener
) {
651 fCanvas
.removeMouseWheelListener(listener
);
654 // ------------------------------------------------------------------------
656 // ------------------------------------------------------------------------
658 private void updateSelectionTime() {
659 if (fSelectionBegin
> fSelectionEnd
) {
660 long end
= fSelectionBegin
;
661 fSelectionBegin
= fSelectionEnd
;
664 ((HistogramView
) fParentView
).updateSelectionTime(fSelectionBegin
, fSelectionEnd
);
668 * Update the range text controls
670 private void updateRangeTextControls() {
671 if (fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
672 fTimeRangeStartLabel
.setText(TmfTimestampFormat
.getDefaulTimeFormat().format(fDataModel
.getStartTime()));
673 fTimeRangeEndLabel
.setText(TmfTimestampFormat
.getDefaulTimeFormat().format(fDataModel
.getEndTime()));
675 fTimeRangeStartLabel
.setText(""); //$NON-NLS-1$
676 fTimeRangeEndLabel
.setText(""); //$NON-NLS-1$
680 // ------------------------------------------------------------------------
682 // ------------------------------------------------------------------------
684 * Image key string for the canvas.
686 protected final String IMAGE_KEY
= "double-buffer-image"; //$NON-NLS-1$
689 public void paintControl(final PaintEvent event
) {
692 final int canvasWidth
= fCanvas
.getBounds().width
;
693 final int canvasHeight
= fCanvas
.getBounds().height
;
695 // Make sure we have something to draw upon
696 if (canvasWidth
<= 0 || canvasHeight
<= 0) {
700 // Retrieve image; re-create only if necessary
701 Image image
= (Image
) fCanvas
.getData(IMAGE_KEY
);
702 if (image
== null || image
.getBounds().width
!= canvasWidth
|| image
.getBounds().height
!= canvasHeight
) {
706 image
= new Image(event
.display
, canvasWidth
, canvasHeight
);
707 fCanvas
.setData(IMAGE_KEY
, image
);
710 // Draw the histogram on its canvas
711 final GC imageGC
= new GC(image
);
712 formatImage(imageGC
, image
);
713 event
.gc
.drawImage(image
, 0, 0);
717 private void formatImage(final GC imageGC
, final Image image
) {
719 if (fScaledData
== null) {
723 final HistogramScaledData scaledData
= new HistogramScaledData(fScaledData
);
726 // Get drawing boundaries
727 final int width
= image
.getBounds().width
;
728 final int height
= image
.getBounds().height
;
730 // Clear the drawing area
731 imageGC
.setBackground(fBackgroundColor
);
732 imageGC
.fillRectangle(0, 0, image
.getBounds().width
+ 1, image
.getBounds().height
+ 1);
734 // Draw the histogram bars
735 final int limit
= width
< scaledData
.fWidth ? width
: scaledData
.fWidth
;
736 double factor
= HistogramScaledData
.hideLostEvents ? scaledData
.fScalingFactor
: scaledData
.fScalingFactorCombined
;
737 final boolean showTracesColors
= showTraces();
738 for (int i
= 0; i
< limit
; i
++) {
739 HistogramBucket hb
= scaledData
.fData
[i
];
740 int totalNbEvents
= hb
.getNbEvents();
741 int value
= (int) Math
.ceil(totalNbEvents
* factor
);
744 // in Linux, the last pixel in a line is not drawn,
745 // so draw lost events first, one pixel too far
746 if (!HistogramScaledData
.hideLostEvents
) {
747 imageGC
.setForeground(fLostEventColor
);
748 final int lostEventValue
= (int) Math
.ceil(scaledData
.fLostEventsData
[i
] * factor
);
749 if (lostEventValue
!= 0) {
750 // drawing a line is inclusive, so we should remove 1 from y2
751 // but we don't because Linux
752 imageGC
.drawLine(x
, height
- value
- lostEventValue
, x
, height
- value
);
756 // then draw normal events second, to overwrite that extra pixel
758 if (showTracesColors
) {
759 for (int traceIndex
= 0; traceIndex
< hb
.getNbTraces(); traceIndex
++) {
760 int nbEventsForTrace
= hb
.getNbEvent(traceIndex
);
761 if (nbEventsForTrace
> 0) {
762 Color c
= fHistoBarColors
[traceIndex
% fHistoBarColors
.length
];
763 imageGC
.setForeground(c
);
764 imageGC
.drawLine(x
, height
- value
, x
, height
);
765 totalNbEvents
-= nbEventsForTrace
;
766 value
= (int) Math
.ceil(totalNbEvents
* scaledData
.fScalingFactor
);
770 Color c
= fHistoBarColors
[0];
771 imageGC
.setForeground(c
);
772 imageGC
.drawLine(x
, height
- value
, x
, height
);
777 // Draw the selection bars
778 int alpha
= imageGC
.getAlpha();
779 imageGC
.setAlpha(100);
780 imageGC
.setForeground(fSelectionForegroundColor
);
781 imageGC
.setBackground(fSelectionBackgroundColor
);
782 final int beginBucket
= scaledData
.fSelectionBeginBucket
+ fOffset
;
783 if (beginBucket
>= 0 && beginBucket
< limit
) {
784 imageGC
.drawLine(beginBucket
, 0, beginBucket
, height
);
786 final int endBucket
= scaledData
.fSelectionEndBucket
+ fOffset
;
787 if (endBucket
>= 0 && endBucket
< limit
&& endBucket
!= beginBucket
) {
788 imageGC
.drawLine(endBucket
, 0, endBucket
, height
);
790 if (Math
.abs(endBucket
- beginBucket
) > 1) {
791 if (endBucket
> beginBucket
) {
792 imageGC
.fillRectangle(beginBucket
+ 1, 0, endBucket
- beginBucket
- 1, height
);
794 imageGC
.fillRectangle(endBucket
+ 1, 0, beginBucket
- endBucket
- 1, height
);
797 imageGC
.setAlpha(alpha
);
799 // Add a dashed line as a delimiter
800 int delimiterIndex
= (int) ((getDataModel().getEndTime() - scaledData
.getFirstBucketTime()) / scaledData
.fBucketDuration
) + 1;
801 drawDelimiter(imageGC
, fLastEventColor
, height
, delimiterIndex
);
803 // Fill the area to the right of delimiter with background color
804 imageGC
.setBackground(fComposite
.getBackground());
805 imageGC
.fillRectangle(delimiterIndex
+ 1, 0, width
- (delimiterIndex
+ 1), height
);
807 } catch (final Exception e
) {
812 private static void drawDelimiter(final GC imageGC
, final Color color
,
813 final int height
, final int index
) {
814 imageGC
.setBackground(color
);
815 final int dash
= height
/ 4;
816 imageGC
.fillRectangle(index
, 0 * dash
, 1, dash
- 1);
817 imageGC
.fillRectangle(index
, 1 * dash
, 1, dash
- 1);
818 imageGC
.fillRectangle(index
, 2 * dash
, 1, dash
- 1);
819 imageGC
.fillRectangle(index
, 3 * dash
, 1, height
- 3 * dash
);
823 * Draw a time range window
827 * @param rangeStartTime
828 * the range start time
829 * @param rangeDuration
833 protected void drawTimeRangeWindow(GC imageGC
, long rangeStartTime
, long rangeDuration
) {
835 if (fScaledData
== null) {
839 // Map times to histogram coordinates
840 long bucketSpan
= Math
.max(fScaledData
.fBucketDuration
, 1);
841 long startTime
= Math
.min(rangeStartTime
, rangeStartTime
+ rangeDuration
);
842 int rangeWidth
= (int) (Math
.abs(rangeDuration
) / bucketSpan
);
844 int left
= (int) ((startTime
- fDataModel
.getFirstBucketTime()) / bucketSpan
);
845 int right
= left
+ rangeWidth
;
846 int center
= (left
+ right
) / 2;
847 int height
= fCanvas
.getSize().y
;
848 int arc
= Math
.min(15, rangeWidth
);
850 // Draw the selection window
851 imageGC
.setForeground(fTimeRangeColor
);
852 imageGC
.setLineWidth(1);
853 imageGC
.setLineStyle(SWT
.LINE_SOLID
);
854 imageGC
.drawRoundRectangle(left
, 0, rangeWidth
, height
- 1, arc
, arc
);
856 // Fill the selection window
857 imageGC
.setBackground(fTimeRangeColor
);
858 imageGC
.setAlpha(35);
859 imageGC
.fillRoundRectangle(left
+ 1, 1, rangeWidth
- 1, height
- 2, arc
, arc
);
860 imageGC
.setAlpha(255);
862 // Draw the cross hair
863 imageGC
.setForeground(fTimeRangeColor
);
864 imageGC
.setLineWidth(1);
865 imageGC
.setLineStyle(SWT
.LINE_SOLID
);
867 int chHalfWidth
= ((rangeWidth
< 60) ?
(rangeWidth
* 2) / 3 : 40) / 2;
868 imageGC
.drawLine(center
- chHalfWidth
, height
/ 2, center
+ chHalfWidth
, height
/ 2);
869 imageGC
.drawLine(center
, (height
/ 2) - chHalfWidth
, center
, (height
/ 2) + chHalfWidth
);
872 // ------------------------------------------------------------------------
874 // ------------------------------------------------------------------------
877 public void keyPressed(final KeyEvent event
) {
878 moveCursor(event
.keyCode
);
882 public void keyReleased(final KeyEvent event
) {
885 // ------------------------------------------------------------------------
887 // ------------------------------------------------------------------------
890 public void mouseDoubleClick(final MouseEvent event
) {
894 public void mouseDown(final MouseEvent event
) {
895 if (fScaledData
!= null && event
.button
== 1 && fDragState
== DRAG_NONE
&& fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
896 fDragState
= DRAG_SELECTION
;
897 fDragButton
= event
.button
;
898 if ((event
.stateMask
& SWT
.MODIFIER_MASK
) == SWT
.SHIFT
) {
899 if (Math
.abs(event
.x
- fScaledData
.fSelectionBeginBucket
) < Math
.abs(event
.x
- fScaledData
.fSelectionEndBucket
)) {
900 fScaledData
.fSelectionBeginBucket
= fScaledData
.fSelectionEndBucket
;
901 fSelectionBegin
= fSelectionEnd
;
903 fSelectionEnd
= Math
.min(getTimestamp(event
.x
), getEndTime());
904 fScaledData
.fSelectionEndBucket
= (int) ((fSelectionEnd
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
906 fSelectionBegin
= Math
.min(getTimestamp(event
.x
), getEndTime());
907 fScaledData
.fSelectionBeginBucket
= (int) ((fSelectionBegin
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
908 fSelectionEnd
= fSelectionBegin
;
909 fScaledData
.fSelectionEndBucket
= fScaledData
.fSelectionBeginBucket
;
916 public void mouseUp(final MouseEvent event
) {
917 if (fDragState
== DRAG_SELECTION
&& event
.button
== fDragButton
) {
918 fDragState
= DRAG_NONE
;
920 updateSelectionTime();
924 // ------------------------------------------------------------------------
926 // ------------------------------------------------------------------------
932 public void mouseMove(MouseEvent event
) {
933 if (fDragState
== DRAG_SELECTION
&& fDataModel
.getStartTime() < fDataModel
.getEndTime()) {
934 fSelectionEnd
= Math
.max(getStartTime(), Math
.min(getEndTime(), getTimestamp(event
.x
)));
935 fScaledData
.fSelectionEndBucket
= (int) ((fSelectionEnd
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
940 // ------------------------------------------------------------------------
941 // MouseTrackListener
942 // ------------------------------------------------------------------------
945 public void mouseEnter(final MouseEvent event
) {
949 public void mouseExit(final MouseEvent event
) {
953 public void mouseHover(final MouseEvent event
) {
954 if (fDataModel
.getStartTime() < fDataModel
.getEndTime() && fScaledData
!= null) {
955 int delimiterIndex
= (int) ((fDataModel
.getEndTime() - fScaledData
.getFirstBucketTime()) / fScaledData
.fBucketDuration
) + 1;
956 if (event
.x
< delimiterIndex
) {
957 final String tooltip
= formatToolTipLabel(event
.x
- fOffset
);
958 fCanvas
.setToolTipText(tooltip
);
962 fCanvas
.setToolTipText(null);
965 private String
formatToolTipLabel(final int index
) {
966 long startTime
= fScaledData
.getBucketStartTime(index
);
967 // negative values are possible if time values came into the model in decreasing order
971 final long endTime
= fScaledData
.getBucketEndTime(index
);
972 final int nbEvents
= (index
>= 0) ? fScaledData
.fData
[index
].getNbEvents() : 0;
973 final String newLine
= System
.getProperty("line.separator"); //$NON-NLS-1$
974 final StringBuffer buffer
= new StringBuffer();
975 int selectionBeginBucket
= Math
.min(fScaledData
.fSelectionBeginBucket
, fScaledData
.fSelectionEndBucket
);
976 int selectionEndBucket
= Math
.max(fScaledData
.fSelectionBeginBucket
, fScaledData
.fSelectionEndBucket
);
977 if (selectionBeginBucket
<= index
&& index
<= selectionEndBucket
&& fSelectionBegin
!= fSelectionEnd
) {
978 TmfTimestampDelta delta
= new TmfTimestampDelta(Math
.abs(fSelectionEnd
- fSelectionBegin
), ITmfTimestamp
.NANOSECOND_SCALE
);
979 buffer
.append(NLS
.bind(Messages
.Histogram_selectionSpanToolTip
, delta
.toString()));
980 buffer
.append(newLine
);
982 buffer
.append(NLS
.bind(Messages
.Histogram_bucketRangeToolTip
,
983 new TmfTimestamp(startTime
, ITmfTimestamp
.NANOSECOND_SCALE
).toString(),
984 new TmfTimestamp(endTime
, ITmfTimestamp
.NANOSECOND_SCALE
).toString()));
985 buffer
.append(newLine
);
986 buffer
.append(NLS
.bind(Messages
.Histogram_eventCountToolTip
, nbEvents
));
987 if (!HistogramScaledData
.hideLostEvents
) {
988 final int nbLostEvents
= (index
>= 0) ? fScaledData
.fLostEventsData
[index
] : 0;
989 buffer
.append(newLine
);
990 buffer
.append(NLS
.bind(Messages
.Histogram_lostEventCountToolTip
, nbLostEvents
));
992 return buffer
.toString();
995 // ------------------------------------------------------------------------
997 // ------------------------------------------------------------------------
1000 public void controlMoved(final ControlEvent event
) {
1001 fDataModel
.complete();
1005 public void controlResized(final ControlEvent event
) {
1006 fDataModel
.complete();
1009 // ------------------------------------------------------------------------
1011 // ------------------------------------------------------------------------
1014 * Format the timestamp and update the display
1017 * the incoming signal
1021 public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal
) {
1022 updateRangeTextControls();
1024 fComposite
.layout();