source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java@ 3558

Last change on this file since 3558 was 3502, checked in by bastiK, 14 years ago

see #3278 (redirect shortcuts in the undocked windows to the main window) - also for relation dialog

  • Property svn:eol-style set to native
File size: 19.5 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.Graphics;
11import java.awt.GridBagLayout;
12import java.awt.GridLayout;
13import java.awt.Image;
14import java.awt.Rectangle;
15import java.awt.event.ActionEvent;
16import java.awt.event.ActionListener;
17import java.awt.event.ComponentAdapter;
18import java.awt.event.ComponentEvent;
19import java.awt.event.MouseAdapter;
20import java.awt.event.MouseEvent;
21import java.awt.event.WindowAdapter;
22import java.awt.event.WindowEvent;
23
24import javax.swing.AbstractAction;
25import javax.swing.BorderFactory;
26import javax.swing.ImageIcon;
27import javax.swing.JButton;
28import javax.swing.JComponent;
29import javax.swing.JDialog;
30import javax.swing.JLabel;
31import javax.swing.JOptionPane;
32import javax.swing.JPanel;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.actions.JosmAction;
36import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
37import org.openstreetmap.josm.gui.help.HelpUtil;
38import org.openstreetmap.josm.gui.help.Helpful;
39import org.openstreetmap.josm.gui.util.RedirectInputMap;
40import org.openstreetmap.josm.tools.GBC;
41import org.openstreetmap.josm.tools.ImageProvider;
42import org.openstreetmap.josm.tools.Shortcut;
43
44/**
45 * This class is a toggle dialog that can be turned on and off.
46 *
47 */
48public class ToggleDialog extends JPanel implements Helpful {
49
50 /** The action to toggle this dialog */
51 protected ToggleDialogAction toggleAction;
52 protected String preferencePrefix;
53 final protected String name;
54
55 /** DialogsPanel that manages all ToggleDialogs */
56 protected DialogsPanel dialogsPanel;
57
58 protected TitleBar titleBar;
59
60 /**
61 * Indicates whether the dialog is showing or not.
62 */
63 protected boolean isShowing;
64 /**
65 * If isShowing is true, indicates whether the dialog is docked or not, e. g.
66 * shown as part of the main window or as a separate dialog window.
67 */
68 protected boolean isDocked;
69 /**
70 * If isShowing and isDocked are true, indicates whether the dialog is
71 * currently minimized or not.
72 */
73 protected boolean isCollapsed;
74
75 /** the preferred height if the toggle dialog is expanded */
76 private int preferredHeight;
77
78 /** the label in the title bar which shows whether the toggle dialog is expanded or collapsed */
79 private JLabel lblMinimized;
80
81 /** the JDialog displaying the toggle dialog as undocked dialog */
82 protected JDialog detachedDialog;
83
84 /**
85 * Constructor
86 * (see below)
87 */
88 public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
89 this(name, iconName, tooltip, shortcut, preferredHeight, false);
90 }
91 /**
92 * Constructor
93 *
94 * @param name the name of the dialog
95 * @param iconName the name of the icon to be displayed
96 * @param tooltip the tool tip
97 * @param shortcut the shortcut
98 * @param preferredHeight the preferred height for the dialog
99 * @param defShow if the dialog should be shown by default, if there is no preference
100 */
101 public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow) {
102 super(new BorderLayout());
103 this.preferencePrefix = iconName;
104 this.name = name;
105
106 /** Use the full width of the parent element */
107 setPreferredSize(new Dimension(0, preferredHeight));
108 /** Override any minimum sizes of child elements so the user can resize freely */
109 setMinimumSize(new Dimension(0,0));
110 this.preferredHeight = preferredHeight;
111 toggleAction = new ToggleDialogAction(name, "dialogs/"+iconName, tooltip, shortcut, iconName);
112 String helpId = "Dialog/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
113 toggleAction.putValue("help", helpId.substring(0, helpId.length()-6));
114
115 setLayout(new BorderLayout());
116
117 /** show the minimize button */
118 lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
119 titleBar = new TitleBar(name, iconName);
120 add(titleBar, BorderLayout.NORTH);
121
122 setBorder(BorderFactory.createEtchedBorder());
123
124 isShowing = Main.pref.getBoolean(preferencePrefix+".visible", defShow);
125 isDocked = Main.pref.getBoolean(preferencePrefix+".docked", true);
126 isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
127
128 RedirectInputMap.redirectToMainContentPane(this);
129 }
130
131 /**
132 * The action to toggle the visibility state of this toggle dialog.
133 *
134 * Emits {@see PropertyChangeEvent}s for the property <tt>selected</tt>:
135 * <ul>
136 * <li>true, if the dialog is currently visible</li>
137 * <li>false, if the dialog is currently invisible</li>
138 * </ul>
139 *
140 */
141 public final class ToggleDialogAction extends JosmAction {
142
143 private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut, String prefname) {
144 super(name, iconName, tooltip, shortcut, false);
145 }
146
147 public void actionPerformed(ActionEvent e) {
148 toggleButtonHook();
149 if (isShowing) {
150 hideDialog();
151 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
152 hideNotify();
153 } else {
154 showDialog();
155 if (isDocked && isCollapsed) {
156 expand();
157 }
158 if (isDocked) {
159 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
160 }
161 showNotify();
162 }
163 }
164
165 @Override
166 public void destroy() {
167 super.destroy();
168 }
169 }
170
171 /**
172 * Shows the dialog
173 */
174 public void showDialog() {
175 setIsShowing(true);
176 if (!isDocked) {
177 detach();
178 } else {
179 dock();
180 this.setVisible(true);
181 }
182 // toggling the selected value in order to enforce PropertyChangeEvents
183 setIsShowing(true);
184 toggleAction.putValue("selected", false);
185 toggleAction.putValue("selected", true);
186 }
187
188 /**
189 * Changes the state of the dialog such that the user can see the content
190 * and takes care of the panel reconstruction.
191 */
192 public void unfurlDialog()
193 {
194 if (isDialogInDefaultView())
195 return;
196 if (isDialogInCollapsedView()) {
197 expand();
198 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, this);
199 } else if (!isDialogShowing()) {
200 showDialog();
201 if (isDocked && isCollapsed) {
202 expand();
203 }
204 if (isDocked) {
205 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, this);
206 }
207 showNotify();
208 }
209 }
210
211 /**
212 * Hides the dialog
213 */
214 public void hideDialog() {
215 closeDetachedDialog();
216 this.setVisible(false);
217 setIsShowing(false);
218 toggleAction.putValue("selected", false);
219 }
220
221 /**
222 * Displays the toggle dialog in the toggle dialog view on the right
223 * of the main map window.
224 *
225 */
226 protected void dock() {
227 detachedDialog = null;
228 titleBar.setVisible(true);
229 setIsDocked(true);
230 }
231
232 /**
233 * Display the dialog in a detached window.
234 *
235 */
236 protected void detach() {
237 setContentVisible(true);
238 this.setVisible(true);
239 titleBar.setVisible(false);
240 detachedDialog = new DetachedDialog();
241 detachedDialog.setVisible(true);
242 setIsShowing(true);
243 setIsDocked(false);
244 }
245
246 /**
247 * Collapses the toggle dialog to the title bar only
248 *
249 */
250 public void collapse() {
251 // if (isShowing && isDocked && !isCollapsed) {
252 if (isDialogInDefaultView()) {
253 setContentVisible(false);
254 setIsCollapsed(true);
255 setPreferredSize(new Dimension(0,20));
256 setMaximumSize(new Dimension(Integer.MAX_VALUE,20));
257 setMinimumSize(new Dimension(Integer.MAX_VALUE,20));
258 lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
259 }
260 else throw new IllegalStateException();
261 }
262
263 /**
264 * Expands the toggle dialog
265 */
266 protected void expand() {
267 // if (isShowing && isDocked && isCollapsed) {
268 if (isDialogInCollapsedView()) {
269 setContentVisible(true);
270 setIsCollapsed(false);
271 setPreferredSize(new Dimension(0,preferredHeight));
272 setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
273 lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
274 }
275 else throw new IllegalStateException();
276 }
277
278 /**
279 * Sets the visibility of all components in this toggle dialog, except the title bar
280 *
281 * @param visible true, if the components should be visible; false otherwise
282 */
283 protected void setContentVisible(boolean visible) {
284 Component comps[] = getComponents();
285 for(int i=0; i<comps.length; i++) {
286 if(comps[i] != titleBar) {
287 comps[i].setVisible(visible);
288 }
289 }
290 }
291
292 public void destroy() {
293 closeDetachedDialog();
294 hideNotify();
295 }
296
297 /**
298 * Closes the detached dialog if this toggle dialog is currently displayed
299 * in a detached dialog.
300 *
301 */
302 public void closeDetachedDialog() {
303 if (detachedDialog != null) {
304 detachedDialog.setVisible(false);
305 detachedDialog.getContentPane().removeAll();
306 detachedDialog.dispose();
307 }
308 }
309
310 /**
311 * Called when toggle dialog is shown (after it was created or expanded). Descendants may overwrite this
312 * method, it's a good place to register listeners needed to keep dialog updated
313 */
314 public void showNotify() {
315
316 }
317
318 /**
319 * Called when toggle dialog is hidden (collapsed, removed, MapFrame is removed, ...). Good place to unregister
320 * listeners
321 */
322 public void hideNotify() {
323
324 }
325
326 /**
327 * The title bar displayed in docked mode
328 *
329 */
330 protected class TitleBar extends JPanel {
331 final private JLabel lblTitle;
332 final private JComponent lblTitle_weak;
333
334 public TitleBar(String toggleDialogName, String iconName) {
335 setLayout(new GridBagLayout());
336 lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
337 add(lblMinimized);
338
339 // scale down the dialog icon
340 ImageIcon inIcon = ImageProvider.get("dialogs", iconName);
341 ImageIcon smallIcon = new ImageIcon(inIcon.getImage().getScaledInstance(16 , 16, Image.SCALE_SMOOTH));
342 lblTitle = new JLabel("",smallIcon, JLabel.TRAILING);
343 lblTitle.setIconTextGap(8);
344
345 JPanel conceal = new JPanel();
346 conceal.add(lblTitle);
347 conceal.setVisible(false);
348 add(conceal, GBC.std());
349
350 // Cannot add the label directly since it would displace other elements on resize
351 lblTitle_weak = new JComponent() {
352 @Override
353 public void paintComponent(Graphics g) {
354 lblTitle.paint(g);
355 }
356 };
357 lblTitle_weak.setPreferredSize(new Dimension(Integer.MAX_VALUE,20));
358 lblTitle_weak.setMinimumSize(new Dimension(0,20));
359 add(lblTitle_weak, GBC.std().fill(GBC.HORIZONTAL));
360
361 addMouseListener(
362 new MouseAdapter() {
363 @Override
364 public void mouseClicked(MouseEvent e) {
365 // toggleExpandedState
366 if (isCollapsed) {
367 expand();
368 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
369 } else {
370 collapse();
371 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
372 }
373 }
374 }
375 );
376
377 // show the sticky button
378 JButton sticky = new JButton(ImageProvider.get("misc", "sticky"));
379 sticky.setToolTipText(tr("Undock the panel"));
380 sticky.setBorder(BorderFactory.createEmptyBorder());
381 sticky.addActionListener(
382 new ActionListener(){
383 public void actionPerformed(ActionEvent e) {
384 detach();
385 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
386 }
387 }
388 );
389 add(sticky);
390
391 // show the close button
392 JButton close = new JButton(ImageProvider.get("misc", "close"));
393 close.setToolTipText(tr("Close this panel. You can reopen it with the buttons in the left toolbar."));
394 close.setBorder(BorderFactory.createEmptyBorder());
395 close.addActionListener(
396 new ActionListener(){
397 public void actionPerformed(ActionEvent e) {
398 hideDialog();
399 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
400 hideNotify();
401 }
402 }
403 );
404 add(close);
405 setToolTipText(tr("Click to minimize/maximize the panel content"));
406 setTitle(toggleDialogName);
407 }
408
409 public void setTitle(String title) {
410 lblTitle.setText(title);
411 lblTitle_weak.repaint();
412 }
413
414 public String getTitle() {
415 return lblTitle.getText();
416 }
417 }
418
419 /**
420 * The dialog class used to display toggle dialogs in a detached window.
421 *
422 */
423 private class DetachedDialog extends JDialog{
424 public DetachedDialog() {
425 super(JOptionPane.getFrameForComponent(Main.parent));
426 getContentPane().add(ToggleDialog.this);
427 addWindowListener(new WindowAdapter(){
428 @Override public void windowClosing(WindowEvent e) {
429 rememberGeometry();
430 getContentPane().removeAll();
431 dispose();
432 if (dockWhenClosingDetachedDlg()) {
433 dock();
434 if (isDialogInCollapsedView()) {
435 expand();
436 }
437 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
438 } else {
439 hideDialog();
440 hideNotify();
441 }
442 }
443 });
444 addComponentListener(new ComponentAdapter() {
445 @Override public void componentMoved(ComponentEvent e) {
446 rememberGeometry();
447 }
448 @Override public void componentResized(ComponentEvent e) {
449 rememberGeometry();
450 }
451 });
452
453 String bounds = Main.pref.get(preferencePrefix+".bounds",null);
454 if (bounds != null) {
455 String[] b = bounds.split(",");
456 setBounds(getDetachedGeometry(new Rectangle(
457 Integer.parseInt(b[0]),Integer.parseInt(b[1]),Integer.parseInt(b[2]),Integer.parseInt(b[3]))));
458 } else {
459 ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize());
460 pack();
461 setLocationRelativeTo(Main.parent);
462 }
463 setTitle(titleBar.getTitle());
464 HelpUtil.setHelpContext(getRootPane(), helpTopic());
465 }
466
467 protected void rememberGeometry() {
468 if (detachedDialog != null) {
469 Main.pref.put(preferencePrefix+".bounds", detachedDialog.getX()+","+detachedDialog.getY()+","+detachedDialog.getWidth()+","+detachedDialog.getHeight());
470 }
471 }
472 }
473
474 /**
475 * Replies the action to toggle the visible state of this toggle dialog
476 *
477 * @return the action to toggle the visible state of this toggle dialog
478 */
479 public AbstractAction getToggleAction() {
480 return toggleAction;
481 }
482
483 /**
484 * Replies the prefix for the preference settings of this dialog.
485 *
486 * @return the prefix for the preference settings of this dialog.
487 */
488 public String getPreferencePrefix() {
489 return preferencePrefix;
490 }
491
492 /**
493 * Sets the dialogsPanel managing all toggle dialogs
494 */
495 public void setDialogsPanel(DialogsPanel dialogsPanel) {
496 this.dialogsPanel = dialogsPanel;
497 }
498
499 /**
500 * Replies the name of this toggle dialog
501 */
502 @Override
503 public String getName() {
504 return "toggleDialog." + preferencePrefix;
505 }
506
507 /**
508 * Sets the title
509 */
510 public void setTitle(String title) {
511 titleBar.setTitle(title);
512 if (detachedDialog != null) {
513 detachedDialog.setTitle(title);
514 }
515 }
516
517 protected void setIsShowing(boolean val) {
518 isShowing = val;
519 Main.pref.put(preferencePrefix+".visible", val);
520 stateChanged();
521 }
522
523 protected void setIsDocked(boolean val) {
524 isDocked = val;
525 Main.pref.put(preferencePrefix+".docked", val);
526 stateChanged();
527 }
528
529 protected void setIsCollapsed(boolean val) {
530 isCollapsed = val;
531 Main.pref.put(preferencePrefix+".minimized", val);
532 stateChanged();
533 }
534
535 public int getPreferredHeight() {
536 return preferredHeight;
537 }
538
539 public String helpTopic() {
540 String help = getClass().getName();
541 help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
542 return "Dialog/"+help;
543 }
544
545 @Override
546 public String toString() {
547 return name;
548 }
549
550 /**
551 * Replies true if this dialog is showing either as docked or as detached dialog
552 */
553 public boolean isDialogShowing() {
554 return isShowing;
555 }
556
557 /**
558 * Replies true if this dialog is docked and expanded
559 */
560 public boolean isDialogInDefaultView() {
561 return isShowing && isDocked && (! isCollapsed);
562 }
563
564 /**
565 * Replies true if this dialog is docked and collapsed
566 */
567 public boolean isDialogInCollapsedView() {
568 return isShowing && isDocked && isCollapsed;
569 }
570
571 /***
572 * The following methods are intended to be overridden, in order to customize
573 * the toggle dialog behavior.
574 **/
575
576 /**
577 * Change the Geometry of the detached dialog to better fit the content.
578 */
579 protected Rectangle getDetachedGeometry(Rectangle last) {
580 return last;
581 }
582
583 /**
584 * Default size of the detached dialog.
585 * Override this method to customize the initial dialog size.
586 */
587 protected Dimension getDefaultDetachedSize() {
588 return new Dimension(dialogsPanel.getWidth(), preferredHeight);
589 }
590
591 /**
592 * Do something when the toggleButton is pressed.
593 */
594 protected void toggleButtonHook() {
595 }
596
597 protected boolean dockWhenClosingDetachedDlg() {
598 return true;
599 }
600
601 /**
602 * primitive stateChangedListener for subclasses
603 */
604 protected void stateChanged() {
605 }
606
607 protected JPanel getButtonPanel(int columns) {
608 JPanel pnl = new JPanel();
609 pnl.setLayout(Main.pref.getBoolean("dialog.align.left", false)
610 ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1,columns));
611 return pnl;
612 }
613}
Note: See TracBrowser for help on using the repository browser.