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

Last change on this file since 14425 was 14425, checked in by GerdP, 6 years ago

see #7561 comment 24
slightly modified 7561_storeHeight.patch,

1a) When JOSM is closed or all layers are removed so that the "splash screen" is showed, the current height of each dialog is stored in the preferences file. The values are stored in preferencePrefix+".lastHeight".
1b) When JOSM is started or a first layer is opened, the previously stored values are restored.
2) The hard coded preferredHeight values are now also stored in preferencePrefix+".preferredHeight". The expert can change that value to a higher or lower value. When the sizes are refreshed because a dialog is added, removed or minimized the modified value is taken into account. With the current implementation the change is only taken into account when JOSM is stopped/started.

  • 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 */
132 RESTORE_SAVED,
133 /* INVISIBLE_TO_COLLAPSED, does not happen */
134 /**
135 * else. (Remaining elements have more space.)
136 */
137 ELEMENT_SHRINKS
138 }
139
140 /**
141 * Reconstruct the view, if the configurations of dialogs has changed.
142 * @param action what happened, so the reconstruction is necessary
143 * @param triggeredBy the dialog that caused the reconstruction
144 */
145 public void reconstruct(Action action, ToggleDialog triggeredBy) {
146
147 final int n = allDialogs.size();
148
149 /**
150 * reset the panels
151 */
152 for (JPanel p: panels) {
153 p.removeAll();
154 p.setVisible(false);
155 }
156
157 /**
158 * Add the elements to their respective panel.
159 *
160 * Each panel contains one dialog in default view and zero or more
161 * collapsed dialogs on top of it. The last panel is an exception
162 * as it can have collapsed dialogs at the bottom as well.
163 * If there are no dialogs in default view, show the collapsed ones
164 * in the last panel anyway.
165 */
166 JPanel p = panels.get(n-1); // current Panel (start with last one)
167 int k = -1; // indicates that current Panel index is N-1, but no default-view-Dialog has been added to this Panel yet.
168 for (int i = n-1; i >= 0; --i) {
169 final ToggleDialog dlg = allDialogs.get(i);
170 if (dlg.isDialogInDefaultView()) {
171 if (k == -1) {
172 k = n-1;
173 } else {
174 --k;
175 p = panels.get(k);
176 }
177 p.add(dlg, 0);
178 p.setVisible(true);
179 } else if (dlg.isDialogInCollapsedView()) {
180 p.add(dlg, 0);
181 p.setVisible(true);
182 }
183 }
184
185 if (k == -1) {
186 k = n-1;
187 }
188 final int numPanels = n - k;
189
190 /**
191 * Determine the panel geometry
192 */
193 if (action == Action.RESTORE_SAVED) {
194 for (int i = 0; i < n; ++i) {
195 final ToggleDialog dlg = allDialogs.get(i);
196 if (dlg.isDialogInDefaultView()) {
197 final int ph = dlg.getLastHeight();
198 final int ah = dlg.getSize().height;
199 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, ah < 20 ? ph : ah));
200 }
201 }
202
203 } else if (action == Action.ELEMENT_SHRINKS) {
204 for (int i = 0; i < n; ++i) {
205 final ToggleDialog dlg = allDialogs.get(i);
206 if (dlg.isDialogInDefaultView()) {
207 final int ph = dlg.getPreferredHeight();
208 final int ah = dlg.getSize().height;
209 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, ah < 20 ? ph : ah));
210 }
211 }
212 } else {
213 CheckParameterUtil.ensureParameterNotNull(triggeredBy, "triggeredBy");
214
215 int sumP = 0; // sum of preferred heights of dialogs in default view (without the triggering dialog)
216 int sumA = 0; // sum of actual heights of dialogs in default view (without the triggering dialog)
217 int sumC = 0; // sum of heights of all collapsed dialogs (triggering dialog is never collapsed)
218
219 for (ToggleDialog dlg: allDialogs) {
220 if (dlg.isDialogInDefaultView()) {
221 if (dlg != triggeredBy) {
222 sumP += dlg.getPreferredHeight();
223 sumA += dlg.getHeight();
224 }
225 } else if (dlg.isDialogInCollapsedView()) {
226 sumC += dlg.getHeight();
227 }
228 }
229
230 /**
231 * If we add additional dialogs on startup (e.g. geoimage), they may
232 * not have an actual height yet.
233 * In this case we simply reset everything to it's preferred size.
234 */
235 if (sumA == 0) {
236 reconstruct(Action.ELEMENT_SHRINKS, null);
237 return;
238 }
239
240 /** total Height */
241 final int h = mSpltPane.getMultiSplitLayout().getModel().getBounds().getSize().height;
242
243 /** space, that is available for dialogs in default view (after the reconfiguration) */
244 final int s2 = h - (numPanels - 1) * DIVIDER_SIZE - sumC;
245
246 final int hpTrig = triggeredBy.getPreferredHeight();
247 if (hpTrig <= 0) throw new IllegalStateException(); // Must be positive
248
249 /** The new dialog gets a fair share */
250 final int hnTrig = hpTrig * s2 / (hpTrig + sumP);
251 triggeredBy.setPreferredSize(new Dimension(Integer.MAX_VALUE, hnTrig));
252
253 /** This is remaining for the other default view dialogs */
254 final int r = s2 - hnTrig;
255
256 /**
257 * Take space only from dialogs that are relatively large
258 */
259 int dm = 0; // additional space needed by the small dialogs
260 int dp = 0; // available space from the large dialogs
261 for (int i = 0; i < n; ++i) {
262 final ToggleDialog dlg = allDialogs.get(i);
263 if (dlg != triggeredBy && dlg.isDialogInDefaultView()) {
264 final int ha = dlg.getSize().height; // current
265 final int h0 = ha * r / sumA; // proportional shrinking
266 final int he = dlg.getPreferredHeight() * s2 / (sumP + hpTrig); // fair share
267 if (h0 < he) { // dialog is relatively small
268 int hn = Math.min(ha, he); // shrink less, but do not grow
269 dm += hn - h0;
270 } else { // dialog is relatively large
271 dp += h0 - he;
272 }
273 }
274 }
275 /** adjust, without changing the sum */
276 for (int i = 0; i < n; ++i) {
277 final ToggleDialog dlg = allDialogs.get(i);
278 if (dlg != triggeredBy && dlg.isDialogInDefaultView()) {
279 final int ha = dlg.getHeight();
280 final int h0 = ha * r / sumA;
281 final int he = dlg.getPreferredHeight() * s2 / (sumP + hpTrig);
282 if (h0 < he) {
283 int hn = Math.min(ha, he);
284 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn));
285 } else {
286 int d = dp == 0 ? 0 : ((h0-he) * dm / dp);
287 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, h0 - d));
288 }
289 }
290 }
291 }
292
293 /**
294 * create Layout
295 */
296 final List<Node> ch = new ArrayList<>();
297
298 for (int i = k; i <= n-1; ++i) {
299 if (i != k) {
300 ch.add(new Divider());
301 }
302 Leaf l = new Leaf('L'+Integer.toString(i));
303 l.setWeight(1.0 / numPanels);
304 ch.add(l);
305 }
306
307 if (numPanels == 1) {
308 Node model = ch.get(0);
309 mSpltPane.getMultiSplitLayout().setModel(model);
310 } else {
311 Split model = new Split();
312 model.setRowLayout(false);
313 model.setChildren(ch);
314 mSpltPane.getMultiSplitLayout().setModel(model);
315 }
316
317 mSpltPane.getMultiSplitLayout().setDividerSize(DIVIDER_SIZE);
318 mSpltPane.getMultiSplitLayout().setFloatingDividers(true);
319 mSpltPane.revalidate();
320
321 /**
322 * Hide the Panel, if there is nothing to show
323 */
324 if (numPanels == 1 && panels.get(n-1).getComponents().length == 0) {
325 parent.setDividerSize(0);
326 this.setVisible(false);
327 } else {
328 if (this.getWidth() != 0) { // only if josm started with hidden panel
329 this.setPreferredSize(new Dimension(this.getWidth(), 0));
330 }
331 this.setVisible(true);
332 parent.setDividerSize(5);
333 parent.resetToPreferredSizes();
334 }
335 }
336
337 @Override
338 public void destroy() {
339 for (ToggleDialog t : allDialogs) {
340 try {
341 t.destroy();
342 } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
343 throw BugReport.intercept(e).put("dialog", t).put("dialog-class", t.getClass());
344 }
345 }
346 mSpltPane.removeAll();
347 allDialogs.clear();
348 panels.clear();
349 }
350
351 /**
352 * Replies the instance of a toggle dialog of type <code>type</code> managed by this
353 * map frame
354 *
355 * @param <T> toggle dialog type
356 * @param type the class of the toggle dialog, i.e. UserListDialog.class
357 * @return the instance of a toggle dialog of type <code>type</code> managed by this
358 * map frame; null, if no such dialog exists
359 *
360 */
361 public <T> T getToggleDialog(Class<T> type) {
362 for (ToggleDialog td : allDialogs) {
363 if (type.isInstance(td))
364 return type.cast(td);
365 }
366 return null;
367 }
368}
Note: See TracBrowser for help on using the repository browser.