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