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

Last change on this file since 2749 was 2749, checked in by jttt, 15 years ago

Workaround division by zero exception when geoimage layer is added as first layer

  • Property svn:eol-style set to native
File size: 11.3 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.dialogs;
4
5import java.awt.Dimension;
6import java.util.ArrayList;
7import java.util.List;
8
9import javax.swing.BoxLayout;
10import javax.swing.JPanel;
11import javax.swing.JSplitPane;
12
13import org.openstreetmap.josm.gui.MultiSplitPane;
14import org.openstreetmap.josm.gui.MultiSplitLayout.Divider;
15import org.openstreetmap.josm.gui.MultiSplitLayout.Leaf;
16import org.openstreetmap.josm.gui.MultiSplitLayout.Node;
17import org.openstreetmap.josm.gui.MultiSplitLayout.Split;
18
19public class DialogsPanel extends JPanel {
20 protected List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
21 protected MultiSplitPane mSpltPane = new MultiSplitPane();
22 final protected int DIVIDER_SIZE = 5;
23
24 /**
25 * Panels that are added to the multisplitpane.
26 */
27 private List<JPanel> panels = new ArrayList<JPanel>();
28
29 final private JSplitPane parent;
30 public DialogsPanel(JSplitPane parent) {
31 this.parent = parent;
32 }
33
34 public boolean initialized = false; // read only from outside
35
36 public void initialize(List<ToggleDialog> pAllDialogs) {
37 if (initialized)
38 throw new IllegalStateException();
39 initialized = true;
40 allDialogs = new ArrayList<ToggleDialog>();
41
42 for (ToggleDialog dialog: pAllDialogs) {
43 add(dialog, false);
44 }
45
46 this.add(mSpltPane);
47 reconstruct(Action.ELEMENT_SHRINKS, null);
48 }
49
50 /**
51 * Invoke before the panel is discarded. This will in turn call {@see ToggleDialog#tearDown()}
52 * on every dialog.
53 *
54 */
55 public void tearDown() {
56 for(ToggleDialog dialog: allDialogs) {
57 dialog.tearDown();
58 }
59 }
60
61 public void add(ToggleDialog dlg) {
62 add(dlg, true);
63 }
64
65 public void add(ToggleDialog dlg, boolean doReconstruct) {
66 allDialogs.add(dlg);
67 int i = allDialogs.size() - 1;
68 dlg.setDialogsPanel(this);
69 dlg.setVisible(false);
70 final JPanel p = new JPanel() {
71 /**
72 * Honoured by the MultiSplitPaneLayout when the
73 * entire Window is resized.
74 */
75 @Override
76 public Dimension getMinimumSize() {
77 return new Dimension(0, 40);
78 }
79 };
80 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
81 p.setVisible(false);
82
83 mSpltPane.add(p, "L"+i);
84 panels.add(p);
85
86 if (dlg.isDialogShowing()) {
87 dlg.showDialog();
88 if (dlg.isDialogInCollapsedView()) {
89 dlg.isCollapsed = false; // pretend to be in Default view, this will be set back by collapse()
90 dlg.collapse();
91 }
92 if (doReconstruct) {
93 reconstruct(Action.INVISIBLE_TO_DEFAULT, dlg);
94 }
95 } else {
96 dlg.hideDialog();
97 }
98 }
99
100 /**
101 * What action was performed to trigger the reconstruction
102 */
103 public enum Action {
104 INVISIBLE_TO_DEFAULT,
105 COLLAPSED_TO_DEFAULT,
106 /* INVISIBLE_TO_COLLAPSED, does not happen */
107 ELEMENT_SHRINKS /* else. (Remaining elements have more space.) */
108 }
109 /**
110 * Reconstruct the view, if the configurations of dialogs has changed.
111 * @param action what happened, so the reconstruction is necessary
112 * @param triggeredBy the dialog that caused the reconstruction
113 */
114 public void reconstruct(Action action, ToggleDialog triggeredBy) {
115
116 final int N = allDialogs.size();
117
118 /**
119 * reset the panels
120 */
121 for (JPanel p: panels) {
122 p.removeAll();
123 p.setVisible(false);
124 }
125
126 /**
127 * Add the elements to their respective panel.
128 *
129 * Each panel contains one dialog in default view and zero or more
130 * collapsed dialogs on top of it. The last panel is an exception
131 * as it can have collapsed dialogs at the bottom as well.
132 * If there are no dialogs in default view, show the collapsed ones
133 * in the last panel anyway.
134 */
135 int k = N-1; // index of the current Panel (start with last one)
136 JPanel p = panels.get(k); // current Panel
137 k = -1; // indicates that the current Panel index is N-1, but no default-view-Dialog was added to this Panel yet.
138 for (int i=N-1; i >= 0 ; --i) {
139 final ToggleDialog dlg = allDialogs.get(i);
140 if (dlg.isDialogInDefaultView()) {
141 if (k == -1) {
142 k = N-1;
143 } else {
144 --k;
145 p = panels.get(k);
146 }
147 p.add(dlg, 0);
148 p.setVisible(true);
149 }
150 else if (dlg.isDialogInCollapsedView()) {
151 p.add(dlg, 0);
152 p.setVisible(true);
153 }
154 }
155
156 if (k == -1) {
157 k = N-1;
158 }
159 final int numPanels = N - k;
160
161 /**
162 * Determine the panel geometry
163 */
164 if (action == Action.ELEMENT_SHRINKS) {
165 for (int i=0; i<N; ++i) {
166 final ToggleDialog dlg = allDialogs.get(i);
167 if (dlg.isDialogInDefaultView()) {
168 final int ph = dlg.getPreferredHeight();
169 final int ah = dlg.getSize().height;
170 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, (ah < 20 ? ph : ah)));
171 }
172 }
173 } else {
174 if (triggeredBy == null)
175 throw new IllegalArgumentException();
176
177 int sumP = 0; // sum of preferred heights of dialogs in default view (without the triggering dialog)
178 int sumA = 0; // sum of actual heights of dialogs in default view (without the triggering dialog)
179 int sumC = 0; // sum of heights of all collapsed dialogs (triggering dialog is never collapsed)
180
181 for (ToggleDialog dlg: allDialogs) {
182 if (dlg.isDialogInDefaultView()) {
183 if (dlg != triggeredBy) {
184 final int ph = dlg.getPreferredHeight();
185 final int ah = dlg.getHeight();
186 sumP += ph;
187 sumA += ah;
188 }
189 } else if (dlg.isDialogInCollapsedView()) {
190 sumC += dlg.getHeight();
191 }
192 }
193
194 /** total Height */
195 final int H = mSpltPane.getMultiSplitLayout().getModel().getBounds().getSize().height;
196
197 /** space, that is available for dialogs in default view (after the reconfiguration) */
198 final int s2 = H - (numPanels - 1) * DIVIDER_SIZE - sumC;
199
200 final int hp_trig = triggeredBy.getPreferredHeight();
201 if (hp_trig <= 0) throw new IllegalStateException(); // Must be positive
202
203 /** The new dialog gets a fair share */
204 final int hn_trig = hp_trig * s2 / (hp_trig + sumP);
205 triggeredBy.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn_trig));
206
207 /** This is remainig for the other default view dialogs */
208 final int R = s2 - hn_trig;
209
210 /**
211 * Take space only from dialogs that are relatively large
212 */
213 int D_m = 0; // additional space needed by the small dialogs
214 int D_p = 0; // available space from the large dialogs
215 for (int i=0; i<N; ++i) {
216 final ToggleDialog dlg = allDialogs.get(i);
217 if (dlg.isDialogInDefaultView() && dlg != triggeredBy) {
218 final int ha = dlg.getSize().height; // current
219 final int h0 = ha * R / sumA; // proportional shrinking
220 final int he = dlg.getPreferredHeight() * s2 / (sumP + hp_trig); // fair share
221 if (h0 < he) { // dialog is relatively small
222 int hn = Math.min(ha, he); // shrink less, but do not grow
223 D_m += hn - h0;
224 } else { // dialog is relatively large
225 D_p += h0 - he;
226 }
227 }
228 }
229 /** adjust, without changing the sum */
230 for (int i=0; i<N; ++i) {
231 final ToggleDialog dlg = allDialogs.get(i);
232 if (dlg.isDialogInDefaultView() && dlg != triggeredBy) {
233 final int ha = dlg.getHeight();
234 final int h0 = ha * R / sumA;
235 final int he = dlg.getPreferredHeight() * s2 / (sumP + hp_trig);
236 if (h0 < he) {
237 int hn = Math.min(ha, he);
238 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, hn));
239 } else {
240 int d;
241 try {
242 d = (h0-he) * D_m / D_p;
243 } catch (ArithmeticException e) { /* D_p may be zero - nothing wrong with that. */
244 d = 0;
245 }
246 dlg.setPreferredSize(new Dimension(Integer.MAX_VALUE, h0 - d));
247 }
248 }
249 }
250 }
251
252 /**
253 * create Layout
254 */
255 final List<Node> ch = new ArrayList<Node>();
256
257 for (int i = k; i <= N-1; ++i) {
258 if (i != k) {
259 ch.add(new Divider());
260 }
261 Leaf l = new Leaf("L"+i);
262 l.setWeight(1.0 / numPanels);
263 ch.add(l);
264 }
265
266 if (numPanels == 1) {
267 Node model = ch.get(0);
268 mSpltPane.getMultiSplitLayout().setModel(model);
269 } else {
270 Split model = new Split();
271 model.setRowLayout(false);
272 model.setChildren(ch);
273 mSpltPane.getMultiSplitLayout().setModel(model);
274 }
275
276 mSpltPane.getMultiSplitLayout().setDividerSize(DIVIDER_SIZE);
277 mSpltPane.getMultiSplitLayout().setFloatingDividers(true);
278 mSpltPane.revalidate();
279
280 /**
281 * Hide the Panel, if there is nothing to show
282 */
283 if (numPanels == 1 && panels.get(N-1).getComponents().length == 0)
284 {
285 parent.setDividerSize(0);
286 this.setVisible(false);
287 } else {
288 if (this.getWidth() != 0) { // only if josm started with hidden panel
289 this.setPreferredSize(new Dimension(this.getWidth(), 0));
290 }
291 this.setVisible(true);
292 parent.setDividerSize(5);
293 parent.resetToPreferredSizes();
294 }
295 }
296
297 public void destroy() {
298 for (ToggleDialog t : allDialogs) {
299 t.destroy();
300 }
301 }
302
303 /**
304 * Replies the instance of a toggle dialog of type <code>type</code> managed by this
305 * map frame
306 *
307 * @param <T>
308 * @param type the class of the toggle dialog, i.e. UserListDialog.class
309 * @return the instance of a toggle dialog of type <code>type</code> managed by this
310 * map frame; null, if no such dialog exists
311 *
312 */
313 public <T> T getToggleDialog(Class<T> type) {
314 for (ToggleDialog td : allDialogs) {
315 if (type.isInstance(td))
316 return type.cast(td);
317 }
318 return null;
319 }
320}
Note: See TracBrowser for help on using the repository browser.