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

Last change on this file since 10035 was 10035, checked in by Don-vip, 8 years ago

code refactoring to ease creation of unit tests by avoiding as much as possible HeadlessException

  • Property svn:eol-style set to native
File size: 33.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.Container;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.Graphics;
13import java.awt.GraphicsEnvironment;
14import java.awt.GridBagLayout;
15import java.awt.GridLayout;
16import java.awt.Rectangle;
17import java.awt.Toolkit;
18import java.awt.event.AWTEventListener;
19import java.awt.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.awt.event.ComponentAdapter;
22import java.awt.event.ComponentEvent;
23import java.awt.event.MouseEvent;
24import java.awt.event.WindowAdapter;
25import java.awt.event.WindowEvent;
26import java.beans.PropertyChangeEvent;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collection;
30import java.util.LinkedList;
31import java.util.List;
32
33import javax.swing.AbstractAction;
34import javax.swing.BorderFactory;
35import javax.swing.ButtonGroup;
36import javax.swing.JButton;
37import javax.swing.JCheckBoxMenuItem;
38import javax.swing.JComponent;
39import javax.swing.JDialog;
40import javax.swing.JLabel;
41import javax.swing.JMenu;
42import javax.swing.JPanel;
43import javax.swing.JPopupMenu;
44import javax.swing.JRadioButtonMenuItem;
45import javax.swing.JScrollPane;
46import javax.swing.JToggleButton;
47import javax.swing.Scrollable;
48import javax.swing.SwingUtilities;
49
50import org.openstreetmap.josm.Main;
51import org.openstreetmap.josm.actions.JosmAction;
52import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
53import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
54import org.openstreetmap.josm.data.preferences.BooleanProperty;
55import org.openstreetmap.josm.data.preferences.ParametrizedEnumProperty;
56import org.openstreetmap.josm.gui.MainMenu;
57import org.openstreetmap.josm.gui.ShowHideButtonListener;
58import org.openstreetmap.josm.gui.SideButton;
59import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
60import org.openstreetmap.josm.gui.help.HelpUtil;
61import org.openstreetmap.josm.gui.help.Helpful;
62import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
63import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
64import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
65import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
66import org.openstreetmap.josm.gui.util.GuiHelper;
67import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
68import org.openstreetmap.josm.tools.Destroyable;
69import org.openstreetmap.josm.tools.GBC;
70import org.openstreetmap.josm.tools.ImageProvider;
71import org.openstreetmap.josm.tools.Shortcut;
72import org.openstreetmap.josm.tools.WindowGeometry;
73import org.openstreetmap.josm.tools.WindowGeometry.WindowGeometryException;
74
75/**
76 * This class is a toggle dialog that can be turned on and off.
77 * @since 8
78 */
79public class ToggleDialog extends JPanel implements ShowHideButtonListener, Helpful, AWTEventListener, Destroyable, PreferenceChangedListener {
80
81 /**
82 * The button-hiding strategy in toggler dialogs.
83 */
84 public enum ButtonHidingType {
85 /** Buttons are always shown (default) **/
86 ALWAYS_SHOWN,
87 /** Buttons are always hidden **/
88 ALWAYS_HIDDEN,
89 /** Buttons are dynamically hidden, i.e. only shown when mouse cursor is in dialog */
90 DYNAMIC
91 }
92
93 /**
94 * Property to enable dynamic buttons globally.
95 * @since 6752
96 */
97 public static final BooleanProperty PROP_DYNAMIC_BUTTONS = new BooleanProperty("dialog.dynamic.buttons", false);
98
99 private final transient ParametrizedEnumProperty<ButtonHidingType> propButtonHiding =
100 new ParametrizedEnumProperty<ToggleDialog.ButtonHidingType>(ButtonHidingType.class, ButtonHidingType.DYNAMIC) {
101 @Override
102 protected String getKey(String... params) {
103 return preferencePrefix + ".buttonhiding";
104 }
105
106 @Override
107 protected ButtonHidingType parse(String s) {
108 try {
109 return super.parse(s);
110 } catch (IllegalArgumentException e) {
111 // Legacy settings
112 return Boolean.parseBoolean(s) ? ButtonHidingType.DYNAMIC : ButtonHidingType.ALWAYS_SHOWN;
113 }
114 }
115 };
116
117 /** The action to toggle this dialog */
118 protected final ToggleDialogAction toggleAction;
119 protected String preferencePrefix;
120 protected final String name;
121
122 /** DialogsPanel that manages all ToggleDialogs */
123 protected DialogsPanel dialogsPanel;
124
125 protected TitleBar titleBar;
126
127 /**
128 * Indicates whether the dialog is showing or not.
129 */
130 protected boolean isShowing;
131
132 /**
133 * If isShowing is true, indicates whether the dialog is docked or not, e. g.
134 * shown as part of the main window or as a separate dialog window.
135 */
136 protected boolean isDocked;
137
138 /**
139 * If isShowing and isDocked are true, indicates whether the dialog is
140 * currently minimized or not.
141 */
142 protected boolean isCollapsed;
143
144 /**
145 * Indicates whether dynamic button hiding is active or not.
146 */
147 protected ButtonHidingType buttonHiding;
148
149 /** the preferred height if the toggle dialog is expanded */
150 private int preferredHeight;
151
152 /** the JDialog displaying the toggle dialog as undocked dialog */
153 protected JDialog detachedDialog;
154
155 protected JToggleButton button;
156 private JPanel buttonsPanel;
157 private final transient List<javax.swing.Action> buttonActions = new ArrayList<>();
158
159 /** holds the menu entry in the windows menu. Required to properly
160 * toggle the checkbox on show/hide
161 */
162 protected JCheckBoxMenuItem windowMenuItem;
163
164 private final JRadioButtonMenuItem alwaysShown = new JRadioButtonMenuItem(new AbstractAction(tr("Always shown")) {
165 @Override
166 public void actionPerformed(ActionEvent e) {
167 setIsButtonHiding(ButtonHidingType.ALWAYS_SHOWN);
168 }
169 });
170
171 private final JRadioButtonMenuItem dynamic = new JRadioButtonMenuItem(new AbstractAction(tr("Dynamic")) {
172 @Override
173 public void actionPerformed(ActionEvent e) {
174 setIsButtonHiding(ButtonHidingType.DYNAMIC);
175 }
176 });
177
178 private final JRadioButtonMenuItem alwaysHidden = new JRadioButtonMenuItem(new AbstractAction(tr("Always hidden")) {
179 @Override
180 public void actionPerformed(ActionEvent e) {
181 setIsButtonHiding(ButtonHidingType.ALWAYS_HIDDEN);
182 }
183 });
184
185 /**
186 * The linked preferences class (optional). If set, accessible from the title bar with a dedicated button
187 */
188 protected Class<? extends PreferenceSetting> preferenceClass;
189
190 /**
191 * Constructor
192 *
193 * @param name the name of the dialog
194 * @param iconName the name of the icon to be displayed
195 * @param tooltip the tool tip
196 * @param shortcut the shortcut
197 * @param preferredHeight the preferred height for the dialog
198 */
199 public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
200 this(name, iconName, tooltip, shortcut, preferredHeight, false);
201 }
202
203 /**
204 * Constructor
205
206 * @param name the name of the dialog
207 * @param iconName the name of the icon to be displayed
208 * @param tooltip the tool tip
209 * @param shortcut the shortcut
210 * @param preferredHeight the preferred height for the dialog
211 * @param defShow if the dialog should be shown by default, if there is no preference
212 */
213 public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow) {
214 this(name, iconName, tooltip, shortcut, preferredHeight, defShow, null);
215 }
216
217 /**
218 * Constructor
219 *
220 * @param name the name of the dialog
221 * @param iconName the name of the icon to be displayed
222 * @param tooltip the tool tip
223 * @param shortcut the shortcut
224 * @param preferredHeight the preferred height for the dialog
225 * @param defShow if the dialog should be shown by default, if there is no preference
226 * @param prefClass the preferences settings class, or null if not applicable
227 */
228 public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow,
229 Class<? extends PreferenceSetting> prefClass) {
230 super(new BorderLayout());
231 this.preferencePrefix = iconName;
232 this.name = name;
233 this.preferenceClass = prefClass;
234
235 /** Use the full width of the parent element */
236 setPreferredSize(new Dimension(0, preferredHeight));
237 /** Override any minimum sizes of child elements so the user can resize freely */
238 setMinimumSize(new Dimension(0, 0));
239 this.preferredHeight = preferredHeight;
240 toggleAction = new ToggleDialogAction(name, "dialogs/"+iconName, tooltip, shortcut);
241 String helpId = "Dialog/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
242 toggleAction.putValue("help", helpId.substring(0, helpId.length()-6));
243
244 isShowing = Main.pref.getBoolean(preferencePrefix+".visible", defShow);
245 isDocked = Main.pref.getBoolean(preferencePrefix+".docked", true);
246 isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
247 buttonHiding = propButtonHiding.get();
248
249 /** show the minimize button */
250 titleBar = new TitleBar(name, iconName);
251 add(titleBar, BorderLayout.NORTH);
252
253 setBorder(BorderFactory.createEtchedBorder());
254
255 Main.redirectToMainContentPane(this);
256 Main.pref.addPreferenceChangeListener(this);
257
258 windowMenuItem = MainMenu.addWithCheckbox(Main.main.menu.windowMenu,
259 (JosmAction) getToggleAction(),
260 MainMenu.WINDOW_MENU_GROUP.TOGGLE_DIALOG);
261 }
262
263 /**
264 * The action to toggle the visibility state of this toggle dialog.
265 *
266 * Emits {@link PropertyChangeEvent}s for the property <tt>selected</tt>:
267 * <ul>
268 * <li>true, if the dialog is currently visible</li>
269 * <li>false, if the dialog is currently invisible</li>
270 * </ul>
271 *
272 */
273 public final class ToggleDialogAction extends JosmAction {
274
275 private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut) {
276 super(name, iconName, tooltip, shortcut, false);
277 }
278
279 @Override
280 public void actionPerformed(ActionEvent e) {
281 toggleButtonHook();
282 if (getValue("toolbarbutton") instanceof JButton) {
283 ((JButton) getValue("toolbarbutton")).setSelected(!isShowing);
284 }
285 if (isShowing) {
286 hideDialog();
287 if (dialogsPanel != null) {
288 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
289 }
290 hideNotify();
291 } else {
292 showDialog();
293 if (isDocked && isCollapsed) {
294 expand();
295 }
296 if (isDocked && dialogsPanel != null) {
297 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
298 }
299 showNotify();
300 }
301 }
302 }
303
304 /**
305 * Shows the dialog
306 */
307 public void showDialog() {
308 setIsShowing(true);
309 if (!isDocked) {
310 detach();
311 } else {
312 dock();
313 this.setVisible(true);
314 }
315 // toggling the selected value in order to enforce PropertyChangeEvents
316 setIsShowing(true);
317 windowMenuItem.setState(true);
318 toggleAction.putValue("selected", Boolean.FALSE);
319 toggleAction.putValue("selected", Boolean.TRUE);
320 }
321
322 /**
323 * Changes the state of the dialog such that the user can see the content.
324 * (takes care of the panel reconstruction)
325 */
326 public void unfurlDialog() {
327 if (isDialogInDefaultView())
328 return;
329 if (isDialogInCollapsedView()) {
330 expand();
331 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, this);
332 } else if (!isDialogShowing()) {
333 showDialog();
334 if (isDocked && isCollapsed) {
335 expand();
336 }
337 if (isDocked) {
338 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, this);
339 }
340 showNotify();
341 }
342 }
343
344 @Override
345 public void buttonHidden() {
346 if ((Boolean) toggleAction.getValue("selected")) {
347 toggleAction.actionPerformed(null);
348 }
349 }
350
351 @Override
352 public void buttonShown() {
353 unfurlDialog();
354 }
355
356 /**
357 * Hides the dialog
358 */
359 public void hideDialog() {
360 closeDetachedDialog();
361 this.setVisible(false);
362 windowMenuItem.setState(false);
363 setIsShowing(false);
364 toggleAction.putValue("selected", Boolean.FALSE);
365 }
366
367 /**
368 * Displays the toggle dialog in the toggle dialog view on the right
369 * of the main map window.
370 *
371 */
372 protected void dock() {
373 detachedDialog = null;
374 titleBar.setVisible(true);
375 setIsDocked(true);
376 }
377
378 /**
379 * Display the dialog in a detached window.
380 *
381 */
382 protected void detach() {
383 setContentVisible(true);
384 this.setVisible(true);
385 titleBar.setVisible(false);
386 if (!GraphicsEnvironment.isHeadless()) {
387 detachedDialog = new DetachedDialog();
388 detachedDialog.setVisible(true);
389 }
390 setIsShowing(true);
391 setIsDocked(false);
392 }
393
394 /**
395 * Collapses the toggle dialog to the title bar only
396 *
397 */
398 public void collapse() {
399 if (isDialogInDefaultView()) {
400 setContentVisible(false);
401 setIsCollapsed(true);
402 setPreferredSize(new Dimension(0, 20));
403 setMaximumSize(new Dimension(Integer.MAX_VALUE, 20));
404 setMinimumSize(new Dimension(Integer.MAX_VALUE, 20));
405 titleBar.lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
406 } else
407 throw new IllegalStateException();
408 }
409
410 /**
411 * Expands the toggle dialog
412 */
413 protected void expand() {
414 if (isDialogInCollapsedView()) {
415 setContentVisible(true);
416 setIsCollapsed(false);
417 setPreferredSize(new Dimension(0, preferredHeight));
418 setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
419 titleBar.lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
420 } else
421 throw new IllegalStateException();
422 }
423
424 /**
425 * Sets the visibility of all components in this toggle dialog, except the title bar
426 *
427 * @param visible true, if the components should be visible; false otherwise
428 */
429 protected void setContentVisible(boolean visible) {
430 Component[] comps = getComponents();
431 for (Component comp : comps) {
432 if (comp != titleBar && (!visible || comp != buttonsPanel || buttonHiding != ButtonHidingType.ALWAYS_HIDDEN)) {
433 comp.setVisible(visible);
434 }
435 }
436 }
437
438 @Override
439 public void destroy() {
440 closeDetachedDialog();
441 hideNotify();
442 Main.main.menu.windowMenu.remove(windowMenuItem);
443 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
444 Main.pref.removePreferenceChangeListener(this);
445 destroyComponents(this, false);
446 }
447
448 private void destroyComponents(Component component, boolean destroyItself) {
449 if (component instanceof Container) {
450 for (Component c: ((Container) component).getComponents()) {
451 destroyComponents(c, true);
452 }
453 }
454 if (destroyItself && component instanceof Destroyable) {
455 ((Destroyable) component).destroy();
456 }
457 }
458
459 /**
460 * Closes the detached dialog if this toggle dialog is currently displayed in a detached dialog.
461 *
462 */
463 public void closeDetachedDialog() {
464 if (detachedDialog != null) {
465 detachedDialog.setVisible(false);
466 detachedDialog.getContentPane().removeAll();
467 detachedDialog.dispose();
468 }
469 }
470
471 /**
472 * Called when toggle dialog is shown (after it was created or expanded). Descendants may overwrite this
473 * method, it's a good place to register listeners needed to keep dialog updated
474 */
475 public void showNotify() {
476
477 }
478
479 /**
480 * Called when toggle dialog is hidden (collapsed, removed, MapFrame is removed, ...). Good place to unregister
481 * listeners
482 */
483 public void hideNotify() {
484
485 }
486
487 /**
488 * The title bar displayed in docked mode
489 *
490 */
491 protected class TitleBar extends JPanel {
492 /** the label which shows whether the toggle dialog is expanded or collapsed */
493 private final JLabel lblMinimized;
494 /** the label which displays the dialog's title **/
495 private final JLabel lblTitle;
496 private final JComponent lblTitleWeak;
497 /** the button which shows whether buttons are dynamic or not */
498 private final JButton buttonsHide;
499 /** the contextual menu **/
500 private DialogPopupMenu popupMenu;
501
502 public TitleBar(String toggleDialogName, String iconName) {
503 setLayout(new GridBagLayout());
504
505 lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
506 add(lblMinimized);
507
508 // scale down the dialog icon
509 lblTitle = new JLabel("", new ImageProvider("dialogs", iconName).setWidth(16).get(), JLabel.TRAILING);
510 lblTitle.setIconTextGap(8);
511
512 JPanel conceal = new JPanel();
513 conceal.add(lblTitle);
514 conceal.setVisible(false);
515 add(conceal, GBC.std());
516
517 // Cannot add the label directly since it would displace other elements on resize
518 lblTitleWeak = new JComponent() {
519 @Override
520 public void paintComponent(Graphics g) {
521 lblTitle.paint(g);
522 }
523 };
524 lblTitleWeak.setPreferredSize(new Dimension(Integer.MAX_VALUE, 20));
525 lblTitleWeak.setMinimumSize(new Dimension(0, 20));
526 add(lblTitleWeak, GBC.std().fill(GBC.HORIZONTAL));
527
528 buttonsHide = new JButton(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
529 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
530 buttonsHide.setToolTipText(tr("Toggle dynamic buttons"));
531 buttonsHide.setBorder(BorderFactory.createEmptyBorder());
532 buttonsHide.addActionListener(
533 new ActionListener() {
534 @Override
535 public void actionPerformed(ActionEvent e) {
536 JRadioButtonMenuItem item = (buttonHiding == ButtonHidingType.DYNAMIC) ? alwaysShown : dynamic;
537 item.setSelected(true);
538 item.getAction().actionPerformed(null);
539 }
540 }
541 );
542 add(buttonsHide);
543
544 // show the pref button if applicable
545 if (preferenceClass != null) {
546 JButton pref = new JButton(new ImageProvider("preference").setWidth(16).get());
547 pref.setToolTipText(tr("Open preferences for this panel"));
548 pref.setBorder(BorderFactory.createEmptyBorder());
549 pref.addActionListener(
550 new ActionListener() {
551 @Override
552 @SuppressWarnings("unchecked")
553 public void actionPerformed(ActionEvent e) {
554 final PreferenceDialog p = new PreferenceDialog(Main.parent);
555 if (TabPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
556 p.selectPreferencesTabByClass((Class<? extends TabPreferenceSetting>) preferenceClass);
557 } else if (SubPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
558 p.selectSubPreferencesTabByClass((Class<? extends SubPreferenceSetting>) preferenceClass);
559 }
560 p.setVisible(true);
561 }
562 }
563 );
564 add(pref);
565 }
566
567 // show the sticky button
568 JButton sticky = new JButton(ImageProvider.get("misc", "sticky"));
569 sticky.setToolTipText(tr("Undock the panel"));
570 sticky.setBorder(BorderFactory.createEmptyBorder());
571 sticky.addActionListener(
572 new ActionListener() {
573 @Override
574 public void actionPerformed(ActionEvent e) {
575 detach();
576 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
577 }
578 }
579 );
580 add(sticky);
581
582 // show the close button
583 JButton close = new JButton(ImageProvider.get("misc", "close"));
584 close.setToolTipText(tr("Close this panel. You can reopen it with the buttons in the left toolbar."));
585 close.setBorder(BorderFactory.createEmptyBorder());
586 close.addActionListener(
587 new ActionListener() {
588 @Override
589 public void actionPerformed(ActionEvent e) {
590 hideDialog();
591 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
592 hideNotify();
593 }
594 }
595 );
596 add(close);
597 setToolTipText(tr("Click to minimize/maximize the panel content"));
598 setTitle(toggleDialogName);
599 }
600
601 public void setTitle(String title) {
602 lblTitle.setText(title);
603 lblTitleWeak.repaint();
604 }
605
606 public String getTitle() {
607 return lblTitle.getText();
608 }
609
610 public class DialogPopupMenu extends JPopupMenu {
611
612 /**
613 * Constructs a new {@code DialogPopupMenu}.
614 */
615 DialogPopupMenu() {
616 alwaysShown.setSelected(buttonHiding == ButtonHidingType.ALWAYS_SHOWN);
617 dynamic.setSelected(buttonHiding == ButtonHidingType.DYNAMIC);
618 alwaysHidden.setSelected(buttonHiding == ButtonHidingType.ALWAYS_HIDDEN);
619 ButtonGroup buttonHidingGroup = new ButtonGroup();
620 JMenu buttonHidingMenu = new JMenu(tr("Side buttons"));
621 for (JRadioButtonMenuItem rb : new JRadioButtonMenuItem[]{alwaysShown, dynamic, alwaysHidden}) {
622 buttonHidingGroup.add(rb);
623 buttonHidingMenu.add(rb);
624 }
625 add(buttonHidingMenu);
626 for (javax.swing.Action action: buttonActions) {
627 add(action);
628 }
629 }
630 }
631
632 public final void registerMouseListener() {
633 popupMenu = new DialogPopupMenu();
634 addMouseListener(new MouseEventHandler());
635 }
636
637 class MouseEventHandler extends PopupMenuLauncher {
638 /**
639 * Constructs a new {@code MouseEventHandler}.
640 */
641 MouseEventHandler() {
642 super(popupMenu);
643 }
644
645 @Override
646 public void mouseClicked(MouseEvent e) {
647 if (SwingUtilities.isLeftMouseButton(e)) {
648 if (isCollapsed) {
649 expand();
650 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
651 } else {
652 collapse();
653 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
654 }
655 }
656 }
657 }
658 }
659
660 /**
661 * The dialog class used to display toggle dialogs in a detached window.
662 *
663 */
664 private class DetachedDialog extends JDialog {
665 DetachedDialog() {
666 super(GuiHelper.getFrameForComponent(Main.parent));
667 getContentPane().add(ToggleDialog.this);
668 addWindowListener(new WindowAdapter() {
669 @Override public void windowClosing(WindowEvent e) {
670 rememberGeometry();
671 getContentPane().removeAll();
672 dispose();
673 if (dockWhenClosingDetachedDlg()) {
674 dock();
675 if (isDialogInCollapsedView()) {
676 expand();
677 }
678 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
679 } else {
680 hideDialog();
681 hideNotify();
682 }
683 }
684 });
685 addComponentListener(new ComponentAdapter() {
686 @Override
687 public void componentMoved(ComponentEvent e) {
688 rememberGeometry();
689 }
690
691 @Override
692 public void componentResized(ComponentEvent e) {
693 rememberGeometry();
694 }
695 });
696
697 try {
698 new WindowGeometry(preferencePrefix+".geometry").applySafe(this);
699 } catch (WindowGeometryException e) {
700 ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize());
701 pack();
702 setLocationRelativeTo(Main.parent);
703 }
704 super.setTitle(titleBar.getTitle());
705 HelpUtil.setHelpContext(getRootPane(), helpTopic());
706 }
707
708 protected void rememberGeometry() {
709 if (detachedDialog != null) {
710 new WindowGeometry(detachedDialog).remember(preferencePrefix+".geometry");
711 }
712 }
713 }
714
715 /**
716 * Replies the action to toggle the visible state of this toggle dialog
717 *
718 * @return the action to toggle the visible state of this toggle dialog
719 */
720 public AbstractAction getToggleAction() {
721 return toggleAction;
722 }
723
724 /**
725 * Replies the prefix for the preference settings of this dialog.
726 *
727 * @return the prefix for the preference settings of this dialog.
728 */
729 public String getPreferencePrefix() {
730 return preferencePrefix;
731 }
732
733 /**
734 * Sets the dialogsPanel managing all toggle dialogs.
735 * @param dialogsPanel The panel managing all toggle dialogs
736 */
737 public void setDialogsPanel(DialogsPanel dialogsPanel) {
738 this.dialogsPanel = dialogsPanel;
739 }
740
741 /**
742 * Replies the name of this toggle dialog
743 */
744 @Override
745 public String getName() {
746 return "toggleDialog." + preferencePrefix;
747 }
748
749 /**
750 * Sets the title.
751 * @param title The dialog's title
752 */
753 public void setTitle(String title) {
754 titleBar.setTitle(title);
755 if (detachedDialog != null) {
756 detachedDialog.setTitle(title);
757 }
758 }
759
760 protected void setIsShowing(boolean val) {
761 isShowing = val;
762 Main.pref.put(preferencePrefix+".visible", val);
763 stateChanged();
764 }
765
766 protected void setIsDocked(boolean val) {
767 if (buttonsPanel != null) {
768 buttonsPanel.setVisible(!val || buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
769 }
770 isDocked = val;
771 Main.pref.put(preferencePrefix+".docked", val);
772 stateChanged();
773 }
774
775 protected void setIsCollapsed(boolean val) {
776 isCollapsed = val;
777 Main.pref.put(preferencePrefix+".minimized", val);
778 stateChanged();
779 }
780
781 protected void setIsButtonHiding(ButtonHidingType val) {
782 buttonHiding = val;
783 propButtonHiding.put(val);
784 refreshHidingButtons();
785 }
786
787 /**
788 * Returns the preferred height of this dialog.
789 * @return The preferred height if the toggle dialog is expanded
790 */
791 public int getPreferredHeight() {
792 return preferredHeight;
793 }
794
795 @Override
796 public String helpTopic() {
797 String help = getClass().getName();
798 help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
799 return "Dialog/"+help;
800 }
801
802 @Override
803 public String toString() {
804 return name;
805 }
806
807 /**
808 * Determines if this dialog is showing either as docked or as detached dialog.
809 * @return {@code true} if this dialog is showing either as docked or as detached dialog
810 */
811 public boolean isDialogShowing() {
812 return isShowing;
813 }
814
815 /**
816 * Determines if this dialog is docked and expanded.
817 * @return {@code true} if this dialog is docked and expanded
818 */
819 public boolean isDialogInDefaultView() {
820 return isShowing && isDocked && (!isCollapsed);
821 }
822
823 /**
824 * Determines if this dialog is docked and collapsed.
825 * @return {@code true} if this dialog is docked and collapsed
826 */
827 public boolean isDialogInCollapsedView() {
828 return isShowing && isDocked && isCollapsed;
829 }
830
831 public void setButton(JToggleButton button) {
832 this.button = button;
833 }
834
835 public JToggleButton getButton() {
836 return button;
837 }
838
839 /*
840 * The following methods are intended to be overridden, in order to customize
841 * the toggle dialog behavior.
842 */
843
844 /**
845 * Returns the default size of the detached dialog.
846 * Override this method to customize the initial dialog size.
847 * @return the default size of the detached dialog
848 */
849 protected Dimension getDefaultDetachedSize() {
850 return new Dimension(dialogsPanel.getWidth(), preferredHeight);
851 }
852
853 /**
854 * Do something when the toggleButton is pressed.
855 */
856 protected void toggleButtonHook() {
857 }
858
859 protected boolean dockWhenClosingDetachedDlg() {
860 return true;
861 }
862
863 /**
864 * primitive stateChangedListener for subclasses
865 */
866 protected void stateChanged() {
867 }
868
869 protected Component createLayout(Component data, boolean scroll, Collection<SideButton> buttons) {
870 return createLayout(data, scroll, buttons, (Collection<SideButton>[]) null);
871 }
872
873 @SafeVarargs
874 protected final Component createLayout(Component data, boolean scroll, Collection<SideButton> firstButtons,
875 Collection<SideButton>... nextButtons) {
876 if (scroll) {
877 JScrollPane sp = new JScrollPane(data);
878 if (!(data instanceof Scrollable)) {
879 GuiHelper.setDefaultIncrement(sp);
880 }
881 data = sp;
882 }
883 LinkedList<Collection<SideButton>> buttons = new LinkedList<>();
884 buttons.addFirst(firstButtons);
885 if (nextButtons != null) {
886 buttons.addAll(Arrays.asList(nextButtons));
887 }
888 add(data, BorderLayout.CENTER);
889 if (!buttons.isEmpty() && buttons.get(0) != null && !buttons.get(0).isEmpty()) {
890 buttonsPanel = new JPanel(new GridLayout(buttons.size(), 1));
891 for (Collection<SideButton> buttonRow : buttons) {
892 if (buttonRow == null) {
893 continue;
894 }
895 final JPanel buttonRowPanel = new JPanel(Main.pref.getBoolean("dialog.align.left", false)
896 ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1, buttonRow.size()));
897 buttonsPanel.add(buttonRowPanel);
898 for (SideButton button : buttonRow) {
899 buttonRowPanel.add(button);
900 javax.swing.Action action = button.getAction();
901 if (action != null) {
902 buttonActions.add(action);
903 } else {
904 Main.warn("Button " + button + " doesn't have action defined");
905 Main.error(new Exception());
906 }
907 }
908 }
909 add(buttonsPanel, BorderLayout.SOUTH);
910 dynamicButtonsPropertyChanged();
911 } else {
912 titleBar.buttonsHide.setVisible(false);
913 }
914
915 // Register title bar mouse listener only after buttonActions has been initialized to have a complete popup menu
916 titleBar.registerMouseListener();
917
918 return data;
919 }
920
921 @Override
922 public void eventDispatched(AWTEvent event) {
923 if (isShowing() && !isCollapsed && isDocked && buttonHiding == ButtonHidingType.DYNAMIC) {
924 if (buttonsPanel != null) {
925 Rectangle b = this.getBounds();
926 b.setLocation(getLocationOnScreen());
927 if (b.contains(((MouseEvent) event).getLocationOnScreen())) {
928 if (!buttonsPanel.isVisible()) {
929 buttonsPanel.setVisible(true);
930 }
931 } else if (buttonsPanel.isVisible()) {
932 buttonsPanel.setVisible(false);
933 }
934 }
935 }
936 }
937
938 @Override
939 public void preferenceChanged(PreferenceChangeEvent e) {
940 if (e.getKey().equals(PROP_DYNAMIC_BUTTONS.getKey())) {
941 dynamicButtonsPropertyChanged();
942 }
943 }
944
945 private void dynamicButtonsPropertyChanged() {
946 boolean propEnabled = PROP_DYNAMIC_BUTTONS.get();
947 if (propEnabled) {
948 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK);
949 } else {
950 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
951 }
952 titleBar.buttonsHide.setVisible(propEnabled);
953 refreshHidingButtons();
954 }
955
956 private void refreshHidingButtons() {
957 titleBar.buttonsHide.setIcon(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
958 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
959 titleBar.buttonsHide.setEnabled(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
960 if (buttonsPanel != null) {
961 buttonsPanel.setVisible(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN || !isDocked);
962 }
963 stateChanged();
964 }
965}
Note: See TracBrowser for help on using the repository browser.