fix #428919: Support multi-trace histograms
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / views / histogram / Histogram.java
1 /*******************************************************************************
2 * Copyright (c) 2011, 2014 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Francois Chouinard - Initial API and implementation
11 * Bernd Hufmann - Changed to updated histogram data model
12 * Francois Chouinard - Reformat histogram labels on format change
13 * Patrick Tasse - Support selection range
14 * Xavier Raynaud - Support multi-trace coloring
15 *******************************************************************************/
16
17 package org.eclipse.linuxtools.tmf.ui.views.histogram;
18
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;
58
59 /**
60 * Re-usable histogram widget.
61 *
62 * It has the following features:
63 * <ul>
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)
68 * </ul>
69 * The widget also has 2 'markers' to identify:
70 * <ul>
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)
74 * </ul>
75 * Clicking on the histogram will select the current event at the mouse
76 * location.
77 * <p>
78 * Once the histogram is selected, there is some limited keyboard support:
79 * <ul>
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
84 * </ul>
85 * Finally, when the mouse hovers over the histogram, a tool tip showing the
86 * following information about the corresponding histogram bar time range:
87 * <ul>
88 * <li>start of the time range
89 * <li>end of the time range
90 * <li>number of events in that time range
91 * </ul>
92 *
93 * @version 1.1
94 * @author Francois Chouinard
95 */
96 public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseMoveListener, MouseTrackListener, IHistogramModelListener {
97
98 // ------------------------------------------------------------------------
99 // Constants
100 // ------------------------------------------------------------------------
101
102 // Histogram colors
103
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);
110
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
135 };
136 private final Color fTimeRangeColor = new Color(Display.getCurrent(), 255, 128, 0);
137 private final Color fLostEventColor = new Color(Display.getCurrent(), 208, 62, 120);
138
139 // Drag states
140 /**
141 * No drag in progress
142 * @since 2.2
143 */
144 protected final int DRAG_NONE = 0;
145 /**
146 * Drag the selection
147 * @since 2.2
148 */
149 protected final int DRAG_SELECTION = 1;
150 /**
151 * Drag the time range
152 * @since 2.2
153 */
154 protected final int DRAG_RANGE = 2;
155 /**
156 * Drag the zoom range
157 * @since 2.2
158 */
159 protected final int DRAG_ZOOM = 3;
160
161 // ------------------------------------------------------------------------
162 // Attributes
163 // ------------------------------------------------------------------------
164
165 /**
166 * The parent TMF view.
167 */
168 protected TmfView fParentView;
169
170 private Composite fComposite;
171 private Font fFont;
172
173 // Histogram text fields
174 private Text fMaxNbEventsText;
175 private Text fMinNbEventsText;
176 private Text fTimeRangeStartText;
177 private Text fTimeRangeEndText;
178
179 /**
180 * Histogram drawing area
181 */
182 protected Canvas fCanvas;
183
184 /**
185 * The histogram data model.
186 */
187 protected final HistogramDataModel fDataModel;
188
189 /**
190 * The histogram data model scaled to current resolution and screen width.
191 */
192 protected HistogramScaledData fScaledData;
193
194 /**
195 * The current event value
196 */
197 protected long fCurrentEventTime = 0L;
198
199 /**
200 * The current selection begin time
201 */
202 private long fSelectionBegin = 0L;
203
204 /**
205 * The current selection end time
206 */
207 private long fSelectionEnd = 0L;
208
209 /**
210 * The drag state
211 * @see #DRAG_NONE
212 * @see #DRAG_SELECTION
213 * @see #DRAG_RANGE
214 * @see #DRAG_ZOOM
215 * @since 2.2
216 */
217 protected int fDragState = DRAG_NONE;
218
219 /**
220 * The button that started a mouse drag, or 0 if no drag in progress
221 * @since 2.2
222 */
223 protected int fDragButton = 0;
224
225 /**
226 * The bucket display offset
227 */
228 private int fOffset = 0;
229
230 /**
231 * show the traces or not
232 * @since 3.0
233 */
234 static boolean showTraces = true;
235
236 // ------------------------------------------------------------------------
237 // Construction
238 // ------------------------------------------------------------------------
239
240 /**
241 * Full constructor.
242 *
243 * @param view A reference to the parent TMF view.
244 * @param parent A parent composite
245 */
246 public Histogram(final TmfView view, final Composite parent) {
247 fParentView = view;
248
249 fComposite = createWidget(parent);
250 fDataModel = new HistogramDataModel();
251 fDataModel.addHistogramListener(this);
252 clear();
253
254 fCanvas.addControlListener(this);
255 fCanvas.addPaintListener(this);
256 fCanvas.addKeyListener(this);
257 fCanvas.addMouseListener(this);
258 fCanvas.addMouseTrackListener(this);
259 fCanvas.addMouseMoveListener(this);
260
261 TmfSignalManager.register(this);
262 }
263
264 /**
265 * Dispose resources and unregisters listeners.
266 */
267 public void dispose() {
268 TmfSignalManager.deregister(this);
269 fLostEventColor.dispose();
270 for (Color c : fHistoBarColors) {
271 c.dispose();
272 }
273 fTimeRangeColor.dispose();
274 fFont.dispose();
275 fDataModel.removeHistogramListener(this);
276 }
277
278 private Composite createWidget(final Composite parent) {
279
280 final Color labelColor = parent.getBackground();
281 fFont = adjustFont(parent);
282
283 final int initalWidth = 10;
284
285 // --------------------------------------------------------------------
286 // Define the histogram
287 // --------------------------------------------------------------------
288
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);
300
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);
308
309 // Y-axis max event
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);
319
320 // Histogram itself
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() {
335 @Override
336 public void widgetDisposed(DisposeEvent e) {
337 Object image = fCanvas.getData(IMAGE_KEY);
338 if (image instanceof Image) {
339 ((Image) image).dispose();
340 }
341 }
342 });
343
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);
354
355 // Dummy cell
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);
361
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);
370
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);
379
380 FocusListener listener = new FocusAdapter() {
381 @Override
382 public void focusGained(FocusEvent e) {
383 fCanvas.setFocus();
384 }
385 };
386 fMaxNbEventsText.addFocusListener(listener);
387 fMinNbEventsText.addFocusListener(listener);
388 fTimeRangeStartText.addFocusListener(listener);
389 fTimeRangeEndText.addFocusListener(listener);
390
391 return composite;
392 }
393
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());
400 }
401
402 // ------------------------------------------------------------------------
403 // Accessors
404 // ------------------------------------------------------------------------
405
406 /**
407 * Returns the start time (equal first bucket time)
408 * @return the start time.
409 */
410 public long getStartTime() {
411 return fDataModel.getFirstBucketTime();
412 }
413
414 /**
415 * Returns the end time.
416 * @return the end time.
417 */
418 public long getEndTime() {
419 return fDataModel.getEndTime();
420 }
421
422 /**
423 * Returns the time limit (end of last bucket)
424 * @return the time limit.
425 */
426 public long getTimeLimit() {
427 return fDataModel.getTimeLimit();
428 }
429
430 /**
431 * Returns a data model reference.
432 * @return data model.
433 */
434 public HistogramDataModel getDataModel() {
435 return fDataModel;
436 }
437
438 /**
439 * Returns the text control for the maximum of events in one bar
440 *
441 * @return the text control
442 * @since 2.2
443 */
444 public Text getMaxNbEventsText() {
445 return fMaxNbEventsText;
446 }
447
448 /**
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
452 * @since 3.0
453 */
454 public boolean showTraces() {
455 return showTraces && fDataModel.getNbTraces() < getMaxNbTraces();
456 }
457
458 /**
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.
462 * @since 3.0
463 */
464 public int getMaxNbTraces() {
465 return fHistoBarColors.length;
466 }
467
468 /**
469 * Returns the color used to display the trace at the given index.
470 * @param traceIndex a trace index
471 * @return a {@link Color}
472 * @since 3.0
473 */
474 public Color getTraceColor(int traceIndex) {
475 return fHistoBarColors[traceIndex % fHistoBarColors.length];
476 }
477
478 // ------------------------------------------------------------------------
479 // Operations
480 // ------------------------------------------------------------------------
481 /**
482 * Updates the time range.
483 * @param startTime A start time
484 * @param endTime A end time.
485 */
486 public void updateTimeRange(long startTime, long endTime) {
487 if (fDragState == DRAG_NONE) {
488 ((HistogramView) fParentView).updateTimeRange(startTime, endTime);
489 }
490 }
491
492 /**
493 * Clear the histogram and reset the data
494 */
495 public void clear() {
496 fDataModel.clear();
497 if (fDragState == DRAG_SELECTION) {
498 updateSelectionTime();
499 }
500 fDragState = DRAG_NONE;
501 fDragButton = 0;
502 synchronized (fDataModel) {
503 fScaledData = null;
504 }
505 }
506
507 /**
508 * Sets the current selection time range and refresh the display
509 *
510 * @param beginTime The begin time of the current selection
511 * @param endTime The end time of the current selection
512 * @since 2.1
513 */
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);
518 }
519
520 /**
521 * Computes the timestamp of the bucket at [offset]
522 *
523 * @param offset offset from the left on the histogram
524 * @return the start timestamp of the corresponding bucket
525 */
526 public synchronized long getTimestamp(final int offset) {
527 assert offset > 0 && offset < fScaledData.fWidth;
528 try {
529 return fScaledData.fFirstBucketTime + fScaledData.fBucketDuration * offset;
530 } catch (final Exception e) {
531 return 0; // TODO: Fix that racing condition (NPE)
532 }
533 }
534
535 /**
536 * Computes the offset of the timestamp in the histogram
537 *
538 * @param timestamp the timestamp
539 * @return the offset of the corresponding bucket (-1 if invalid)
540 */
541 public synchronized int getOffset(final long timestamp) {
542 if (timestamp < fDataModel.getFirstBucketTime() || timestamp > fDataModel.getEndTime()) {
543 return -1;
544 }
545 return (int) ((timestamp - fDataModel.getFirstBucketTime()) / fScaledData.fBucketDuration);
546 }
547
548 /**
549 * Set the bucket display offset
550 *
551 * @param offset
552 * the bucket display offset
553 * @since 2.2
554 */
555 protected void setOffset(final int offset) {
556 fOffset = offset;
557 }
558
559 /**
560 * Move the currently selected bar cursor to a non-empty bucket.
561 *
562 * @param keyCode the SWT key code
563 */
564 protected void moveCursor(final int keyCode) {
565
566 int index;
567 switch (keyCode) {
568
569 case SWT.HOME:
570 index = 0;
571 while (index < fScaledData.fLastBucket && fScaledData.fData[index].isEmpty()) {
572 index++;
573 }
574 if (index < fScaledData.fLastBucket) {
575 fScaledData.fSelectionBeginBucket = index;
576 }
577 break;
578
579 case SWT.ARROW_RIGHT:
580 index = Math.max(0, fScaledData.fSelectionBeginBucket + 1);
581 while (index < fScaledData.fWidth && fScaledData.fData[index].isEmpty()) {
582 index++;
583 }
584 if (index < fScaledData.fLastBucket) {
585 fScaledData.fSelectionBeginBucket = index;
586 }
587 break;
588
589 case SWT.END:
590 index = fScaledData.fLastBucket;
591 while (index >= 0 && fScaledData.fData[index].isEmpty()) {
592 index--;
593 }
594 if (index >= 0) {
595 fScaledData.fSelectionBeginBucket = index;
596 }
597 break;
598
599 case SWT.ARROW_LEFT:
600 index = Math.min(fScaledData.fLastBucket - 1, fScaledData.fSelectionBeginBucket - 1);
601 while (index >= 0 && fScaledData.fData[index].isEmpty()) {
602 index--;
603 }
604 if (index >= 0) {
605 fScaledData.fSelectionBeginBucket = index;
606 }
607 break;
608
609 default:
610 return;
611 }
612
613 fScaledData.fSelectionEndBucket = fScaledData.fSelectionBeginBucket;
614 fSelectionBegin = getTimestamp(fScaledData.fSelectionBeginBucket);
615 fSelectionEnd = fSelectionBegin;
616 updateSelectionTime();
617 }
618
619 /**
620 * Refresh the histogram display
621 */
622 @Override
623 public void modelUpdated() {
624 if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) {
625 fCanvas.getDisplay().asyncExec(new Runnable() {
626 @Override
627 public void run() {
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) {
633 return;
634 }
635 fDataModel.setSelection(fSelectionBegin, fSelectionEnd);
636 fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight, 1);
637 synchronized (fDataModel) {
638 if (fScaledData != null) {
639 fCanvas.redraw();
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();
648 }
649 }
650 }
651 }
652 });
653 }
654 }
655
656 /**
657 * Add a mouse wheel listener to the histogram
658 * @param listener the mouse wheel listener
659 * @since 2.0
660 */
661 public void addMouseWheelListener(MouseWheelListener listener) {
662 fCanvas.addMouseWheelListener(listener);
663 }
664
665 /**
666 * Remove a mouse wheel listener from the histogram
667 * @param listener the mouse wheel listener
668 * @since 2.0
669 */
670 public void removeMouseWheelListener(MouseWheelListener listener) {
671 fCanvas.removeMouseWheelListener(listener);
672 }
673
674 // ------------------------------------------------------------------------
675 // Helper functions
676 // ------------------------------------------------------------------------
677
678 private void updateSelectionTime() {
679 if (fSelectionBegin > fSelectionEnd) {
680 long end = fSelectionBegin;
681 fSelectionBegin = fSelectionEnd;
682 fSelectionEnd = end;
683 }
684 ((HistogramView) fParentView).updateSelectionTime(fSelectionBegin, fSelectionEnd);
685 }
686
687 /**
688 * Update the range text controls
689 */
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()));
694 } else {
695 fTimeRangeStartText.setText(""); //$NON-NLS-1$
696 fTimeRangeEndText.setText(""); //$NON-NLS-1$
697 }
698 }
699
700 // ------------------------------------------------------------------------
701 // PaintListener
702 // ------------------------------------------------------------------------
703 /**
704 * Image key string for the canvas.
705 */
706 protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$
707
708 @Override
709 public void paintControl(final PaintEvent event) {
710
711 // Get the geometry
712 final int canvasWidth = fCanvas.getBounds().width;
713 final int canvasHeight = fCanvas.getBounds().height;
714
715 // Make sure we have something to draw upon
716 if (canvasWidth <= 0 || canvasHeight <= 0) {
717 return;
718 }
719
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) {
723 if (image != null) {
724 image.dispose();
725 }
726 image = new Image(event.display, canvasWidth, canvasHeight);
727 fCanvas.setData(IMAGE_KEY, image);
728 }
729
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);
734 imageGC.dispose();
735 }
736
737 private void formatImage(final GC imageGC, final Image image) {
738
739 if (fScaledData == null) {
740 return;
741 }
742
743 final HistogramScaledData scaledData = new HistogramScaledData(fScaledData);
744
745 try {
746 // Get drawing boundaries
747 final int width = image.getBounds().width;
748 final int height = image.getBounds().height;
749
750 // Clear the drawing area
751 imageGC.setBackground(fBackgroundColor);
752 imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1);
753
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);
762 int x = i + fOffset;
763
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);
773 }
774 }
775
776 // then draw normal events second, to overwrite that extra pixel
777 if (!hb.isEmpty()) {
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);
787 }
788 }
789 } else {
790 Color c = fHistoBarColors[0];
791 imageGC.setForeground(c);
792 imageGC.drawLine(x, height - value, x, height);
793 }
794 }
795 }
796
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);
805 }
806 final int endBucket = scaledData.fSelectionEndBucket + fOffset;
807 if (endBucket >= 0 && endBucket < limit && endBucket != beginBucket) {
808 imageGC.drawLine(endBucket, 0, endBucket, height);
809 }
810 if (Math.abs(endBucket - beginBucket) > 1) {
811 if (endBucket > beginBucket) {
812 imageGC.fillRectangle(beginBucket + 1, 0, endBucket - beginBucket - 1, height);
813 } else {
814 imageGC.fillRectangle(endBucket + 1, 0, beginBucket - endBucket - 1, height);
815 }
816 }
817 imageGC.setAlpha(alpha);
818
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);
822
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);
826
827 } catch (final Exception e) {
828 // Do nothing
829 }
830 }
831
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);
840 }
841
842 /**
843 * Draw a time range window
844 *
845 * @param imageGC
846 * the GC
847 * @param rangeStartTime
848 * the range start time
849 * @param rangeDuration
850 * the range duration
851 * @since 2.2
852 */
853 protected void drawTimeRangeWindow(GC imageGC, long rangeStartTime, long rangeDuration) {
854
855 if (fScaledData == null) {
856 return;
857 }
858
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);
863
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);
869
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);
875
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);
881
882 // Draw the cross hair
883 imageGC.setForeground(fTimeRangeColor);
884 imageGC.setLineWidth(1);
885 imageGC.setLineStyle(SWT.LINE_SOLID);
886
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);
890 }
891
892 // ------------------------------------------------------------------------
893 // KeyListener
894 // ------------------------------------------------------------------------
895
896 @Override
897 public void keyPressed(final KeyEvent event) {
898 moveCursor(event.keyCode);
899 }
900
901 @Override
902 public void keyReleased(final KeyEvent event) {
903 }
904
905 // ------------------------------------------------------------------------
906 // MouseListener
907 // ------------------------------------------------------------------------
908
909 @Override
910 public void mouseDoubleClick(final MouseEvent event) {
911 }
912
913 @Override
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;
922 }
923 fSelectionEnd = Math.min(getTimestamp(event.x), getEndTime());
924 fScaledData.fSelectionEndBucket = (int) ((fSelectionEnd - fScaledData.fFirstBucketTime) / fScaledData.fBucketDuration);
925 } else {
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;
930 }
931 fCanvas.redraw();
932 }
933 }
934
935 @Override
936 public void mouseUp(final MouseEvent event) {
937 if (fDragState == DRAG_SELECTION && event.button == fDragButton) {
938 fDragState = DRAG_NONE;
939 fDragButton = 0;
940 updateSelectionTime();
941 }
942 }
943
944 // ------------------------------------------------------------------------
945 // MouseMoveListener
946 // ------------------------------------------------------------------------
947
948 /**
949 * @since 2.2
950 */
951 @Override
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);
956 fCanvas.redraw();
957 }
958 }
959
960 // ------------------------------------------------------------------------
961 // MouseTrackListener
962 // ------------------------------------------------------------------------
963
964 @Override
965 public void mouseEnter(final MouseEvent event) {
966 }
967
968 @Override
969 public void mouseExit(final MouseEvent event) {
970 }
971
972 @Override
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);
979 return;
980 }
981 }
982 fCanvas.setToolTipText(null);
983 }
984
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
988 if (startTime < 0) {
989 startTime = 0;
990 }
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);
1001 }
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));
1011 }
1012 return buffer.toString();
1013 }
1014
1015 // ------------------------------------------------------------------------
1016 // ControlListener
1017 // ------------------------------------------------------------------------
1018
1019 @Override
1020 public void controlMoved(final ControlEvent event) {
1021 fDataModel.complete();
1022 }
1023
1024 @Override
1025 public void controlResized(final ControlEvent event) {
1026 fDataModel.complete();
1027 }
1028
1029 // ------------------------------------------------------------------------
1030 // Signal Handlers
1031 // ------------------------------------------------------------------------
1032
1033 /**
1034 * Format the timestamp and update the display
1035 *
1036 * @param signal
1037 * the incoming signal
1038 * @since 2.0
1039 */
1040 @TmfSignalHandler
1041 public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) {
1042 updateRangeTextControls();
1043
1044 fComposite.layout();
1045 }
1046
1047 }
This page took 0.052477 seconds and 6 git commands to generate.