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