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