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

Last change on this file since 14426 was 14426, checked in by GerdP, 7 years ago

see #7561 comment 30 add javadoc @since to new public methods and new enum

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