source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/DialogsPanel.java

Last change on this file was 18871, checked in by taylor.smock, 9 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

  • Property svn:eol-style set to native
File size: 13.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import java.awt.Dimension;
5import java.util.ArrayList;
6import java.util.List;
7
8import javax.swing.BoxLayout;
9import javax.swing.JPanel;
10import javax.swing.JSplitPane;
11
12import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Divider;
13import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Leaf;
14import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Node;
15import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Split;
16import org.openstreetmap.josm.gui.widgets.MultiSplitPane;
17import org.openstreetmap.josm.tools.CheckParameterUtil;
18import org.openstreetmap.josm.tools.Destroyable;
19import org.openstreetmap.josm.tools.JosmRuntimeException;
20import org.openstreetmap.josm.tools.Utils;
21import org.openstreetmap.josm.tools.bugreport.BugReport;
22
23/**
24 * This is the panel displayed on the right side of JOSM. It displays a list of panels.
25 */
26public class DialogsPanel extends JPanel implements Destroyable {
27 private final List<ToggleDialog> allDialogs = new ArrayList<>();
28 private final MultiSplitPane mSpltPane = new MultiSplitPane();
29 private static final int DIVIDER_SIZE = 5;
30
31 /**
32 * Panels that are added to the multisplitpane.
33 */
34 private final List<JPanel> panels = new ArrayList<>();
35
36 /**
37 * If {@link #initialize(List)} was called. read only from outside
38 */
39 public boolean initialized;
40
41 private final JSplitPane myParent;
42
43 /**
44 * Creates a new {@link DialogsPanel}.
45 * @param parent The parent split pane that allows this panel to change it's size.
46 */
47 public DialogsPanel(JSplitPane parent) {
48 this.myParent = parent;
49 }
50
51 /**
52 * Initializes this panel
53 * @param pAllDialogs The list of dialogs this panel should contain on start.
54 */
55 public void initialize(List<ToggleDialog> pAllDialogs) {
56 if (initialized) {
57 throw new IllegalStateException("Panel can only be initialized once.");
58 }
59 initialized = true;
60 allDialogs.clear();
61
62 for (ToggleDialog dialog: pAllDialogs) {
63 add(dialog, false);
64 }
65
66 this.add(mSpltPane);
67 reconstruct(Action.RESTORE_SAVED, null);
68 }
69
70 /**
71 * Add a new {@link ToggleDialog} to the list of known dialogs and trigger reconstruct.
72 * @param dlg The dialog to add
73 */
74 public void add(ToggleDialog dlg) {
75 add(dlg, true);
76 }
77
78 /**
79 * Add a new {@link ToggleDialog} to the list of known dialogs.
80 * @param dlg The dialog to add
81 * @param doReconstruct <code>true</code> if reconstruction should be triggered.
82 */
83 public void add(ToggleDialog dlg, boolean doReconstruct) {
84 allDialogs.add(dlg);
85 dlg.setDialogsPanel(this);
86 dlg.setVisible(false);
87 final JPanel p = new MinSizePanel();
88 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
89 p.setVisible(false);
90
91 int dialogIndex = allDialogs.size() - 1;
92 mSpltPane.add(p, 'L'+Integer.toString(dialogIndex));
93 panels.add(p);
94
95 if (dlg.isDialogShowing()) {
96 dlg.showDialog();
97 if (dlg.isDialogInCollapsedView()) {
98 dlg.isCollapsed = false; // pretend to be in Default view, this will be set back by collapse()
99 dlg.collapse();
100 }
101 if (doReconstruct) {
102 reconstruct(Action.INVISIBLE_TO_DEFAULT, dlg);
103 }
104 dlg.showNotify();
105 } else {
106 dlg.hideDialog();
107 }
108 }
109
110 /**
111 * Remove a {@link ToggleDialog} from the list of known dialogs and trigger reconstruct.
112 * @param toggleDialog The dialog to remove
113 * @since 18686
114 */
115 public void remove(ToggleDialog toggleDialog) {
116 remove(toggleDialog, true);
117 }
118
119 /**
120 * Remove a {@link ToggleDialog} from the list of known dialogs.
121 * @param toggleDialog The dialog to remove
122 * @param doReconstruct <code>true</code> if reconstruction should be triggered.
123 * @since 18686
124 */
125 public void remove(ToggleDialog toggleDialog, boolean doReconstruct) {
126 toggleDialog.setDialogsPanel(null);
127 final JPanel oldPanel = panels.get(allDialogs.indexOf(toggleDialog));
128 allDialogs.remove(toggleDialog);
129 panels.remove(oldPanel);
130 mSpltPane.remove(oldPanel);
131 if (doReconstruct && !allDialogs.isEmpty()) {
132 reconstruct(Action.ELEMENT_SHRINKS, toggleDialog);
133 }
134 }
135
136 static final class MinSizePanel extends JPanel {
137 @Override
138 public Dimension getMinimumSize() {
139 // Honoured by the MultiSplitPaneLayout when the entire Window is resized
140 return new Dimension(0, 40);
141 }
142 }
143
144 /**
145 * What action was performed to trigger the reconstruction
146 */
147 public enum Action {
148 /**
149 * The panel was invisible previously
150 */
151 INVISIBLE_TO_DEFAULT,
152 /**
153 * The panel was collapsed by the user.
154 */
155 COLLAPSED_TO_DEFAULT,
156 /**
157 * Restore saved heights.
158 * @since 14425
159 */
160 RESTORE_SAVED,
161 /* INVISIBLE_TO_COLLAPSED, does not happen */
162 /**
163 * else. (Remaining elements have more space.)
164 */
165 ELEMENT_SHRINKS
166 }
167
168 /**
169 * Reconstruct the view, if the configurations of dialogs has changed.
170 * @param action what happened, so the reconstruction is necessary
171 * @param triggeredBy the dialog that caused the reconstruction
172 */
173 public void reconstruct(Action action, ToggleDialog triggeredBy) {
174
175 final int n = allDialogs.size();
176
177 /*
178 * reset the panels
179 */
180 for (JPanel p: panels) {
181 p.removeAll();
182 p.setVisible(false);
183 }
184
185 /*
186 * Add the elements to their respective panel.
187 *
188 * Each panel contains one dialog in default view and zero or more
189 * collapsed dialogs on top of it. The last panel is an exception
190 * as it can have collapsed dialogs at the bottom as well.
191 * If there are no dialogs in default view, show the collapsed ones
192 * in the last panel anyway.
193 */
194 JPanel p = panels.get(n-1); // current Panel (start with last one)
195 int k = -1; // indicates that current Panel index is N-1, but no default-view-Dialog has been added to this Panel yet.
196 for (int i = n-1; i >= 0; --i) {
197 final ToggleDialog dlg = allDialogs.get(i);
198 if (dlg.isDialogInDefaultView()) {
199 if (k == -1) {
200 k = n-1;
201 } else {
202 --k;
203 p = panels.get(k);
204 }
205 p.add(dlg, 0);
206 p.setVisible(true);
207 } else if (dlg.isDialogInCollapsedView()) {
208 p.add(dlg, 0);
209 p.setVisible(true);
210 }
211 }
212
213 if (k == -1) {
214 k = n-1;
215 }
216 final int numPanels = n - k;
217
218 /*
219 * Determine the panel geometry
220 */
221 if (action == Action.RESTORE_SAVED || action == Action.ELEMENT_SHRINKS) {
222 for (final ToggleDialog dlg : allDialogs) {
223 if (dlg.isDialogInDefaultView()) {
224 final int ph = action == Action.RESTORE_SAVED ? dlg.getLastHeight() : dlg.getPreferredHeight();
225 final int ah = dlg.getSize().height;
226 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, ah < 20 ? ph : ah));
227 }
228 }
229 } else {
230 CheckParameterUtil.ensureParameterNotNull(triggeredBy, "triggeredBy");
231
232 int sumP = 0; // sum of preferred heights of dialogs in default view (without the triggering dialog)
233 int sumA = 0; // sum of actual heights of dialogs in default view (without the triggering dialog)
234 int sumC = 0; // sum of heights of all collapsed dialogs (triggering dialog is never collapsed)
235
236 for (ToggleDialog dlg: allDialogs) {
237 if (dlg.isDialogInDefaultView()) {
238 if (dlg != triggeredBy) {
239 sumP += dlg.getPreferredHeight();
240 sumA += dlg.getHeight();
241 }
242 } else if (dlg.isDialogInCollapsedView()) {
243 sumC += dlg.getHeight();
244 }
245 }
246
247 /*
248 * If we add additional dialogs on startup (e.g. geoimage), they may
249 * not have an actual height yet.
250 * In this case we simply reset everything to it's preferred size.
251 */
252 if (sumA == 0) {
253 reconstruct(Action.ELEMENT_SHRINKS, null);
254 return;
255 }
256
257 /* total Height */
258 final int h = mSpltPane.getMultiSplitLayout().getModel().getBounds().getSize().height;
259
260 /* space, that is available for dialogs in default view (after the reconfiguration) */
261 final int s2 = h - (numPanels - 1) * DIVIDER_SIZE - sumC;
262
263 final int hpTrig = triggeredBy.getPreferredHeight();
264 if (hpTrig <= 0) throw new IllegalStateException(); // Must be positive
265
266 /* The new dialog gets a fair share */
267 final int hnTrig = hpTrig * s2 / (hpTrig + sumP);
268 triggeredBy.setPreferredSize(new Dimension(Integer.MAX_VALUE, hnTrig));
269
270 /* This is remaining for the other default view dialogs */
271 final int r = s2 - hnTrig;
272
273 /*
274 * Take space only from dialogs that are relatively large
275 */
276 int dm = 0; // additional space needed by the small dialogs
277 int dp = 0; // available space from the large dialogs
278 for (final ToggleDialog dlg : allDialogs) {
279 if (dlg != triggeredBy && dlg.isDialogInDefaultView()) {
280 final int ha = dlg.getSize().height; // current
281 final int h0 = ha * r / sumA; // proportional shrinking
282 final int he = dlg.getPreferredHeight() * s2 / (sumP + hpTrig); // fair share
283 if (h0 < he) { // dialog is relatively small
284 int hn = Math.min(ha, he); // shrink less, but do not grow
285 dm += hn - h0;
286 } else { // dialog is relatively large
287 dp += h0 - he;
288 }
289 }
290 }
291 /* adjust, without changing the sum */
292 for (final ToggleDialog dlg : allDialogs) {
293 if (dlg != triggeredBy && dlg.isDialogInDefaultView()) {
294 final int ha = dlg.getHeight();
295 final int h0 = ha * r / sumA;
296 final int he = dlg.getPreferredHeight() * s2 / (sumP + hpTrig);
297 if (h0 < he) {
298 int hn = Math.min(ha, he);
299 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn));
300 } else {
301 int d = dp == 0 ? 0 : ((h0 - he) * dm / dp);
302 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, h0 - d));
303 }
304 }
305 }
306 }
307
308 /*
309 * create Layout
310 */
311 final List<Node> ch = new ArrayList<>();
312
313 for (int i = k; i <= n-1; ++i) {
314 if (i != k) {
315 ch.add(new Divider());
316 }
317 Leaf l = new Leaf('L'+Integer.toString(i));
318 l.setWeight(1.0 / numPanels);
319 ch.add(l);
320 }
321
322 if (numPanels == 1) {
323 Node model = ch.get(0);
324 mSpltPane.getMultiSplitLayout().setModel(model);
325 } else {
326 Split model = new Split();
327 model.setRowLayout(false);
328 model.setChildren(ch);
329 mSpltPane.getMultiSplitLayout().setModel(model);
330 }
331
332 mSpltPane.getMultiSplitLayout().setDividerSize(DIVIDER_SIZE);
333 mSpltPane.getMultiSplitLayout().setFloatingDividers(true);
334 mSpltPane.revalidate();
335
336 /*
337 * Hide the Panel, if there is nothing to show
338 */
339 if (numPanels == 1 && panels.get(n-1).getComponents().length == 0) {
340 myParent.setDividerSize(0);
341 this.setVisible(false);
342 } else {
343 if (this.getWidth() != 0) { // only if josm started with hidden panel
344 this.setPreferredSize(new Dimension(this.getWidth(), 0));
345 }
346 this.setVisible(true);
347 myParent.setDividerSize(5);
348 myParent.resetToPreferredSizes();
349 }
350 }
351
352 @Override
353 public void destroy() {
354 for (ToggleDialog t : new ArrayList<>(allDialogs)) {
355 try {
356 t.destroy();
357 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
358 throw BugReport.intercept(e).put("dialog", t).put("dialog-class", t.getClass());
359 }
360 }
361 mSpltPane.removeAll();
362 allDialogs.clear();
363 panels.clear();
364 }
365
366 /**
367 * Replies the instance of a toggle dialog of type <code>type</code> managed by this
368 * map frame
369 *
370 * @param <T> toggle dialog type
371 * @param type the class of the toggle dialog, i.e. UserListDialog.class
372 * @return the instance of a toggle dialog of type <code>type</code> managed by this
373 * map frame; null, if no such dialog exists
374 *
375 */
376 public <T extends ToggleDialog> T getToggleDialog(Class<T> type) {
377 return Utils.filteredCollection(allDialogs, type).stream()
378 .findFirst().orElse(null);
379 }
380}
Note: See TracBrowser for help on using the repository browser.