This repository has been archived by the owner on Mar 27, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 55
/
PlotGridComposite.java
767 lines (679 loc) · 24 KB
/
PlotGridComposite.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
package org.eclipse.ice.client.widgets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.ice.client.common.ActionTree;
import org.eclipse.ice.client.widgets.viz.service.IPlot;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.ToolBar;
/**
* A {@code PlotGridComposite} is designed to display a grid of drawn
* {@link IPlot}s. It includes widgets to customize the grid-based layout of the
* plots. The order of a plot in the grid is based on its add order, and the
* same plot can be added to the grid more than once.
*
* @author Jordan Deyton
*
*/
public class PlotGridComposite extends Composite {
/**
* The {@code ToolBar} that contains widgets to update the grid layout and
* clear the grid.
*/
private final ToolBar toolBar;
/**
* The grid of drawn plots.
*/
private final Composite gridComposite;
/**
* The user-customizable layout for {@link #gridComposite}, the grid of
* drawn plots.
*/
private final GridLayout gridLayout;
/**
* The number of rows to display in the grid.
*/
private int rows = 2;
/**
* The number of columns to display in the grid.
*/
private int columns = 2;
/**
* This is a button used to close the drawn plot over which the mouse is
* currently hovering. It should only be visible in the top-right corner of
* a drawn plot when the mouse cursor is over the plot.
*/
private Button closeButton;
/**
* The listener responsible for showing and hiding the {@link #closeButton}.
*/
private final Listener plotHoverListener;
/**
* The list of currently drawn plots. This list is used to maintain the
* insertion order. A nested class, {@link DrawnPlot}, is used to contain
* meta-data about each drawn plot or cell in the grid.
*/
private final List<DrawnPlot> drawnPlots;
/**
* A map containing the currently drawn plots and their indices in
* {@link #drawnPlots}. This is used for relatively fast removal based on a
* reference to a drawn plot.
*/
private final Map<DrawnPlot, Integer> drawnPlotMap;
/**
* A dispose listener that will remove the disposed {@link DrawnPlot}
* completely.
*/
private final DisposeListener plotDisposeListener = new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
removePlot((DrawnPlot) e.widget, true);
}
};
/**
* The default constructor. Creates a {@code Composite} designed to display
* a grid of {@code Composites} populated by {@code IPlot} implementations.
*
* @param parent
* A widget that will be the parent of the new instance (cannot
* be null).
* @param style
* The style of widget to construct.
*/
public PlotGridComposite(Composite parent, int style) {
super(parent, style);
// Set the background to be the same as the parent's.
setBackground(parent.getBackground());
// Initialize the list of drawn plots.
drawnPlots = new ArrayList<DrawnPlot>();
drawnPlotMap = new HashMap<DrawnPlot, Integer>();
// Set up the ToolBar.
toolBar = createToolBar(this);
// Set up the Composite containing the grid of plots.
gridComposite = new Composite(this, SWT.NONE);
gridComposite.setBackground(getBackground());
gridLayout = new GridLayout();
gridLayout.makeColumnsEqualWidth = true;
gridComposite.setLayout(gridLayout);
// Lay out this Composite. The ToolBar should be across the top, while
// the Composite containing the plot grid should grab all available
// space.
GridData gridData;
setLayout(new GridLayout());
gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
toolBar.setLayoutData(gridData);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridComposite.setLayoutData(gridData);
// Create the listener that creates the close button when the mouse
// enters a plot Composite (in the grid).
plotHoverListener = new Listener() {
/**
* The most recent plot that was entered, or null.
*/
private DrawnPlot lastPlot;
@Override
public void handleEvent(Event event) {
// We only deal with Composites here.
if (event.widget instanceof Composite) {
// Determine the plot Composite that the mouse entered.
Composite child = (Composite) event.widget;
DrawnPlot plot = findDrawnPlot(child);
// If the mouse entered a new plot Composite (or exited
// one), then a change occurred and requires an update to
// the close button.
if (plot != lastPlot) {
// If necessary, dispose the old close button.
if (closeButton != null) {
closeButton.dispose();
}
// If a plot Composite (or one of its children) was
// entered, create a new close button in it.
if (plot != null) {
closeButton = plot.createCloseButton();
// When the button is disposed, its reference should
// be cleared.
closeButton
.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(
DisposeEvent e) {
closeButton = null;
}
});
}
// Set the reference to the most recently entered plot.
lastPlot = plot;
}
}
return;
}
};
// Add the listener as a filter so that it will be notified of *all*
// SWT.MouseEnter events. This listener *must* be removed when this plot
// grid is disposed.
getDisplay().addFilter(SWT.MouseEnter, plotHoverListener);
return;
}
/**
* Creates a {@code ToolBar} for this {@code Composite}. It includes the
* following controls:
* <ol>
* <li>Grid rows and columns</li>
* <li>A button to clear plots from the grid</li>
* </ol>
*
* @param parent
* The parent {@code Composite} in which to draw the
* {@code ToolBar}.
* @return The newly created {@code ToolBar}.
*/
private ToolBar createToolBar(Composite parent) {
// Create and adapt the ToolBar first so that the default styles will be
// passed down to the widgets created by the ToolBarManager.
ToolBar toolBar = new ToolBar(parent, SWT.WRAP | SWT.FLAT
| SWT.HORIZONTAL);
toolBar.setBackground(parent.getBackground());
ToolBarManager toolBarManager = new ToolBarManager(toolBar);
// Add a "Rows" label next to the row Spinner.
LabelContribution rowLabel = new LabelContribution("rows.label");
rowLabel.setText("Rows:");
toolBarManager.add(rowLabel);
// Add a Spinner for setting the grid rows to the ToolBarManager (this
// requires a JFace ControlContribution).
SpinnerContribution rowSpinner = new SpinnerContribution("rows.spinner");
rowSpinner.setMinimum(1);
rowSpinner.setMaximum(4);
rowSpinner.setSelection(rows);
rowSpinner.setIncrement(1);
rowSpinner.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
rows = ((Spinner) e.widget).getSelection();
refreshLayout();
}
});
toolBarManager.add(rowSpinner);
// Add a "Columns" label next to the row Spinner.
LabelContribution columnLabel = new LabelContribution("columns.label");
columnLabel.setText("Columns:");
toolBarManager.add(columnLabel);
// Add a Spinner for setting the grid columns to the ToolBarManager
// (this requires a JFace ControlContribution).
SpinnerContribution columnSpinner = new SpinnerContribution(
"columns.spinner");
columnSpinner.setMinimum(1);
columnSpinner.setMaximum(4);
columnSpinner.setSelection(columns);
columnSpinner.setIncrement(1);
columnSpinner.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
columns = ((Spinner) e.widget).getSelection();
refreshLayout();
}
});
toolBarManager.add(columnSpinner);
// Add a separator between the spinners and the clear button.
toolBarManager.add(new Separator());
// Add a ToolBar button to clear the plots.
toolBarManager.add(new Action("Clear") {
@Override
public void run() {
clearPlots();
}
});
// Apply the ToolBarManager changes to the ToolBar.
toolBarManager.update(true);
return toolBar;
}
/**
* Adds a plot to be drawn inside the plot grid. Note that the same plot can
* be added more than once.
*
* @param plot
* The plot to draw inside the grid.
* @return The index of the plot in the grid, or -1 if the plot could not be
* drawn.
* @throws Exception
* An exception is thrown if the {@link IPlot} implementation
* cannot be rendered.
*/
public int addPlot(IPlot plot) throws Exception {
int index = -1;
// Proceed if the plot is not null and there is still space available in
// the grid.
if (plot != null && drawnPlots.size() < rows * columns) {
// Try to get the available categories and plot types, then try to
// plot the first available one.
Map<String, String[]> plotTypes = plot.getPlotTypes();
// Find the first category and plot type.
String category = null;
String type = null;
for (Entry<String, String[]> entry : plotTypes.entrySet()) {
category = entry.getKey();
String[] types = entry.getValue();
if (category != null && types != null) {
for (int i = 0; i < types.length && type == null; i++) {
type = types[i];
}
if (type != null) {
break;
}
}
}
// If a category and type could be found, try to draw the plot in a
// new cell in the grid.
if (category != null && type != null) {
// Create the basic plot Composite.
final DrawnPlot drawnPlot = new DrawnPlot(gridComposite, plot);
// Try to draw the category and type. If the underlying IPlot
// cannot draw, then dispose of the undrawn plot Composite.
try {
drawnPlot.draw(category, type);
} catch (Exception e) {
drawnPlot.dispose();
throw e;
}
// Add the drawn plot to the list.
index = drawnPlots.size();
drawnPlots.add(drawnPlot);
drawnPlotMap.put(drawnPlot, index);
// When the drawn plot is disposed, make sure it is removed from
// the list of drawn plots.
drawnPlot.addDisposeListener(plotDisposeListener);
// Set the layout data for the new drawn plot. It should grab
// all available space in the gridComposite's layout.
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
drawnPlot.setLayoutData(gridData);
// Since a new plot was added, refresh the grid layout.
refreshLayout();
}
}
return index;
}
/**
* Removes the drawn plot at the specified index in the grid. If t a drawn
* plot was removed, then, at the end of this call, the grid layout will be
* refreshed.
*
* @param index
* The index of the drawn plot to remove. If invalid, nothing is
* done.
*/
public void removePlot(int index) {
// Check the index before passing on the removal request to the defacto
// remove method. We should refresh the grid layout.
if (index >= 0 && index < drawnPlots.size()) {
removePlot(drawnPlots.get(index), true);
}
return;
}
/**
* Removes the drawn plot completely, refreshing the layout if necessary.
* <b>This should be the defacto way to remove a drawn plot</b>. <i>If the
* code only has access to the drawn plot itself and not this method</i>,
* then the plot should be disposed directly.
*
* @param drawnPlot
* The drawn plot to remove (and, if necessary, dispose).
* @param refreshLayout
* Whether or not to refresh the layout after the plot is
* removed.
*/
private void removePlot(DrawnPlot drawnPlot, boolean refreshLayout) {
// The drawn plot is assumed to be valid.
// Remove it from the book keeping (both the list and the map).
int i = drawnPlotMap.remove(drawnPlot);
drawnPlots.remove(i);
for (; i < drawnPlots.size(); i++) {
drawnPlotMap.put(drawnPlots.get(i), i);
}
// If necessary, dispose it.
if (!drawnPlot.isDisposed()) {
// We don't want to trigger this method again, so remove the plot
// dispose listener first.
drawnPlot.removeDisposeListener(plotDisposeListener);
drawnPlot.dispose();
}
// If necessary, refresh the layout.
if (refreshLayout) {
refreshLayout();
}
return;
}
/**
* Removes all renderings of the specified plot from the grid. If a drawn
* plot was removed, then, at the end of this call, the grid layout will be
* refreshed.
*
* @param plot
* The plot whose renderings should be removed from the grid. If
* invalid or not rendered, nothing is done.
*/
public void removePlots(IPlot plot) {
// We can only process non-null plots.
if (plot != null) {
// Remove all drawn plots whose IPlot matches the specified plot.
boolean refreshLayout = false;
// We traverse backward in the list to reduce the length of
// traversals in the main remove method.
for (int i = drawnPlots.size() - 1; i >= 0; i--) {
DrawnPlot drawnPlot = drawnPlots.get(i);
if (drawnPlot.plot == plot) {
removePlot(drawnPlot, false);
refreshLayout = true;
}
}
// Only refresh the layout if at least one composite was disposed.
if (refreshLayout) {
refreshLayout();
}
}
return;
}
/**
* Removes all drawn plots from the grid. At the end of this call, the grid
* layout will be refreshed.
*/
public void clearPlots() {
// Remove all plots. We traverse backward in the list to reduce the
// length of traversals in the main remove method.
for (int i = drawnPlots.size() - 1; i >= 0; i--) {
removePlot(drawnPlots.get(i), false);
}
// We should refresh the layout.
refreshLayout();
return;
}
/**
* Finds the ancestor plot for the specified child {@code Composite}.
*
* @param child
* The child {@code Composite} from which to start the search.
* This could even be the plot {@code Composite} itself. Assumed
* not to be null.
* @return The main plot {@code Composite} that is an ancestor of the child,
* or {@code null} if one could not be found.
*/
private DrawnPlot findDrawnPlot(Composite child) {
// This loop breaks when all ancestors have been searched OR when the
// child plot Composite (whose parent is gridComposite) has been found.
Composite parent = child.getParent();
while (parent != gridComposite && parent != null) {
child = parent;
parent = child.getParent();
}
return (parent != null ? (DrawnPlot) child : null);
}
/**
* Refreshes the {@link #gridLayout} and drawn plot {@code GridData} based
* on the {@link #rows} and {@link #columns} and the number of drawn plots.
*/
private void refreshLayout() {
// Remove all excess drawn plots.
int limit = rows * columns;
for (int i = drawnPlots.size() - 1; i >= limit; i--) {
removePlot(drawnPlots.get(i), false);
}
// Reset all cells to only take up one grid cell.
GridData gridData;
for (DrawnPlot drawnPlot : drawnPlots) {
gridData = (GridData) drawnPlot.getLayoutData();
gridData.verticalSpan = 1;
}
int size = drawnPlots.size();
// Set the user-defined number of columns. The rows are handled already
// because we've removed all excess plots.
gridLayout.numColumns = (size > columns ? columns : size);
// If the last row has empty cells, then all of the cells directly above
// those empty cells should grab the excess vertical space by updating
// the verticalSpan property.
int lastRowSize = size % columns;
// We shouldn't do anything if the last row is full or if there is only
// one row.
if (lastRowSize > 0 && size > columns) {
int lastIndex = size - 1 - lastRowSize;
for (int i = 0; i < columns - lastRowSize; i++) {
DrawnPlot drawnPlot = drawnPlots.get(lastIndex - i);
gridData = (GridData) drawnPlot.getLayoutData();
gridData.verticalSpan = 2;
}
}
// Refresh the grid layout.
gridComposite.layout();
return;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose() {
// Remove the filter for the listener that creates the close button on
// certain SWT.MouseEnter events.
getDisplay().removeFilter(SWT.MouseEnter, plotHoverListener);
// Clear the plots. There is no need to refresh the layout.
for (int i = drawnPlots.size() - 1; i >= 0; i--) {
removePlot(drawnPlots.get(i), false);
}
// Proceed with the normal dispose operation.
super.dispose();
}
/**
* This nested class provides the {@code Composite} that immediately wraps a
* rendering from an {@code IPlot} implementation. It handles all
* plot-specific widgets and layouts for the associated {@link #plot}.
*
* @author Jordan Deyton
*
*/
private class DrawnPlot extends Composite {
/**
* The {@link IPlot} instance/implementation that is drawn.
*/
public final IPlot plot;
/**
* The default constructor. Creates a container for an {@code IPlot}
* instance with the {@code SWT.BORDER} style.
*
* @param parent
* The parent in which to draw the plot.
* @param plot
* The {@code IPlot} instance that will be drawn.
*/
public DrawnPlot(Composite parent, IPlot plot) {
this(parent, plot, SWT.BORDER);
}
/**
* The full constructor that allows a custom style to be set.
*
* @param parent
* The parent in which to draw the plot.
* @param plot
* The {@code IPlot} instance that will be drawn.
* @param style
* The style to use for this {@code Composite}.
*/
public DrawnPlot(Composite parent, IPlot plot, int style) {
super(parent, style);
this.plot = plot;
createContextMenu();
// The plot Composite should have a FormLayout so that the close
// button can be properly displayed in the top-right corner on
// top of all other controls. Otherwise, this is effectively a
// FillLayout.
setLayout(new FormLayout());
return;
}
/**
* Creates a context {@code Menu} for the drawn plot's main
* {@code Composite}. It includes the following controls by default:
* <ol>
* <li>Remove Plot</li>
* <li>Separator</li>
* <li>Set Plot Type</li>
* <li>Separator</li>
* <li>Any implementation-specific plot actions...</li>
* </ol>
* It is up to the related {@code IPlot} implementation to take the
* created {@code Menu} and add it to child {@code Composite}s or update
* it.
*
* @return The JFace {@code MenuManager} for the context {@code Menu}.
*/
private MenuManager createContextMenu() {
MenuManager contextMenuManager = new MenuManager();
// Create an action to remove the moused-over or clicked plot
// rendering.
final Action removeAction = new Action("Remove") {
@Override
public void run() {
dispose();
}
};
// Create the root ActionTree for setting the plot category and
// type.
final ActionTree plotTypeTree = new ActionTree("Set Plot Type");
try {
// Add an ActionTree for each category, and then add ActionTree
// leaf
// nodes for each type.
Map<String, String[]> plotTypes = plot.getPlotTypes();
for (Entry<String, String[]> entry : plotTypes.entrySet()) {
String category = entry.getKey();
String[] types = entry.getValue();
if (category != null && types != null && types.length > 0) {
// Create the category ActionTree.
ActionTree categoryTree = new ActionTree(category);
plotTypeTree.add(categoryTree);
// Add all types to the category ActionTree. Each Action
// should try to set the plot category and type of the
// drawn
// plot.
final String categoryRef = category;
for (String type : types) {
final String typeRef = type;
categoryTree.add(new ActionTree(new Action(type) {
@Override
public void run() {
try {
draw(categoryRef, typeRef);
} catch (Exception e) {
e.printStackTrace();
}
}
}));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
// Add the items to the context menu.
contextMenuManager.add(removeAction);
contextMenuManager.add(new Separator());
contextMenuManager.add(plotTypeTree.getContributionItem());
// Set the context Menu for the plot Composite.
setMenu(contextMenuManager.createContextMenu(this));
return contextMenuManager;
}
/**
* Attempts to draw the specified plot category and type with the
* underlying {@link IPlot} implementation.
*
* @param category
* The new plot category.
* @param type
* The new plot type.
* @throws Exception
* An exception is thrown if the underlying {@code IPlot}
* implementation fails to draw or update.
*/
public void draw(String category, String type) throws Exception {
plot.draw(category, type, this);
refreshLayout();
}
/**
* Refreshes the drawn plot's layout, adding a new {@code FormData} to
* any new child Composites.
*/
private void refreshLayout() {
boolean changed = false;
for (Control child : getChildren()) {
if (child.getLayoutData() == null) {
// Set up the child to fill the plot Composite.
FormData formData = new FormData();
formData.top = new FormAttachment(0, 0);
formData.bottom = new FormAttachment(100, 0);
formData.left = new FormAttachment(0, 0);
formData.right = new FormAttachment(100, 0);
child.setLayoutData(formData);
changed = true;
}
}
layout(changed);
return;
}
/**
* Creates a new {@link #closeButton} in the corner of the drawn plot.
* <p>
* To get rid of the close button, simply dispose it.
* {@link #closeButton} will automatically be set to {@code null}.
* </p>
*/
public Button createCloseButton() {
// Set up the close button.
Button closeButton = new Button(this, SWT.FLAT | SWT.CENTER);
closeButton.setText("X");
FontData[] smallFont = closeButton.getFont().getFontData();
for (FontData fd : smallFont) {
fd.setHeight(7);
}
closeButton.setFont(new Font(getDisplay(), smallFont));
closeButton.setToolTipText("Close plot");
closeButton.pack();
// Set the location of the button to the upper right-hand corner of
// the
// plot Composite, and above all other children of the plot
// Composite.
FormData formData = new FormData();
formData.top = new FormAttachment(0, 0);
formData.right = new FormAttachment(100, -4);
closeButton.setLayoutData(formData);
closeButton.moveAbove(null);
layout();
// Add a selection listener on it to close the drawn plot.
closeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
dispose();
}
});
return closeButton;
}
}
}