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

Last change on this file since 4778 was 4778, checked in by stoecker, 12 years ago

fix #7234 - dynamic toggle starts inverted

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