Fix a .gitignore that is too permissive
[deliverable/tracecompass.git] / org.eclipse.linuxtools.lttng.ui / src / org / eclipse / linuxtools / lttng / ui / views / histogram / Histogram.java
CommitLineData
c392540b
FC
1/*******************************************************************************
2 * Copyright (c) 2011 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 *******************************************************************************/
12
13package org.eclipse.linuxtools.lttng.ui.views.histogram;
14
15import org.eclipse.linuxtools.tmf.ui.views.TmfView;
16import org.eclipse.swt.SWT;
17import org.eclipse.swt.events.ControlEvent;
18import org.eclipse.swt.events.ControlListener;
19import org.eclipse.swt.events.KeyEvent;
20import org.eclipse.swt.events.KeyListener;
21import org.eclipse.swt.events.MouseEvent;
22import org.eclipse.swt.events.MouseListener;
23import org.eclipse.swt.events.MouseTrackListener;
24import org.eclipse.swt.events.PaintEvent;
25import org.eclipse.swt.events.PaintListener;
26import org.eclipse.swt.graphics.Color;
27import org.eclipse.swt.graphics.Font;
28import org.eclipse.swt.graphics.FontData;
29import org.eclipse.swt.graphics.GC;
30import org.eclipse.swt.graphics.Image;
31import org.eclipse.swt.layout.GridData;
32import org.eclipse.swt.layout.GridLayout;
33import org.eclipse.swt.widgets.Canvas;
34import org.eclipse.swt.widgets.Composite;
35import org.eclipse.swt.widgets.Display;
36import org.eclipse.swt.widgets.Text;
37
38/**
39 * <b><u>Histogram</u></b>
40 * <p>
41 * Re-usable histogram widget with the following features:
42 * <ul>
43 * <li>Y-axis labels displaying min/max count values
44 * <li>X-axis labels displaying time range
45 * <li>a histogram displaying the distribution of values over time (note that
46 * the histogram might not necessarily fill the whole canvas)
47 * </ul>
48 * The widget also has 2 'markers' to identify:
49 * <ul>
50 * <li>a red dashed line over the bar that contains the currently selected event
51 * <li>a dark red dashed line that delimits the right end of the histogram (if
52 * it doesn't fill the canvas)
53 * </ul>
54 * Clicking on the histogram will select the current event at the mouse
55 * location.
56 * <p>
57 * Once the histogram is selected, there is some limited keyboard support:
58 * <ul>
59 * <li>Home: go to the first histogram bar
60 * <li>End: go to the last histogram bar
61 * <li>Left: go to the previous histogram
62 * <li>Right: go to the next histogram bar
63 * </ul>
64 * Finally, when the mouse hovers over the histogram, a tool tip showing the
65 * following information about the corresponding histogram bar time range:
66 * <ul>
67 * <li>start of the time range
68 * <li>end of the time range
69 * <li>number of events in that time range
70 * </ul>
71 */
72public abstract class Histogram implements ControlListener, PaintListener, KeyListener, MouseListener, MouseTrackListener {
73
74 // ------------------------------------------------------------------------
75 // Constants
76 // ------------------------------------------------------------------------
77
78 // Histogram refresh frequency
79 private final static int REFRESH_FREQUENCY = HistogramDataModel.DEFAULT_NUMBER_OF_BUCKETS;
80
81 // Histogram colors
82 private final Color fBackgroundColor = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE);
83 private final Color fCurrentEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_RED);
84 private final Color fLastEventColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED);
85 private final Color fHistoBarColor = new Color(Display.getDefault(), 74, 112, 139);
86
87 // Timestamp scale (nanosecond)
88 public static final byte TIME_SCALE = -9;
89
90 // ------------------------------------------------------------------------
91 // Attributes
92 // ------------------------------------------------------------------------
93
94 // Owner view
95 protected TmfView fParentView;
96
97 // Histogram text fields
98 private Text fMaxNbEventsText;
99 private Text fMinNbEventsText;
100 private Text fTimeRangeStartText;
101 private Text fTimeRangeEndText;
102
103 // Histogram drawing area
104 protected Canvas fCanvas;
105
106 // Data model
107 protected final HistogramDataModel fDataModel;
108 protected HistogramScaledData fScaledData;
109
110 protected long fCurrentEventTime = 0;
111
112 // ------------------------------------------------------------------------
113 // Construction
114 // ------------------------------------------------------------------------
115
116 public Histogram(TmfView view, Composite parent) {
117 fParentView = view;
118
119 createWidget(parent);
120 fDataModel = new HistogramDataModel();
121 clear();
122
123 fCanvas.addControlListener(this);
124 fCanvas.addPaintListener(this);
125 fCanvas.addKeyListener(this);
126 fCanvas.addMouseListener(this);
127 fCanvas.addMouseTrackListener(this);
128 }
129
130 public void dispose() {
131 fHistoBarColor.dispose();
132 }
133
134 private void createWidget(Composite parent) {
135
136 final Color labelColor = parent.getDisplay().getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND);
137 final Font fFont = adjustFont(parent);
138
139 final int initalWidth = 10;
140
141 // --------------------------------------------------------------------
142 // Define the histogram
143 // --------------------------------------------------------------------
144
145 GridLayout gridLayout = new GridLayout();
146 gridLayout.numColumns = 3;
147 gridLayout.marginHeight = 0;
148 gridLayout.marginWidth = 0;
149 gridLayout.marginTop = 0;
150 gridLayout.horizontalSpacing = 0;
151 gridLayout.verticalSpacing = 0;
152 gridLayout.marginLeft = 0;
153 gridLayout.marginRight = 0;
154 Composite composite = new Composite(parent, SWT.FILL);
155 composite.setLayout(gridLayout);
156
157 // Use all the horizontal space
158 GridData gridData = new GridData();
159 gridData.horizontalAlignment = SWT.FILL;
160 gridData.verticalAlignment = SWT.FILL;
161 gridData.grabExcessHorizontalSpace = true;
162 composite.setLayoutData(gridData);
163
164 // Y-axis max event
165 gridData = new GridData();
166 gridData.horizontalAlignment = SWT.RIGHT;
167 gridData.verticalAlignment = SWT.TOP;
168 fMaxNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT);
169 fMaxNbEventsText.setFont(fFont);
170 fMaxNbEventsText.setBackground(labelColor);
171 fMaxNbEventsText.setEditable(false);
172 fMaxNbEventsText.setText("0"); //$NON-NLS-1$
173 fMaxNbEventsText.setLayoutData(gridData);
174
175 // Histogram itself
176 gridData = new GridData();
177 gridData.horizontalSpan = 2;
178 gridData.verticalSpan = 2;
179 gridData.horizontalAlignment = SWT.FILL;
180 gridData.verticalAlignment = SWT.FILL;
181 gridData.grabExcessHorizontalSpace = true;
182 fCanvas = new Canvas(composite, SWT.BORDER | SWT.DOUBLE_BUFFERED);
183 fCanvas.setLayoutData(gridData);
184
185 // Y-axis min event (always 0...)
186 gridData = new GridData();
187 gridData.horizontalAlignment = SWT.RIGHT;
188 gridData.verticalAlignment = SWT.BOTTOM;
189 fMinNbEventsText = new Text(composite, SWT.READ_ONLY | SWT.RIGHT);
190 fMinNbEventsText.setFont(fFont);
191 fMinNbEventsText.setBackground(labelColor);
192 fMinNbEventsText.setEditable(false);
193 fMinNbEventsText.setText("0"); //$NON-NLS-1$
194 fMinNbEventsText.setLayoutData(gridData);
195
196 // Dummy cell
197 gridData = new GridData(initalWidth, SWT.DEFAULT);
198 gridData.horizontalAlignment = SWT.RIGHT;
199 gridData.verticalAlignment = SWT.BOTTOM;
200 Text dummyText = new Text(composite, SWT.READ_ONLY);
201 dummyText.setFont(fFont);
202 dummyText.setBackground(labelColor);
203 dummyText.setEditable(false);
204 dummyText.setText(""); //$NON-NLS-1$
205 dummyText.setLayoutData(gridData);
206
207 // Window range start time
208 gridData = new GridData();
209 gridData.horizontalAlignment = SWT.LEFT;
210 gridData.verticalAlignment = SWT.BOTTOM;
211 fTimeRangeStartText = new Text(composite, SWT.READ_ONLY);
212 fTimeRangeStartText.setFont(fFont);
213 fTimeRangeStartText.setBackground(labelColor);
214 fTimeRangeStartText.setText(HistogramUtils.nanosecondsToString(0));
215 fTimeRangeStartText.setLayoutData(gridData);
216
217 // Window range end time
218 gridData = new GridData();
219 gridData.horizontalAlignment = SWT.RIGHT;
220 gridData.verticalAlignment = SWT.BOTTOM;
221 fTimeRangeEndText = new Text(composite, SWT.READ_ONLY);
222 fTimeRangeEndText.setFont(fFont);
223 fTimeRangeEndText.setBackground(labelColor);
224 fTimeRangeEndText.setText(HistogramUtils.nanosecondsToString(0));
225 fTimeRangeEndText.setLayoutData(gridData);
226 }
227
228 private Font adjustFont(Composite composite) {
229 // Reduce font size for a more pleasing rendering
230 int fontSizeAdjustment = -2;
231 Font font = composite.getFont();
232 FontData fontData = font.getFontData()[0];
233 return new Font(font.getDevice(), fontData.getName(), fontData.getHeight() + fontSizeAdjustment, fontData.getStyle());
234 }
235
236 // ------------------------------------------------------------------------
237 // Accessors
238 // ------------------------------------------------------------------------
239
240 public long getStartTime() {
241 return fDataModel.getStartTime();
242 }
243
244 public long getEndTime() {
245 return fDataModel.getEndTime();
246 }
247
248 public long getTimeLimit() {
249 return fDataModel.getTimeLimit();
250 }
251
252 // ------------------------------------------------------------------------
253 // Operations
254 // ------------------------------------------------------------------------
255
256 public abstract void updateTimeRange(long startTime, long endTime);
257
258 /**
259 * Clear the histogram and reset the data
260 */
261 public void clear() {
262 fDataModel.clear();
263 fScaledData = null;
264 refresh();
265 }
266
267 /**
268 * Increase the histogram bucket corresponding to [timestamp]
269 *
270 * @param timestamp
271 */
272 public void countEvent(long timestamp) {
273 fDataModel.countEvent(timestamp);
274 if (fDataModel.getNbEvents() % REFRESH_FREQUENCY == 0) {
275 refresh();
74237cc3 276 refresh(); // This is intentional. Exercise left to the reader :-)
c392540b
FC
277 }
278 }
279
280 /**
281 * Sets the current event time and refresh the display
282 *
283 * @param timestamp
284 */
285 public void setCurrentEvent(long timestamp) {
74237cc3 286 fCurrentEventTime = (timestamp > 0) ? timestamp : 0;
c392540b
FC
287 fDataModel.setCurrentEvent(timestamp);
288 refresh();
289 }
290
291 /**
292 * Computes the timestamp of the bucket at [offset]
293 *
294 * @param offset offset from the left on the histogram
295 * @return the start timestamp of the corresponding bucket
296 */
297 public synchronized long getTimestamp(int offset) {
298 assert offset > 0 && offset < fScaledData.fWidth;
299 try {
300 return fDataModel.getStartTime() + fScaledData.fBucketDuration * offset;
301 } catch (Exception e) {
302 return 0; // TODO: Fix that racing condition (NPE)
303 }
304 }
305
306 /**
307 * Computes the offset of the timestamp in the histogram
308 *
309 * @param timestamp the timestamp
310 * @return the offset of the corresponding bucket (-1 if invalid)
311 */
312 public synchronized int getOffset(long timestamp) {
313 if (timestamp < fDataModel.getStartTime() || timestamp > fDataModel.getEndTime())
314 return -1;
315 return (int) ((timestamp - fDataModel.getStartTime()) / fScaledData.fBucketDuration);
316 }
317
318 /**
319 * Move the currently selected bar cursor to a non-empty bucket.
320 *
321 * @param keyCode the SWT key code
322 */
323 protected void moveCursor(int keyCode) {
324
325 if (fScaledData.fCurrentBucket == HistogramScaledData.OUT_OF_RANGE_BUCKET)
326 return;
327
328 int index;
329 switch (keyCode) {
330
331 case SWT.HOME:
332 index = 0;
333 while (index < fScaledData.fLastBucket && fScaledData.fData[index] == 0)
334 index++;
335 if (index < fScaledData.fLastBucket)
336 fScaledData.fCurrentBucket = index;
337 break;
338
339 case SWT.ARROW_RIGHT:
340 index = fScaledData.fCurrentBucket + 1;
341 while (index < fScaledData.fWidth && fScaledData.fData[index] == 0)
342 index++;
343 if (index < fScaledData.fLastBucket)
344 fScaledData.fCurrentBucket = index;
345 break;
346
347 case SWT.END:
348 index = fScaledData.fLastBucket;
349 while (index >= 0 && fScaledData.fData[index] == 0)
350 index--;
351 if (index >= 0)
352 fScaledData.fCurrentBucket = index;
353 break;
354
355 case SWT.ARROW_LEFT:
356 index = fScaledData.fCurrentBucket - 1;
357 while (index >= 0 && fScaledData.fData[index] == 0)
358 index--;
359 if (index >= 0)
360 fScaledData.fCurrentBucket = index;
361 break;
362
363 default:
364 return;
365 }
366
367 updateCurrentEventTime();
368 }
369
370 /**
371 * Refresh the histogram display
372 */
373 protected void refresh() {
374 if (!fCanvas.isDisposed() && fCanvas.getDisplay() != null) {
375 fCanvas.getDisplay().asyncExec(new Runnable() {
376 @Override
377 public void run() {
378 if (!fCanvas.isDisposed()) {
379 // Retrieve and normalize the data
380 int canvasWidth = fCanvas.getBounds().width;
381 int canvasHeight = fCanvas.getBounds().height;
40890fec
FC
382 if (canvasWidth <= 0 || canvasHeight <= 0)
383 return;
c392540b
FC
384 fDataModel.setCurrentEvent(fCurrentEventTime);
385 fScaledData = fDataModel.scaleTo(canvasWidth, canvasHeight);
c392540b 386 fCanvas.redraw();
74237cc3 387 // Display histogram and update X-,Y-axis labels
c392540b
FC
388 fTimeRangeStartText.setText(HistogramUtils.nanosecondsToString(fDataModel.getStartTime()));
389 fTimeRangeEndText.setText(HistogramUtils.nanosecondsToString(fDataModel.getEndTime()));
74237cc3 390 fMaxNbEventsText.setText(Long.toString(fScaledData.fMaxValue));
c392540b
FC
391 // The Y-axis area might need to be re-sized
392 fMaxNbEventsText.getParent().layout();
393 }
394 }
395 });
396 }
397 }
398
399 // ------------------------------------------------------------------------
400 // Helper functions
401 // ------------------------------------------------------------------------
402
403 private void updateCurrentEventTime() {
404 long bucketStartTime = getTimestamp(fScaledData.fCurrentBucket);
405 ((HistogramView) fParentView).updateCurrentEventTime(bucketStartTime);
406 }
407
408 // ------------------------------------------------------------------------
409 // PaintListener
410 // ------------------------------------------------------------------------
411
412 protected final String IMAGE_KEY = "double-buffer-image"; //$NON-NLS-1$
413
414 @Override
415 public void paintControl(PaintEvent event) {
416
417 // Get the geometry
418 int canvasWidth = fCanvas.getBounds().width;
419 int canvasHeight = fCanvas.getBounds().height;
420
421 // Make sure we have something to draw upon
422 if (canvasWidth <= 0 || canvasHeight <= 0)
423 return;
424
425 // Retrieve image; re-create only if necessary
426 Image image = (Image) fCanvas.getData(IMAGE_KEY);
427 if (image == null || image.getBounds().width != canvasWidth || image.getBounds().height != canvasHeight) {
428 image = new Image(event.display, canvasWidth, canvasHeight);
429 fCanvas.setData(IMAGE_KEY, image);
430 }
431
432 // Draw the histogram on its canvas
433 GC imageGC = new GC(image);
434 formatImage(imageGC, image);
435 event.gc.drawImage(image, 0, 0);
436 imageGC.dispose();
437 }
438
439 private void formatImage(GC imageGC, Image image) {
440
441 if (fScaledData == null)
442 return;
443
444 HistogramScaledData scaledData = new HistogramScaledData(fScaledData);
445
446 try {
447 // Get drawing boundaries
448 int width = image.getBounds().width;
449 int height = image.getBounds().height;
450
451 // Clear the drawing area
452 imageGC.setBackground(fBackgroundColor);
453 imageGC.fillRectangle(0, 0, image.getBounds().width + 1, image.getBounds().height + 1);
454
455 // Draw the histogram bars
456 imageGC.setBackground(fHistoBarColor);
457 int limit = width < scaledData.fWidth ? width : scaledData.fWidth;
458 for (int i = 1; i < limit; i++) {
459 int value = (int) (scaledData.fData[i] * scaledData.fScalingFactor);
460 imageGC.fillRectangle(i, height - value, 1, value);
461 }
462
463 // Draw the current event bar
464 int currentBucket = scaledData.fCurrentBucket;
465 if (currentBucket >= 0 && currentBucket < limit) {
466 drawDelimiter(imageGC, fCurrentEventColor, height, currentBucket);
467 }
468
469 // Add a dashed line as a delimiter (at the right of the last bar)
470 int lastEventIndex = limit - 1;
471 while (lastEventIndex >= 0 && scaledData.fData[lastEventIndex] == 0)
472 lastEventIndex--;
473 lastEventIndex += (lastEventIndex < limit - 1) ? 1 : 0;
474 drawDelimiter(imageGC, fLastEventColor, height, lastEventIndex);
475 } catch (Exception e) {
476 // Do nothing
477 }
478 }
479
480 private void drawDelimiter(GC imageGC, Color color, int height, int index) {
481 imageGC.setBackground(color);
482 int dash = height / 4;
483 imageGC.fillRectangle(index, 0 * dash, 1, dash - 1);
484 imageGC.fillRectangle(index, 1 * dash, 1, dash - 1);
485 imageGC.fillRectangle(index, 2 * dash, 1, dash - 1);
486 imageGC.fillRectangle(index, 3 * dash, 1, height - 3 * dash);
487 }
488
489 // ------------------------------------------------------------------------
490 // KeyListener
491 // ------------------------------------------------------------------------
492
493 @Override
494 public void keyPressed(KeyEvent event) {
495 moveCursor(event.keyCode);
496 }
497
498 @Override
499 public void keyReleased(KeyEvent event) {
500 }
501
502 // ------------------------------------------------------------------------
503 // MouseListener
504 // ------------------------------------------------------------------------
505
506 @Override
507 public void mouseDoubleClick(MouseEvent event) {
508 }
509
510 @Override
511 public void mouseDown(MouseEvent event) {
512 if (fDataModel.getNbEvents() > 0 && fScaledData.fLastBucket >= event.x) {
513 fScaledData.fCurrentBucket = event.x;
514 updateCurrentEventTime();
515 }
516 }
517
518 @Override
519 public void mouseUp(MouseEvent event) {
520 }
521
522 // ------------------------------------------------------------------------
523 // MouseTrackListener
524 // ------------------------------------------------------------------------
525
526 @Override
527 public void mouseEnter(MouseEvent event) {
528 }
529
530 @Override
531 public void mouseExit(MouseEvent event) {
532 }
533
534 @Override
535 public void mouseHover(MouseEvent event) {
74237cc3 536 if (fDataModel.getNbEvents() > 0 && fScaledData != null && fScaledData.fLastBucket >= event.x) {
c392540b
FC
537 String tooltip = formatToolTipLabel(event.x);
538 fCanvas.setToolTipText(tooltip);
539 }
540 }
541
542 private String formatToolTipLabel(int index) {
543 long startTime = fDataModel.getStartTime() + fScaledData.fCurrentBucket * fScaledData.fBucketDuration;
544 long endTime = startTime + fScaledData.fBucketDuration;
74237cc3 545 int nbEvents = (index >= 0) ? fScaledData.fData[index] : 0;
c392540b
FC
546
547 StringBuffer buffer = new StringBuffer();
548 buffer.append("Range = ["); //$NON-NLS-1$
549 buffer.append(HistogramUtils.nanosecondsToString(startTime));
550 buffer.append(","); //$NON-NLS-1$
551 buffer.append(HistogramUtils.nanosecondsToString(endTime));
552 buffer.append(")\n"); //$NON-NLS-1$
553 buffer.append("Event count = "); //$NON-NLS-1$
554 buffer.append(nbEvents);
555 return buffer.toString();
556 }
557
558 // ------------------------------------------------------------------------
559 // ControlListener
560 // ------------------------------------------------------------------------
561
562 @Override
563 public void controlMoved(ControlEvent event) {
564 refresh();
565 }
566
567 @Override
568 public void controlResized(ControlEvent event) {
569 refresh();
570 }
571
572}
This page took 0.066845 seconds and 5 git commands to generate.