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