tmf: Bug 490400: Leaking widgets due to incorrect cleanup in dispose()
[deliverable/tracecompass.git] / tmf / org.eclipse.tracecompass.tmf.ui / src / org / eclipse / tracecompass / internal / tmf / ui / viewers / piecharts / TmfPieChartViewer.java
1 /*******************************************************************************
2 * Copyright (c) 2015, 2016 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 * Alexis Cabana-Loriaux - Initial API and implementation
11 *
12 *******************************************************************************/
13
14 package org.eclipse.tracecompass.internal.tmf.ui.viewers.piecharts;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22
23 import org.eclipse.core.runtime.ListenerList;
24 import org.eclipse.linuxtools.dataviewers.piechart.PieChart;
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.events.MouseEvent;
27 import org.eclipse.swt.events.MouseListener;
28 import org.eclipse.swt.layout.FillLayout;
29 import org.eclipse.swt.widgets.Composite;
30 import org.eclipse.swt.widgets.Event;
31 import org.eclipse.swt.widgets.Listener;
32 import org.eclipse.tracecompass.internal.tmf.ui.viewers.piecharts.model.TmfPieChartStatisticsModel;
33 import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
34
35 /**
36 * Creates a viewer containing 2 pie charts, one for showing information about
37 * the current selection, and the second one for showing information about the
38 * current time-range selection. It follows the MVC pattern, being a view.
39 *
40 * This class is closely related with the IPieChartViewerState interface that
41 * acts as a state machine for the general layout of the charts.
42 *
43 * @author Alexis Cabana-Loriaux
44 * @since 2.0
45 *
46 */
47 public class TmfPieChartViewer extends Composite {
48
49 /**
50 * The pie chart containing global information about the trace
51 */
52 private PieChart fGlobalPC;
53
54 /**
55 * The name of the piechart containing the statistics about the global trace
56 */
57 private String fGlobalPCname;
58
59 /**
60 * The pie chart containing information about the current time-range
61 * selection
62 */
63 private PieChart fTimeRangePC;
64
65 /**
66 * The name of the piechart containing the statistics about the current
67 * selection
68 */
69 private String fTimeRangePCname;
70
71 /**
72 * The listener for the mouse movement event.
73 */
74 private Listener fMouseMoveListener;
75
76 /**
77 * The listener for the mouse right click event.
78 */
79 private MouseListener fMouseClickListener;
80
81 /**
82 * The list of listener to notify when an event type is selected
83 */
84 private ListenerList fEventTypeSelectedListeners = new ListenerList(ListenerList.IDENTITY);
85
86 /**
87 * The name of the slice containing the too little slices
88 */
89 private String fOthersSliceName;
90
91 /**
92 * Implementation of the State design pattern to reorder the layout
93 * depending on the selection. This variable holds the current state of the
94 * layout.
95 */
96 private IPieChartViewerState fCurrentState;
97
98 /**
99 * Represents the minimum percentage a slice of pie must have in order to be
100 * shown
101 */
102 private static final float MIN_PRECENTAGE_TO_SHOW_SLICE = 0.025F;// 2.5%
103
104 /**
105 * Represents the maximum number of slices of the pie charts. WE don't want
106 * to pollute the viewer with too much slice entries.
107 */
108 private static final int NB_MAX_SLICES = 10;
109
110 /**
111 * The data that has to be presented by the pie charts
112 */
113 private TmfPieChartStatisticsModel fModel = null;
114
115 /**
116 * @param parent
117 * The parent composite that will hold the viewer
118 */
119 public TmfPieChartViewer(Composite parent) {
120 super(parent, SWT.NONE);
121 fGlobalPCname = Messages.TmfStatisticsView_GlobalSelectionPieChartName;
122 fTimeRangePCname = Messages.TmfStatisticsView_TimeRangeSelectionPieChartName;
123 fOthersSliceName = Messages.TmfStatisticsView_PieChartOthersSliceName;
124 initContent();
125 }
126
127 // ------------------------------------------------------------------------
128 // Class methods
129 // ------------------------------------------------------------------------
130
131 /**
132 * Called by this class' constructor. Constructs the basic viewer containing
133 * the charts, as well as their listeners
134 */
135 private synchronized void initContent() {
136 setLayout(new FillLayout());
137
138 fGlobalPC = null;
139 fTimeRangePC = null;
140
141 // Setup listeners for the tooltips
142 fMouseMoveListener = new Listener() {
143 @Override
144 public void handleEvent(org.eclipse.swt.widgets.Event event) {
145 PieChart pc = (PieChart) event.widget;
146 switch (event.type) {
147 /* Get tooltip information on the slice */
148 case SWT.MouseMove:
149 int sliceIndex = pc.getSliceIndexFromPosition(0, event.x, event.y);
150 if (sliceIndex < 0) {
151 // mouse is outside the chart
152 pc.setToolTipText(null);
153 break;
154 }
155 float percOfSlice = (float) pc.getSlicePercent(0, sliceIndex);
156 String percent = String.format("%.1f", percOfSlice); //$NON-NLS-1$
157 Long nbEvents = Long.valueOf((long) pc.getSeriesSet().getSeries()[sliceIndex].getXSeries()[0]);
158
159 String text = Messages.TmfStatisticsView_PieChartToolTipTextName + " = " + //$NON-NLS-1$
160 pc.getSeriesSet().getSeries()[sliceIndex].getId() + "\n"; //$NON-NLS-1$
161
162 text += Messages.TmfStatisticsView_PieChartToolTipTextEventCount + " = "//$NON-NLS-1$
163 + nbEvents.toString() + " (" + percent + "%)"; //$NON-NLS-1$ //$NON-NLS-2$
164 pc.setToolTipText(text);
165 return;
166 default:
167 }
168 }
169 };
170
171 fMouseClickListener = new MouseListener() {
172
173 @Override
174 public void mouseUp(MouseEvent e) {
175 }
176
177 @Override
178 public void mouseDown(MouseEvent e) {
179 PieChart pc = (PieChart) e.widget;
180 int slicenb = pc.getSliceIndexFromPosition(0, e.x, e.y);
181 if (slicenb < 0 || slicenb >= pc.getSeriesSet().getSeries().length) {
182 // mouse is outside the chart
183 return;
184 }
185 Event selectionEvent = new Event();
186 selectionEvent.text = pc.getSeriesSet().getSeries()[slicenb].getId();
187 notifyEventTypeSelectionListener(selectionEvent);
188 }
189
190 @Override
191 public void mouseDoubleClick(MouseEvent e) {
192 }
193 };
194
195 // at creation no content is selected
196 setCurrentState(new PieChartViewerStateNoContentSelected(this));
197 }
198
199 /**
200 * Updates the data contained in the Global PieChart by using a Map.
201 * Normally, this method is only called by the state machine.
202 */
203 synchronized void updateGlobalPieChart() {
204 if (getGlobalPC() == null) {
205 fGlobalPC = new PieChart(this, SWT.NONE);
206 getGlobalPC().getTitle().setText(fGlobalPCname);
207 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
208 getGlobalPC().getLegend().setVisible(true);
209 getGlobalPC().getLegend().setPosition(SWT.RIGHT);
210 getGlobalPC().addListener(SWT.MouseMove, fMouseMoveListener);
211 getGlobalPC().addMouseListener(fMouseClickListener);
212 } else if (getGlobalPC().isDisposed() || fModel == null || fModel.getPieChartGlobalModel() == null) {
213 return;
214 }
215
216 Map<String, Long> totalEventCountForChart = getTotalEventCountForChart(true);
217
218 if (totalEventCountForChart == null) {
219 return;
220 }
221
222 updatePieChartWithData(fGlobalPC, totalEventCountForChart, MIN_PRECENTAGE_TO_SHOW_SLICE, fOthersSliceName);
223 }
224
225 /**
226 * Updates the data contained in the Time-Range PieChart by using a Map.
227 * Normally, this method is only called by the state machine.
228 */
229 synchronized void updateTimeRangeSelectionPieChart() {
230 if (getTimeRangePC() == null) {
231 fTimeRangePC = new PieChart(this, SWT.NONE);
232 getTimeRangePC().getTitle().setText(fTimeRangePCname);
233 getTimeRangePC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
234 getTimeRangePC().getLegend().setPosition(SWT.BOTTOM);
235 getTimeRangePC().getLegend().setVisible(true);
236 getTimeRangePC().addListener(SWT.MouseMove, fMouseMoveListener);
237 getTimeRangePC().addMouseListener(fMouseClickListener);
238 }
239 else if (getTimeRangePC().isDisposed()) {
240 return;
241 }
242
243 Map<String, Long> totalEventCountForChart = getTotalEventCountForChart(false);
244
245 if (totalEventCountForChart == null) {
246 return;
247 }
248
249 updatePieChartWithData(fTimeRangePC, totalEventCountForChart, MIN_PRECENTAGE_TO_SHOW_SLICE, fOthersSliceName);
250 }
251
252 /* return the chart-friendly map given by the TmfPieChartStatisticsModel */
253 private Map<String, Long> getTotalEventCountForChart(boolean isGlobal) {
254 if (fModel == null) {
255 return null;
256 }
257 Map<ITmfTrace, Map<String, Long>> chartModel;
258 if (isGlobal) {
259 chartModel = fModel.getPieChartGlobalModel();
260 } else {
261 chartModel = fModel.getPieChartSelectionModel();
262 }
263 if (chartModel == null) {
264 return null;
265 }
266
267 Map<String, Long> totalEventCountForChart = new HashMap<>();
268 for (Entry<ITmfTrace, Map<String, Long>> entry : chartModel.entrySet()) {
269 Map<String, Long> traceEventCount = entry.getValue();
270 if (traceEventCount == null) {
271 continue;
272 }
273 for (Entry<String, Long> event : traceEventCount.entrySet()) {
274 final Long value = totalEventCountForChart.get(event.getKey());
275 if (value != null) {
276 totalEventCountForChart.put(event.getKey(), value + event.getValue());
277 } else {
278 totalEventCountForChart.put(event.getKey(), event.getValue());
279 }
280 }
281 }
282
283 return totalEventCountForChart;
284 }
285
286 /**
287 * Reinitializes the charts to their initial state, without any data
288 */
289 public synchronized void reinitializeCharts() {
290 if (isDisposed()) {
291 return;
292 }
293
294 if (getGlobalPC() != null && !getGlobalPC().isDisposed()) {
295 getGlobalPC().dispose();
296 }
297 fGlobalPC = new PieChart(this, SWT.NONE);
298 getGlobalPC().getTitle().setText(fGlobalPCname);
299 getGlobalPC().getAxisSet().getXAxis(0).getTitle().setText(""); //Hide the title over the legend //$NON-NLS-1$
300 if (getTimeRangePC() != null && !getTimeRangePC().isDisposed()) {
301 getTimeRangePC().dispose();
302 fTimeRangePC = null;
303 }
304 layout();
305 setCurrentState(new PieChartViewerStateNoContentSelected(this));
306 }
307
308 /**
309 * Function used to update or create the slices of a PieChart to match the
310 * content of a Map passed in parameter. It also provides a facade to use
311 * the PieChart API
312 */
313 private static void updatePieChartWithData(
314 final PieChart chart,
315 final Map<String, Long> slices,
316 final float minimumSizeOfSlice,
317 final String nameOfOthers) {
318
319 List<EventOccurrenceObject> chartValues = new ArrayList<>();
320 Long eventTotal = 0L;
321 for (Entry<String, Long> entry : slices.entrySet()) {
322 eventTotal += entry.getValue();
323 chartValues.add(new EventOccurrenceObject(entry.getKey(), entry.getValue()));
324 }
325
326 // No events in the selection
327 if (eventTotal == 0) {
328 // clear the chart and show "NO DATA"
329
330 return;
331 }
332
333 /*
334 * filter out the event types taking too little space in the chart and
335 * label the whole group together. The remaining slices will be showing
336 */
337 List<EventOccurrenceObject> filteredChartValues = new ArrayList<>();
338 Long othersEntryCount = 0L;
339 int nbSlices = 0;
340 for (EventOccurrenceObject entry : chartValues) {
341 if (entry.getNbOccurence() / eventTotal.floatValue() > minimumSizeOfSlice && nbSlices <= NB_MAX_SLICES) {
342 filteredChartValues.add(entry);
343 nbSlices++;
344 } else {
345 othersEntryCount += entry.getNbOccurence();
346 }
347 }
348
349 Collections.sort(filteredChartValues);
350
351 // Add the "Others" slice in the pie if its not empty
352 if (othersEntryCount != 0) {
353 filteredChartValues.add(new EventOccurrenceObject(nameOfOthers, othersEntryCount));
354 }
355
356 // put the entries in the chart and add their percentage
357 double[][] tempValues = new double[filteredChartValues.size()][1];
358 String[] tempNames = new String[filteredChartValues.size()];
359 int index = 0;
360 for (EventOccurrenceObject entry : filteredChartValues) {
361 tempValues[index][0] = entry.getNbOccurence();
362 tempNames[index] = entry.getName();
363 index++;
364 }
365
366 chart.addPieChartSeries(tempNames, tempValues);
367 }
368
369 /**
370 * Refresh this viewer
371 *
372 * @param refreshGlobal
373 * if we have to refresh the global piechart
374 * @param refreshSelection
375 * if we have to refresh the selection piechart
376 */
377 public synchronized void refresh(boolean refreshGlobal, boolean refreshSelection) {
378 if (fModel == null) {
379 reinitializeCharts();
380 } else {
381 if (refreshGlobal) {
382 /* will update the global pc */
383 getCurrentState().newGlobalEntries(this);
384 }
385
386 if (refreshSelection) {
387 // Check if the selection is empty
388 int nbEventsType = 0;
389 Map<String, Long> selectionModel = getTotalEventCountForChart(false);
390 for (Long l : selectionModel.values()) {
391 if (l != 0) {
392 nbEventsType++;
393 }
394 }
395
396 // Check if the selection is empty or if
397 // there is enough event types to show in the piecharts
398 if (nbEventsType < 2) {
399 getCurrentState().newEmptySelection(this);
400 } else {
401 getCurrentState().newSelection(this);
402 }
403 }
404 }
405 }
406
407 /**
408 * @param l
409 * the listener to add
410 */
411 public void addEventTypeSelectionListener(Listener l) {
412 fEventTypeSelectedListeners.add(l);
413 }
414
415 /**
416 * @param l
417 * the listener to remove
418 */
419 public void removeEventTypeSelectionListener(Listener l) {
420 fEventTypeSelectedListeners.remove(l);
421 }
422
423 /* Notify all listeners that an event type has been selected */
424 private void notifyEventTypeSelectionListener(Event e) {
425 for (Object o : fEventTypeSelectedListeners.getListeners()) {
426 ((Listener) o).handleEvent(e);
427 }
428 }
429
430 // ------------------------------------------------------------------------
431 // Getters
432 // ------------------------------------------------------------------------
433
434 /**
435 * @return the global piechart
436 */
437 synchronized PieChart getGlobalPC() {
438 return fGlobalPC;
439 }
440
441 /**
442 * @return the time-range selection piechart
443 */
444 synchronized PieChart getTimeRangePC() {
445 return fTimeRangePC;
446 }
447
448 /**
449 * @return the current state of the viewer
450 */
451 synchronized IPieChartViewerState getCurrentState() {
452 return fCurrentState;
453 }
454
455 // ------------------------------------------------------------------------
456 // Setters
457 // ------------------------------------------------------------------------
458
459 /**
460 * @return the model
461 */
462 public TmfPieChartStatisticsModel getModel() {
463 return fModel;
464 }
465
466 /**
467 * @param model
468 * the model to set
469 */
470 public void setInput(TmfPieChartStatisticsModel model) {
471 fModel = model;
472 }
473
474 /**
475 * Normally, this method is only called by the state machine
476 *
477 * @param newChart
478 * the new PieChart
479 */
480 public synchronized void setTimeRangePC(PieChart newChart) {
481 fTimeRangePC = newChart;
482 }
483
484 /**
485 * Setter method for the state.
486 *
487 * @param newState
488 * The new state of the viewer Normally only called by classes
489 * implementing the IPieChartViewerState interface.
490 */
491 public synchronized void setCurrentState(final IPieChartViewerState newState) {
492 fCurrentState = newState;
493 }
494
495 /**
496 * Nested class used to handle and sort more easily the pair (Name, Number
497 * of occurrences)
498 *
499 * @author Alexis Cabana-Loriaux
500 */
501 private static class EventOccurrenceObject implements Comparable<EventOccurrenceObject> {
502
503 private String fName;
504
505 private Long fNbOccurrences;
506
507 EventOccurrenceObject(String name, Long nbOccurences) {
508 this.fName = name;
509 this.fNbOccurrences = nbOccurences;
510 }
511
512 @Override
513 public int compareTo(EventOccurrenceObject other) {
514 // descending order
515 return Long.compare(other.getNbOccurence(), this.getNbOccurence());
516 }
517
518 public String getName() {
519 return fName;
520 }
521
522 public Long getNbOccurence() {
523 return fNbOccurrences;
524 }
525 }
526 }
This page took 0.060951 seconds and 5 git commands to generate.