eb4f839dca7fda4ea79160ecc589d77ae2b51ceb
[deliverable/tracecompass.git] / analysis / org.eclipse.tracecompass.analysis.lami.ui / src / org / eclipse / tracecompass / internal / provisional / analysis / lami / ui / viewers / LamiXYChartViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 EfficiOS Inc., Michael Jeanson
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
10 package org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.viewers;
11
12 import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
13 import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;
14
15 import java.text.Format;
16 import java.util.ArrayList;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Set;
20 import java.util.concurrent.TimeUnit;
21 import java.util.function.ToDoubleFunction;
22
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.events.SelectionListener;
28 import org.eclipse.swt.graphics.Color;
29 import org.eclipse.swt.graphics.Font;
30 import org.eclipse.swt.graphics.GC;
31 import org.eclipse.swt.graphics.Image;
32 import org.eclipse.swt.graphics.Point;
33 import org.eclipse.swt.graphics.Rectangle;
34 import org.eclipse.swt.widgets.Composite;
35 import org.eclipse.swt.widgets.Control;
36 import org.eclipse.swt.widgets.Display;
37 import org.eclipse.swt.widgets.Event;
38 import org.eclipse.swt.widgets.Listener;
39 import org.eclipse.swt.widgets.ToolBar;
40 import org.eclipse.swt.widgets.ToolItem;
41 import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
42 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.aspect.LamiTableEntryAspect;
43 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiChartModel;
44 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiResultTable;
45 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTableEntry;
46 import org.eclipse.tracecompass.internal.provisional.analysis.lami.core.module.LamiTimeStampFormat;
47 import org.eclipse.tracecompass.internal.provisional.analysis.lami.ui.signals.LamiSelectionUpdateSignal;
48 import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
49 import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;
50 import org.eclipse.ui.ISharedImages;
51 import org.eclipse.ui.PlatformUI;
52 import org.swtchart.Chart;
53 import org.swtchart.ITitle;
54
55 import com.google.common.collect.ImmutableList;
56
57 /**
58 * Abstract XYChart Viewer for LAMI views.
59 *
60 * @author Michael Jeanson
61 *
62 */
63 public abstract class LamiXYChartViewer extends TmfViewer implements ILamiViewer {
64
65 /** Ellipsis character */
66 protected static final String ELLIPSIS = "…"; //$NON-NLS-1$
67
68 /**
69 * String representing unknown values. Can be present even in numerical
70 * aspects!
71 */
72 protected static final String UNKNOWN = "?"; //$NON-NLS-1$
73
74 /** Zero value */
75 protected static final double ZERO = 0.0;
76
77 /** Symbol for seconds (used in the custom ns -> s conversion) */
78 private static final String SECONDS_SYMBOL = "s"; //$NON-NLS-1$
79
80 /** Symbol for nanoseconds (used in the custom ns -> s conversion) */
81 private static final String NANOSECONDS_SYMBOL = "ns"; //$NON-NLS-1$
82
83 /**
84 * Function to use to map Strings read from the data table to doubles for
85 * use in SWTChart series.
86 */
87 protected static final ToDoubleFunction<@Nullable String> DOUBLE_MAPPER = str -> {
88 if (str == null || str.equals(UNKNOWN)) {
89 return ZERO;
90 }
91 return Double.parseDouble(str);
92 };
93
94 /**
95 * List of standard colors
96 */
97 protected static final List<@NonNull Color> COLORS = ImmutableList.of(
98 new Color(Display.getDefault(), 72, 120, 207),
99 new Color(Display.getDefault(), 106, 204, 101),
100 new Color(Display.getDefault(), 214, 95, 95),
101 new Color(Display.getDefault(), 180, 124, 199),
102 new Color(Display.getDefault(), 196, 173, 102),
103 new Color(Display.getDefault(), 119, 190, 219)
104 );
105
106 /**
107 * List of "light" colors (when unselected)
108 */
109 protected static final List<@NonNull Color> LIGHT_COLORS = ImmutableList.of(
110 new Color(Display.getDefault(), 173, 195, 233),
111 new Color(Display.getDefault(), 199, 236, 197),
112 new Color(Display.getDefault(), 240, 196, 196),
113 new Color(Display.getDefault(), 231, 213, 237),
114 new Color(Display.getDefault(), 231, 222, 194),
115 new Color(Display.getDefault(), 220, 238, 246)
116 );
117
118 /**
119 * Time stamp formatter for intervals in the days range.
120 */
121 protected static final LamiTimeStampFormat DAYS_FORMATTER = new LamiTimeStampFormat("dd HH:mm"); //$NON-NLS-1$
122
123 /**
124 * Time stamp formatter for intervals in the hours range.
125 */
126 protected static final LamiTimeStampFormat HOURS_FORMATTER = new LamiTimeStampFormat("HH:mm"); //$NON-NLS-1$
127
128 /**
129 * Time stamp formatter for intervals in the minutes range.
130 */
131 protected static final LamiTimeStampFormat MINUTES_FORMATTER = new LamiTimeStampFormat("mm:ss"); //$NON-NLS-1$
132
133 /**
134 * Time stamp formatter for intervals in the seconds range.
135 */
136 protected static final LamiTimeStampFormat SECONDS_FORMATTER = new LamiTimeStampFormat("ss"); //$NON-NLS-1$
137
138 /**
139 * Time stamp formatter for intervals in the milliseconds range.
140 */
141 protected static final LamiTimeStampFormat MILLISECONDS_FORMATTER = new LamiTimeStampFormat("ss.SSS"); //$NON-NLS-1$
142
143 /**
144 * Decimal formatter to display nanoseconds as seconds.
145 */
146 protected static final DecimalUnitFormat NANO_TO_SECS_FORMATTER = new DecimalUnitFormat(0.000000001);
147
148 /**
149 * Default decimal formatter.
150 */
151 protected static final DecimalUnitFormat DECIMAL_FORMATTER = new DecimalUnitFormat();
152
153 private final Listener fResizeListener = event -> {
154 /* Refresh the titles to fit the current chart size */
155 refreshDisplayTitles();
156
157 /* Refresh the Axis labels to fit the current chart size */
158 refreshDisplayLabels();
159 };
160
161 private final LamiResultTable fResultTable;
162 private final LamiChartModel fChartModel;
163
164 private final Chart fChart;
165
166 private final String fChartTitle;
167 private final String fXTitle;
168 private final String fYTitle;
169
170 private boolean fSelected;
171 private Set<Integer> fSelection;
172
173 private final ToolBar fToolBar;
174
175 /**
176 * Creates a Viewer instance based on SWTChart.
177 *
178 * @param parent
179 * The parent composite to draw in.
180 * @param resultTable
181 * The result table containing the data from which to build the
182 * chart
183 * @param chartModel
184 * The information about the chart to build
185 */
186 public LamiXYChartViewer(Composite parent, LamiResultTable resultTable, LamiChartModel chartModel) {
187 super(parent);
188
189 fParent = parent;
190 fResultTable = resultTable;
191 fChartModel = chartModel;
192 fSelection = new HashSet<>();
193
194 fChart = new Chart(parent, SWT.NONE);
195 fChart.addListener(SWT.Resize, fResizeListener);
196
197 /* Set Chart title */
198 fChartTitle = fResultTable.getTableClass().getTableTitle();
199
200 /* Set X axis title */
201 if (fChartModel.getXSeriesColumns().size() == 1) {
202 /*
203 * There is only 1 series in the chart, we will use its name as the
204 * Y axis (and hide the legend).
205 */
206 String seriesName = getChartModel().getXSeriesColumns().get(0);
207 // The time duration formatter converts ns to s on the axis
208 if (NANOSECONDS_SYMBOL.equals(getXAxisAspects().get(0).getUnits())) {
209 seriesName = getXAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
210 }
211 fXTitle = seriesName;
212 } else {
213 /*
214 * There are multiple series in the chart, if they all share the same
215 * units, display that.
216 */
217 long nbDiffAspects = getXAxisAspects().stream()
218 .map(aspect -> aspect.getUnits())
219 .distinct()
220 .count();
221
222 String units = getXAxisAspects().get(0).getUnits();
223 if (nbDiffAspects == 1 && units != null) {
224 /* All aspects use the same unit type */
225
226 // The time duration formatter converts ns to s on the axis
227 if (NANOSECONDS_SYMBOL.equals(units)) {
228 units = SECONDS_SYMBOL;
229 }
230 fXTitle = Messages.LamiViewer_DefaultValueName + " (" + units + ')'; //$NON-NLS-1$
231 } else {
232 /* Various unit types, just say "Value" */
233 fXTitle = nullToEmptyString(Messages.LamiViewer_DefaultValueName);
234 }
235 }
236
237 /* Set Y axis title */
238 if (fChartModel.getYSeriesColumns().size() == 1) {
239 /*
240 * There is only 1 series in the chart, we will use its name as the
241 * Y axis (and hide the legend).
242 */
243 String seriesName = getChartModel().getYSeriesColumns().get(0);
244 // The time duration formatter converts ns to s on the axis
245 if (NANOSECONDS_SYMBOL.equals(getYAxisAspects().get(0).getUnits())) {
246 seriesName = getYAxisAspects().get(0).getName() + " (" + SECONDS_SYMBOL + ')'; //$NON-NLS-1$
247 }
248 fYTitle = seriesName;
249 fChart.getLegend().setVisible(false);
250 } else {
251 /*
252 * There are multiple series in the chart, if they all share the same
253 * units, display that.
254 */
255 long nbDiffAspects = getYAxisAspects().stream()
256 .map(aspect -> aspect.getUnits())
257 .distinct()
258 .count();
259
260 String units = getYAxisAspects().get(0).getUnits();
261 if (nbDiffAspects == 1 && units != null) {
262 /* All aspects use the same unit type */
263
264 // The time duration formatter converts ns to s on the axis
265 if (NANOSECONDS_SYMBOL.equals(units)) {
266 units = SECONDS_SYMBOL;
267 }
268 fYTitle = Messages.LamiViewer_DefaultValueName + " (" + units + ')'; //$NON-NLS-1$
269 } else {
270 /* Various unit types, just say "Value" */
271 fYTitle = nullToEmptyString(Messages.LamiViewer_DefaultValueName);
272 }
273
274 /* Put legend at the bottom */
275 fChart.getLegend().setPosition(SWT.BOTTOM);
276 }
277
278 /* Set all titles and labels font color to black */
279 fChart.getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
280 fChart.getAxisSet().getXAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
281 fChart.getAxisSet().getYAxis(0).getTitle().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
282 fChart.getAxisSet().getXAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
283 fChart.getAxisSet().getYAxis(0).getTick().setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
284
285 /* Set X label 90 degrees */
286 fChart.getAxisSet().getXAxis(0).getTick().setTickLabelAngle(90);
287
288 /* Refresh the titles to fit the current chart size */
289 refreshDisplayTitles();
290
291 fToolBar = createChartToolBar();
292
293 fChart.addDisposeListener(e -> {
294 /* Dispose resources of this class */
295 LamiXYChartViewer.super.dispose();
296 });
297 }
298
299 /**
300 * Util method to check if a list of aspects are all continuous.
301 *
302 * @param axisAspects
303 * The list of aspects to check.
304 * @return true is all aspects are continuous, otherwise false.
305 */
306 protected static boolean areAspectsContinuous(List<LamiTableEntryAspect> axisAspects) {
307 return axisAspects.stream().allMatch(aspect -> aspect.isContinuous());
308 }
309
310 /**
311 * Util method to check if a list of aspects are all time stamps.
312 *
313 * @param axisAspects
314 * The list of aspects to check.
315 * @return true is all aspects are time stamps, otherwise false.
316 */
317 protected static boolean areAspectsTimeStamp(List<LamiTableEntryAspect> axisAspects) {
318 return axisAspects.stream().allMatch(aspect -> aspect.isTimeStamp());
319 }
320
321 /**
322 * Util method to check if a list of aspects are all time durations.
323 *
324 * @param axisAspects
325 * The list of aspects to check.
326 * @return true is all aspects are time durations, otherwise false.
327 */
328 protected static boolean areAspectsTimeDuration(List<LamiTableEntryAspect> axisAspects) {
329 return axisAspects.stream().allMatch(aspect -> aspect.isTimeDuration());
330 }
331
332 /**
333 * Util method that will return a formatter based on the aspects linked to an axis
334 *
335 * If all aspects are time stamps, return a timestamp formatter tuned to the interval.
336 * If all aspects are time durations, return the nanoseconds to seconds formatter.
337 * Otherwise, return the generic decimal formatter.
338 *
339 * @param axisAspects
340 * The list of aspects of the axis.
341 * @param entries
342 * The list of entries of the chart.
343 * @return a formatter for the axis.
344 */
345 protected static Format getContinuousAxisFormatter(List<LamiTableEntryAspect> axisAspects, List<LamiTableEntry> entries) {
346
347 if (areAspectsTimeStamp(axisAspects)) {
348 /* Set a TimeStamp formatter depending on the duration between the first and last value */
349 double max = Double.MIN_VALUE;
350 double min = Double.MAX_VALUE;
351
352 for (LamiTableEntry entry : entries) {
353 for (LamiTableEntryAspect aspect : axisAspects) {
354 Double current = aspect.resolveDouble(entry);
355 if (current != null) {
356 max = Math.max(max, current);
357 min = Math.min(min, current);
358 }
359 }
360 }
361 long duration = (long) max - (long) min;
362
363 if (duration > TimeUnit.DAYS.toNanos(1)) {
364 return DAYS_FORMATTER;
365 } else if (duration > TimeUnit.HOURS.toNanos(1)) {
366 return HOURS_FORMATTER;
367 } else if (duration > TimeUnit.MINUTES.toNanos(1)) {
368 return MINUTES_FORMATTER;
369 } else if (duration > TimeUnit.SECONDS.toNanos(15)) {
370 return SECONDS_FORMATTER;
371 } else {
372 return MILLISECONDS_FORMATTER;
373 }
374 } else if (areAspectsTimeDuration(axisAspects)) {
375 /* Set the time duration formatter */
376 return NANO_TO_SECS_FORMATTER;
377
378 } else {
379 /* For other numeric aspects, use the default decimal unit formatter */
380 return DECIMAL_FORMATTER;
381 }
382 }
383
384 /**
385 * Get the chart result table.
386 *
387 * @return The chart result table.
388 */
389 protected LamiResultTable getResultTable() {
390 return fResultTable;
391 }
392
393 /**
394 * Get the chart model.
395 *
396 * @return The chart model.
397 */
398 protected LamiChartModel getChartModel() {
399 return fChartModel;
400 }
401
402 /**
403 * Get the chart object.
404 * @return The chart object.
405 */
406 protected Chart getChart() {
407 return fChart;
408 }
409
410 /**
411 * @return the toolBar
412 */
413 public ToolBar getToolBar() {
414 return fToolBar;
415 }
416
417 /**
418 * Is a selection made in the chart.
419 *
420 * @return true if there is a selection.
421 */
422 protected boolean isSelected() {
423 return fSelected;
424 }
425
426 /**
427 * Set the selection index.
428 *
429 * @param selection the index to select.
430 */
431 protected void setSelection(Set<Integer> selection) {
432 fSelection = selection;
433 fSelected = !selection.isEmpty();
434 }
435
436 /**
437 * Unset the chart selection.
438 */
439 protected void unsetSelection() {
440 fSelection.clear();
441 fSelected = false;
442 }
443
444 /**
445 * Get the current selection index.
446 *
447 * @return the current selection index.
448 */
449 protected Set<Integer> getSelection() {
450 return fSelection;
451 }
452
453 @Override
454 public @Nullable Control getControl() {
455 return fChart.getParent();
456 }
457
458 @Override
459 public void refresh() {
460 Display.getDefault().asyncExec(() -> {
461 if (!fChart.isDisposed()) {
462 fChart.redraw();
463 }
464 });
465 }
466
467 @Override
468 public void dispose() {
469 fChart.dispose();
470 /* The control's DisposeListener will call super.dispose() */
471 }
472
473 /**
474 * Get a list of all the aspect of the Y axis.
475 *
476 * @return The aspects for the Y axis
477 */
478 protected List<LamiTableEntryAspect> getYAxisAspects() {
479
480 List<LamiTableEntryAspect> yAxisAspects = new ArrayList<>();
481
482 for (String colName : getChartModel().getYSeriesColumns()) {
483 yAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
484 }
485
486 return yAxisAspects;
487 }
488
489 /**
490 * Get a list of all the aspect of the X axis.
491 *
492 * @return The aspects for the X axis
493 */
494 protected List<LamiTableEntryAspect> getXAxisAspects() {
495
496 List<LamiTableEntryAspect> xAxisAspects = new ArrayList<>();
497
498 for (String colName : getChartModel().getXSeriesColumns()) {
499 xAxisAspects.add(checkNotNull(getAspectFromName(getResultTable().getTableClass().getAspects(), colName)));
500 }
501
502 return xAxisAspects;
503 }
504
505 /**
506 * Set the ITitle object text to a substring of canonicalTitle that when
507 * rendered in the chart will fit maxPixelLength.
508 */
509 private void refreshDisplayTitle(ITitle title, String canonicalTitle, int maxPixelLength) {
510 if (title.isVisible()) {
511
512 String newTitle = canonicalTitle;
513
514 /* Get the title font */
515 Font font = title.getFont();
516
517 GC gc = new GC(fParent);
518 gc.setFont(font);
519
520 /* Get the length and height of the canonical title in pixels */
521 Point pixels = gc.stringExtent(canonicalTitle);
522
523 /*
524 * If the title is too long, generate a shortened version based on the
525 * average character width of the current font.
526 */
527 if (pixels.x > maxPixelLength) {
528 int charwidth = gc.getFontMetrics().getAverageCharWidth();
529
530 int minimum = 3;
531
532 int strLen = ((maxPixelLength / charwidth) - minimum);
533
534 if (strLen > minimum) {
535 newTitle = canonicalTitle.substring(0, strLen) + ELLIPSIS;
536 } else {
537 newTitle = ELLIPSIS;
538 }
539 }
540
541 title.setText(newTitle);
542
543 // Cleanup
544 gc.dispose();
545 }
546 }
547
548 /**
549 * Refresh the Chart, XAxis and YAxis titles to fit the current
550 * chart size.
551 */
552 private void refreshDisplayTitles() {
553 Rectangle chartRect = fChart.getClientArea();
554 Rectangle plotRect = fChart.getPlotArea().getClientArea();
555
556 ITitle chartTitle = checkNotNull(fChart.getTitle());
557 refreshDisplayTitle(chartTitle, fChartTitle, chartRect.width);
558
559 ITitle xTitle = checkNotNull(fChart.getAxisSet().getXAxis(0).getTitle());
560 refreshDisplayTitle(xTitle, fXTitle, plotRect.width);
561
562 ITitle yTitle = checkNotNull(fChart.getAxisSet().getYAxis(0).getTitle());
563 refreshDisplayTitle(yTitle, fYTitle, plotRect.height);
564 }
565
566 /**
567 * Get the aspect with the given name
568 *
569 * @param aspects
570 * The list of aspects to search into
571 * @param aspectName
572 * The name of the aspect we are looking for
573 * @return The corresponding aspect
574 */
575 protected static @Nullable LamiTableEntryAspect getAspectFromName(List<LamiTableEntryAspect> aspects, String aspectName) {
576 for (LamiTableEntryAspect lamiTableEntryAspect : aspects) {
577
578 if (lamiTableEntryAspect.getLabel().equals(aspectName)) {
579 return lamiTableEntryAspect;
580 }
581 }
582
583 return null;
584 }
585
586 /**
587 * Refresh the axis labels to fit the current chart size.
588 */
589 protected abstract void refreshDisplayLabels();
590
591 /**
592 * Redraw the chart.
593 */
594 protected void redraw() {
595 refresh();
596 }
597
598 /**
599 * Signal handler for selection update.
600 *
601 * @param signal
602 * The selection update signal
603 */
604 @TmfSignalHandler
605 public void updateSelection(LamiSelectionUpdateSignal signal) {
606 if (getResultTable().hashCode() != signal.getSignalHash() || equals(signal.getSource())) {
607 /* The signal is not for us */
608 return;
609 }
610 setSelection(signal.getEntryIndex());
611
612 redraw();
613 }
614
615 /**
616 * Create a tool bar on top right of the chart. Contained actions:
617 * <ul>
618 * <li>Dispose the current viewer, also known as "Close the chart"</li>
619 * </ul>
620 *
621 * This tool bar should only appear when the mouse enters the composite.
622 *
623 * @return the tool bar
624 */
625 protected ToolBar createChartToolBar() {
626 Image removeImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_ELCL_REMOVE);
627 ToolBar toolBar = new ToolBar(getChart(), SWT.HORIZONTAL);
628
629 /* Default state */
630 toolBar.moveAbove(null);
631 toolBar.setVisible(false);
632
633 /*
634 * Close chart button
635 */
636 ToolItem closeButton = new ToolItem(toolBar, SWT.PUSH);
637 closeButton.setImage(removeImage);
638 closeButton.setToolTipText(Messages.LamiXYChartViewer_CloseChartToolTip);
639 closeButton.addSelectionListener(new SelectionListener() {
640 @Override
641 public void widgetSelected(@Nullable SelectionEvent e) {
642 Composite parent = getParent();
643 dispose();
644 parent.layout();
645 }
646
647 @Override
648 public void widgetDefaultSelected(@Nullable SelectionEvent e) {
649 }
650 });
651
652 toolBar.pack();
653 toolBar.setLocation(new Point(getChart().getSize().x - toolBar.getSize().x, 0));
654
655 /* Visibility toggle filter */
656 Listener toolBarVisibilityToggleListener = e -> {
657 if (e.widget instanceof Control) {
658 Control control = (Control) e.widget;
659 Point display = control.toDisplay(e.x, e.y);
660 Point location = getChart().getParent().toControl(display);
661
662 /*
663 * Only set to visible if we are at the right location, in the
664 * right shell.
665 */
666 boolean visible = getChart().getBounds().contains(location) &&
667 control.getShell().equals(getChart().getShell());
668 getToolBar().setVisible(visible);
669 }
670 };
671
672 /* Filter to make sure we hide the toolbar if we exit the window */
673 Listener hideToolBarListener = (e -> getToolBar().setVisible(false));
674
675 /*
676 * Add the filters to the main Display, and remove them when we dispose
677 * the chart.
678 */
679 Display display = getChart().getDisplay();
680 display.addFilter(SWT.MouseEnter, toolBarVisibilityToggleListener);
681 display.addFilter(SWT.MouseExit, hideToolBarListener);
682
683 getChart().addDisposeListener(e -> {
684 display.removeFilter(SWT.MouseEnter, toolBarVisibilityToggleListener);
685 display.removeFilter(SWT.MouseExit, hideToolBarListener);
686 });
687
688 /* Reposition the tool bar on resize */
689 getChart().addListener(SWT.Resize, new Listener() {
690 @Override
691 public void handleEvent(@Nullable Event event) {
692 toolBar.setLocation(new Point(getChart().getSize().x - toolBar.getSize().x, 0));
693 }
694 });
695
696 return toolBar;
697 }
698 }
This page took 0.048881 seconds and 4 git commands to generate.