1 /*******************************************************************************
2 * Copyright (c) 2011, 2013 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 *******************************************************************************/
16 package org
.eclipse
.linuxtools
.tmf
.ui
.views
.histogram
;
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
;
57 * Re-usable histogram widget.
59 * It has the following features:
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)
66 * The widget also has 2 'markers' to identify:
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)
72 * Clicking on the histogram will select the current event at the mouse
75 * Once the histogram is selected, there is some limited keyboard support:
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
82 * Finally, when the mouse hovers over the histogram, a tool tip showing the
83 * following information about the corresponding histogram bar time range:
85 * <li>start of the time range
86 * <li>end of the time range
87 * <li>number of events in that time range
91 * @author Francois Chouinard
93 public abstract class Histogram
implements ControlListener
, PaintListener
, KeyListener
, MouseListener
, MouseMoveListener
, MouseTrackListener
, IHistogramModelListener
{
95 // ------------------------------------------------------------------------
97 // ------------------------------------------------------------------------
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);
111 * No drag in progress
114 protected final int DRAG_NONE
= 0;
119 protected final int DRAG_SELECTION
= 1;
121 * Drag the time range
124 protected final int DRAG_RANGE
= 2;
126 * Drag the zoom range
129 protected final int DRAG_ZOOM
= 3;
131 // ------------------------------------------------------------------------
133 // ------------------------------------------------------------------------
136 * The parent TMF view.
138 protected TmfView fParentView
;
140 private Composite fComposite
;
143 // Histogram text fields
144 private Text fMaxNbEventsText
;
145 private Text fMinNbEventsText
;
146 private Text fTimeRangeStartText
;
147 private Text fTimeRangeEndText
;
150 * Histogram drawing area
152 protected Canvas fCanvas
;
155 * The histogram data model.
157 protected final HistogramDataModel fDataModel
;
160 * The histogram data model scaled to current resolution and screen width.
162 protected HistogramScaledData fScaledData
;
165 * The current event value
167 protected long fCurrentEventTime
= 0L;
170 * The current selection begin time
172 private long fSelectionBegin
= 0L;
175 * The current selection end time
177 private long fSelectionEnd
= 0L;
182 * @see #DRAG_SELECTION
187 protected int fDragState
= DRAG_NONE
;
190 * The button that started a mouse drag, or 0 if no drag in progress
193 protected int fDragButton
= 0;
196 * The bucket display offset
198 private int fOffset
= 0;
200 // ------------------------------------------------------------------------
202 // ------------------------------------------------------------------------
207 * @param view A reference to the parent TMF view.
208 * @param parent A parent composite
210 public Histogram(final TmfView view
, final Composite parent
) {
213 fComposite
= createWidget(parent
);
214 fDataModel
= new HistogramDataModel();
215 fDataModel
.addHistogramListener(this);
218 fCanvas
.addControlListener(this);
219 fCanvas
.addPaintListener(this);
220 fCanvas
.addKeyListener(this);
221 fCanvas
.addMouseListener(this);
222 fCanvas
.addMouseTrackListener(this);
223 fCanvas
.addMouseMoveListener(this);
225 TmfSignalManager
.register(this);
229 * Dispose resources and unregisters listeners.
231 public void dispose() {
232 TmfSignalManager
.deregister(this);
234 fHistoBarColor
.dispose();
235 fLastEventColor
.dispose();
236 fTimeRangeColor
.dispose();
237 fDataModel
.removeHistogramListener(this);
240 private Composite
createWidget(final Composite parent
) {
242 final Color labelColor
= parent
.getBackground();
243 fFont
= adjustFont(parent
);
245 final int initalWidth
= 10;
247 // --------------------------------------------------------------------
248 // Define the histogram
249 // --------------------------------------------------------------------
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
333 FocusListener listener
= new FocusAdapter() {
335 public void focusGained(FocusEvent e
) {
339 fMaxNbEventsText
.addFocusListener(listener
);
340 fMinNbEventsText
.addFocusListener(listener
);
341 fTimeRangeStartText
.addFocusListener(listener
);
342 fTimeRangeEndText
.addFocusListener(listener
);
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());
355 // ------------------------------------------------------------------------
357 // ------------------------------------------------------------------------
360 * Returns the start time (equal first bucket time)
361 * @return the start time.
363 public long getStartTime() {
364 return fDataModel
.getFirstBucketTime();
368 * Returns the end time.
369 * @return the end time.
371 public long getEndTime() {
372 return fDataModel
.getEndTime();
376 * Returns the time limit (end of last bucket)
377 * @return the time limit.
379 public long getTimeLimit() {
380 return fDataModel
.getTimeLimit();
384 * Returns a data model reference.
385 * @return data model.
387 public HistogramDataModel
getDataModel() {
392 * Returns the text control for the maximum of events in one bar
394 * @return the text control
397 public Text
getMaxNbEventsText() {
398 return fMaxNbEventsText
;
401 // ------------------------------------------------------------------------
403 // ------------------------------------------------------------------------
405 * Updates the time range.
406 * @param startTime A start time
407 * @param endTime A end time.
409 public void updateTimeRange(long startTime
, long endTime
) {
410 if (fDragState
== DRAG_NONE
) {
411 ((HistogramView
) fParentView
).updateTimeRange(startTime
, endTime
);
416 * Clear the histogram and reset the data
418 public void clear() {
420 fDragState
= DRAG_NONE
;
422 synchronized (fDataModel
) {
428 * Increase the histogram bucket corresponding to [timestamp]
430 * @param eventCount The new event count
431 * @param timestamp The latest timestamp
433 public void countEvent(final long eventCount
, final long timestamp
) {
434 fDataModel
.countEvent(eventCount
, timestamp
);
438 * Sets the current event time and refresh the display
440 * @param timestamp The time of the current event
441 * @deprecated As of 2.1, use {@link #setSelection(long, long)}
444 public void setCurrentEvent(final long timestamp
) {
445 fSelectionBegin
= (timestamp
> 0) ? timestamp
: 0;
446 fSelectionEnd
= (timestamp
> 0) ? timestamp
: 0;
447 fDataModel
.setSelectionNotifyListeners(timestamp
, timestamp
);
451 * Sets the current selection time range and refresh the display
453 * @param beginTime The begin time of the current selection
454 * @param endTime The end time of the current selection
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
);
464 * Computes the timestamp of the bucket at [offset]
466 * @param offset offset from the left on the histogram
467 * @return the start timestamp of the corresponding bucket
469 public synchronized long getTimestamp(final int offset
) {
470 assert offset
> 0 && offset
< fScaledData
.fWidth
;
472 return fScaledData
.fFirstBucketTime
+ fScaledData
.fBucketDuration
* offset
;
473 } catch (final Exception e
) {
474 return 0; // TODO: Fix that racing condition (NPE)
479 * Computes the offset of the timestamp in the histogram
481 * @param timestamp the timestamp
482 * @return the offset of the corresponding bucket (-1 if invalid)
484 public synchronized int getOffset(final long timestamp
) {
485 if (timestamp
< fDataModel
.getFirstBucketTime() || timestamp
> fDataModel
.getEndTime()) {
488 return (int) ((timestamp
- fDataModel
.getFirstBucketTime()) / fScaledData
.fBucketDuration
);
492 * Set the bucket display offset
495 * the bucket display offset
498 protected void setOffset(final int offset
) {
503 * Move the currently selected bar cursor to a non-empty bucket.
505 * @param keyCode the SWT key code
507 protected void moveCursor(final int keyCode
) {
514 while (index
< fScaledData
.fLastBucket
&& fScaledData
.fData
[index
] == 0) {
517 if (index
< fScaledData
.fLastBucket
) {
518 fScaledData
.fSelectionBeginBucket
= index
;
522 case SWT
.ARROW_RIGHT
:
523 index
= Math
.max(0, fScaledData
.fSelectionBeginBucket
+ 1);
524 while (index
< fScaledData
.fWidth
&& fScaledData
.fData
[index
] == 0) {
527 if (index
< fScaledData
.fLastBucket
) {
528 fScaledData
.fSelectionBeginBucket
= index
;
533 index
= fScaledData
.fLastBucket
;
534 while (index
>= 0 && fScaledData
.fData
[index
] == 0) {
538 fScaledData
.fSelectionBeginBucket
= index
;
543 index
= Math
.min(fScaledData
.fLastBucket
- 1, fScaledData
.fSelectionBeginBucket
- 1);
544 while (index
>= 0 && fScaledData
.fData
[index
] == 0) {
548 fScaledData
.fSelectionBeginBucket
= index
;
556 fScaledData
.fSelectionEndBucket
= fScaledData
.fSelectionBeginBucket
;
557 fSelectionBegin
= getTimestamp(fScaledData
.fSelectionBeginBucket
);
558 fSelectionEnd
= fSelectionBegin
;
559 updateSelectionTime();
563 * Refresh the histogram display
566 public void modelUpdated() {
567 if (!fCanvas
.isDisposed() && fCanvas
.getDisplay() != null) {
568 fCanvas
.getDisplay().asyncExec(new Runnable() {
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) {
578 fDataModel
.setSelection(fSelectionBegin
, fSelectionEnd
);
579 fScaledData
= fDataModel
.scaleTo(canvasWidth
, canvasHeight
, 1);
580 synchronized (fDataModel
) {
581 if (fScaledData
!= null) {
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();
600 * Add a mouse wheel listener to the histogram
601 * @param listener the mouse wheel listener
604 public void addMouseWheelListener(MouseWheelListener listener
) {
605 fCanvas
.addMouseWheelListener(listener
);
609 * Remove a mouse wheel listener from the histogram
610 * @param listener the mouse wheel listener
613 public void removeMouseWheelListener(MouseWheelListener listener
) {
614 fCanvas
.removeMouseWheelListener(listener
);
617 // ------------------------------------------------------------------------
619 // ------------------------------------------------------------------------
621 private void updateSelectionTime() {
622 if (fSelectionBegin
> fSelectionEnd
) {
623 long end
= fSelectionBegin
;
624 fSelectionBegin
= fSelectionEnd
;
627 ((HistogramView
) fParentView
).updateSelectionTime(fSelectionBegin
, fSelectionEnd
);
631 * Update the range text controls
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()));
638 fTimeRangeStartText
.setText(""); //$NON-NLS-1$
639 fTimeRangeEndText
.setText(""); //$NON-NLS-1$
643 // ------------------------------------------------------------------------
645 // ------------------------------------------------------------------------
647 * Image key string for the canvas.
649 protected final String IMAGE_KEY
= "double-buffer-image"; //$NON-NLS-1$
652 public void paintControl(final PaintEvent event
) {
655 final int canvasWidth
= fCanvas
.getBounds().width
;
656 final int canvasHeight
= fCanvas
.getBounds().height
;
658 // Make sure we have something to draw upon
659 if (canvasWidth
<= 0 || canvasHeight
<= 0) {
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
);
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);
677 private void formatImage(final GC imageGC
, final Image image
) {
679 if (fScaledData
== null) {
683 final HistogramScaledData scaledData
= new HistogramScaledData(fScaledData
);
686 // Get drawing boundaries
687 final int width
= image
.getBounds().width
;
688 final int height
= image
.getBounds().height
;
690 // Turn off anti-aliasing
691 int aliasing
= imageGC
.getAntialias();
692 imageGC
.setAntialias(SWT
.OFF
);
694 // Clear the drawing area
695 imageGC
.setBackground(fBackgroundColor
);
696 imageGC
.fillRectangle(0, 0, image
.getBounds().width
+ 1, image
.getBounds().height
+ 1);
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
);
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
);
717 // then draw normal events second, to overwrite that extra pixel
718 imageGC
.setForeground(fHistoBarColor
);
719 imageGC
.drawLine(x
, height
- value
, x
, height
);
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
);
731 final int endBucket
= scaledData
.fSelectionEndBucket
+ fOffset
;
732 if (endBucket
>= 0 && endBucket
< limit
&& endBucket
!= beginBucket
) {
733 imageGC
.drawLine(endBucket
, 0, endBucket
, height
);
735 if (Math
.abs(endBucket
- beginBucket
) > 1) {
736 if (endBucket
> beginBucket
) {
737 imageGC
.fillRectangle(beginBucket
+ 1, 0, endBucket
- beginBucket
- 1, height
);
739 imageGC
.fillRectangle(endBucket
+ 1, 0, beginBucket
- endBucket
- 1, height
);
742 imageGC
.setAlpha(alpha
);
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
);
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
);
752 // Restore anti-aliasing
753 imageGC
.setAntialias(aliasing
);
755 } catch (final Exception e
) {
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
);
771 * Draw a time range window
775 * @param rangeStartTime
776 * the range start time
777 * @param rangeDuration
781 protected void drawTimeRangeWindow(GC imageGC
, long rangeStartTime
, long rangeDuration
) {
783 if (fScaledData
== null) {
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
);
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
);
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
);
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);
810 // Draw the cross hair
811 imageGC
.setForeground(fTimeRangeColor
);
812 imageGC
.setLineWidth(1);
813 imageGC
.setLineStyle(SWT
.LINE_SOLID
);
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
);
820 // ------------------------------------------------------------------------
822 // ------------------------------------------------------------------------
825 public void keyPressed(final KeyEvent event
) {
826 moveCursor(event
.keyCode
);
830 public void keyReleased(final KeyEvent event
) {
833 // ------------------------------------------------------------------------
835 // ------------------------------------------------------------------------
838 public void mouseDoubleClick(final MouseEvent event
) {
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
;
851 fSelectionEnd
= Math
.min(getTimestamp(event
.x
), getEndTime());
852 fScaledData
.fSelectionEndBucket
= (int) ((fSelectionEnd
- fScaledData
.fFirstBucketTime
) / fScaledData
.fBucketDuration
);
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
;
864 public void mouseUp(final MouseEvent event
) {
865 if (fDragState
== DRAG_SELECTION
&& event
.button
== fDragButton
) {
866 fDragState
= DRAG_NONE
;
868 updateSelectionTime();
872 // ------------------------------------------------------------------------
874 // ------------------------------------------------------------------------
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
);
888 // ------------------------------------------------------------------------
889 // MouseTrackListener
890 // ------------------------------------------------------------------------
893 public void mouseEnter(final MouseEvent event
) {
897 public void mouseExit(final MouseEvent event
) {
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
);
910 fCanvas
.setToolTipText(null);
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
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
);
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
));
940 return buffer
.toString();
943 // ------------------------------------------------------------------------
945 // ------------------------------------------------------------------------
948 public void controlMoved(final ControlEvent event
) {
949 fDataModel
.complete();
953 public void controlResized(final ControlEvent event
) {
954 fDataModel
.complete();
957 // ------------------------------------------------------------------------
959 // ------------------------------------------------------------------------
962 * Format the timestamp and update the display
965 * the incoming signal
969 public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal
) {
970 updateRangeTextControls();