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

Last change on this file since 4221 was 3598, checked in by bastiK, 14 years ago

add option to hide toggle buttons on the left

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