Commit | Line | Data |
---|---|---|
4208b510 AM |
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; | |
7710e6ed JR |
26 | import org.eclipse.swt.events.SelectionEvent; |
27 | import org.eclipse.swt.events.SelectionListener; | |
4208b510 AM |
28 | import org.eclipse.swt.graphics.Color; |
29 | import org.eclipse.swt.graphics.Font; | |
30 | import org.eclipse.swt.graphics.GC; | |
7710e6ed | 31 | import org.eclipse.swt.graphics.Image; |
4208b510 AM |
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; | |
7710e6ed | 37 | import org.eclipse.swt.widgets.Event; |
4208b510 | 38 | import org.eclipse.swt.widgets.Listener; |
7710e6ed JR |
39 | import org.eclipse.swt.widgets.ToolBar; |
40 | import org.eclipse.swt.widgets.ToolItem; | |
4208b510 AM |
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; | |
7710e6ed JR |
50 | import org.eclipse.ui.ISharedImages; |
51 | import org.eclipse.ui.PlatformUI; | |
4208b510 AM |
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 | ||
7710e6ed JR |
173 | private final ToolBar fToolBar; |
174 | ||
4208b510 AM |
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 | ||
7710e6ed JR |
291 | fToolBar = createChartToolBar(); |
292 | ||
4208b510 AM |
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 | ||
7710e6ed JR |
410 | /** |
411 | * @return the toolBar | |
412 | */ | |
413 | public ToolBar getToolBar() { | |
414 | return fToolBar; | |
415 | } | |
416 | ||
4208b510 AM |
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 | } | |
7710e6ed JR |
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 | } | |
4208b510 | 698 | } |