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

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

fix #12904 - Documentation of DialogsPanel (patch by michael2402)

  • Property svn:eol-style set to native
File size: 34.7 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 static 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 public void closeDetachedDialog() {
463 if (detachedDialog != null) {
464 detachedDialog.setVisible(false);
465 detachedDialog.getContentPane().removeAll();
466 detachedDialog.dispose();
467 }
468 }
469
470 /**
471 * Called when toggle dialog is shown (after it was created or expanded). Descendants may overwrite this
472 * method, it's a good place to register listeners needed to keep dialog updated
473 */
474 public void showNotify() {
475 // Do nothing
476 }
477
478 /**
479 * Called when toggle dialog is hidden (collapsed, removed, MapFrame is removed, ...). Good place to unregister listeners
480 */
481 public void hideNotify() {
482 // Do nothing
483 }
484
485 /**
486 * The title bar displayed in docked mode
487 */
488 protected class TitleBar extends JPanel {
489 /** the label which shows whether the toggle dialog is expanded or collapsed */
490 private final JLabel lblMinimized;
491 /** the label which displays the dialog's title **/
492 private final JLabel lblTitle;
493 private final JComponent lblTitleWeak;
494 /** the button which shows whether buttons are dynamic or not */
495 private final JButton buttonsHide;
496 /** the contextual menu **/
497 private DialogPopupMenu popupMenu;
498
499 public TitleBar(String toggleDialogName, String iconName) {
500 setLayout(new GridBagLayout());
501
502 lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
503 add(lblMinimized);
504
505 // scale down the dialog icon
506 lblTitle = new JLabel("", new ImageProvider("dialogs", iconName).setWidth(16).get(), JLabel.TRAILING);
507 lblTitle.setIconTextGap(8);
508
509 JPanel conceal = new JPanel();
510 conceal.add(lblTitle);
511 conceal.setVisible(false);
512 add(conceal, GBC.std());
513
514 // Cannot add the label directly since it would displace other elements on resize
515 lblTitleWeak = new JComponent() {
516 @Override
517 public void paintComponent(Graphics g) {
518 lblTitle.paint(g);
519 }
520 };
521 lblTitleWeak.setPreferredSize(new Dimension(Integer.MAX_VALUE, 20));
522 lblTitleWeak.setMinimumSize(new Dimension(0, 20));
523 add(lblTitleWeak, GBC.std().fill(GBC.HORIZONTAL));
524
525 buttonsHide = new JButton(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
526 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
527 buttonsHide.setToolTipText(tr("Toggle dynamic buttons"));
528 buttonsHide.setBorder(BorderFactory.createEmptyBorder());
529 buttonsHide.addActionListener(
530 new ActionListener() {
531 @Override
532 public void actionPerformed(ActionEvent e) {
533 JRadioButtonMenuItem item = (buttonHiding == ButtonHidingType.DYNAMIC) ? alwaysShown : dynamic;
534 item.setSelected(true);
535 item.getAction().actionPerformed(null);
536 }
537 }
538 );
539 add(buttonsHide);
540
541 // show the pref button if applicable
542 if (preferenceClass != null) {
543 JButton pref = new JButton(new ImageProvider("preference").setWidth(16).get());
544 pref.setToolTipText(tr("Open preferences for this panel"));
545 pref.setBorder(BorderFactory.createEmptyBorder());
546 pref.addActionListener(
547 new ActionListener() {
548 @Override
549 @SuppressWarnings("unchecked")
550 public void actionPerformed(ActionEvent e) {
551 final PreferenceDialog p = new PreferenceDialog(Main.parent);
552 if (TabPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
553 p.selectPreferencesTabByClass((Class<? extends TabPreferenceSetting>) preferenceClass);
554 } else if (SubPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
555 p.selectSubPreferencesTabByClass((Class<? extends SubPreferenceSetting>) preferenceClass);
556 }
557 p.setVisible(true);
558 }
559 }
560 );
561 add(pref);
562 }
563
564 // show the sticky button
565 JButton sticky = new JButton(ImageProvider.get("misc", "sticky"));
566 sticky.setToolTipText(tr("Undock the panel"));
567 sticky.setBorder(BorderFactory.createEmptyBorder());
568 sticky.addActionListener(
569 new ActionListener() {
570 @Override
571 public void actionPerformed(ActionEvent e) {
572 detach();
573 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
574 }
575 }
576 );
577 add(sticky);
578
579 // show the close button
580 JButton close = new JButton(ImageProvider.get("misc", "close"));
581 close.setToolTipText(tr("Close this panel. You can reopen it with the buttons in the left toolbar."));
582 close.setBorder(BorderFactory.createEmptyBorder());
583 close.addActionListener(
584 new ActionListener() {
585 @Override
586 public void actionPerformed(ActionEvent e) {
587 hideDialog();
588 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
589 hideNotify();
590 }
591 }
592 );
593 add(close);
594 setToolTipText(tr("Click to minimize/maximize the panel content"));
595 setTitle(toggleDialogName);
596 }
597
598 public void setTitle(String title) {
599 lblTitle.setText(title);
600 lblTitleWeak.repaint();
601 }
602
603 public String getTitle() {
604 return lblTitle.getText();
605 }
606
607 /**
608 * This is the popup menu used for the title bar.
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 /**
633 * Registers the mouse listeners.
634 * <p>
635 * Should be called once after this title was added to the dialog.
636 */
637 public final void registerMouseListener() {
638 popupMenu = new DialogPopupMenu();
639 addMouseListener(new MouseEventHandler());
640 }
641
642 class MouseEventHandler extends PopupMenuLauncher {
643 /**
644 * Constructs a new {@code MouseEventHandler}.
645 */
646 MouseEventHandler() {
647 super(popupMenu);
648 }
649
650 @Override
651 public void mouseClicked(MouseEvent e) {
652 if (SwingUtilities.isLeftMouseButton(e)) {
653 if (isCollapsed) {
654 expand();
655 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
656 } else {
657 collapse();
658 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
659 }
660 }
661 }
662 }
663 }
664
665 /**
666 * The dialog class used to display toggle dialogs in a detached window.
667 *
668 */
669 private class DetachedDialog extends JDialog {
670 DetachedDialog() {
671 super(GuiHelper.getFrameForComponent(Main.parent));
672 getContentPane().add(ToggleDialog.this);
673 addWindowListener(new WindowAdapter() {
674 @Override public void windowClosing(WindowEvent e) {
675 rememberGeometry();
676 getContentPane().removeAll();
677 dispose();
678 if (dockWhenClosingDetachedDlg()) {
679 dock();
680 if (isDialogInCollapsedView()) {
681 expand();
682 }
683 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
684 } else {
685 hideDialog();
686 hideNotify();
687 }
688 }
689 });
690 addComponentListener(new ComponentAdapter() {
691 @Override
692 public void componentMoved(ComponentEvent e) {
693 rememberGeometry();
694 }
695
696 @Override
697 public void componentResized(ComponentEvent e) {
698 rememberGeometry();
699 }
700 });
701
702 try {
703 new WindowGeometry(preferencePrefix+".geometry").applySafe(this);
704 } catch (WindowGeometryException e) {
705 ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize());
706 pack();
707 setLocationRelativeTo(Main.parent);
708 }
709 super.setTitle(titleBar.getTitle());
710 HelpUtil.setHelpContext(getRootPane(), helpTopic());
711 }
712
713 protected void rememberGeometry() {
714 if (detachedDialog != null) {
715 new WindowGeometry(detachedDialog).remember(preferencePrefix+".geometry");
716 }
717 }
718 }
719
720 /**
721 * Replies the action to toggle the visible state of this toggle dialog
722 *
723 * @return the action to toggle the visible state of this toggle dialog
724 */
725 public AbstractAction getToggleAction() {
726 return toggleAction;
727 }
728
729 /**
730 * Replies the prefix for the preference settings of this dialog.
731 *
732 * @return the prefix for the preference settings of this dialog.
733 */
734 public String getPreferencePrefix() {
735 return preferencePrefix;
736 }
737
738 /**
739 * Sets the dialogsPanel managing all toggle dialogs.
740 * @param dialogsPanel The panel managing all toggle dialogs
741 */
742 public void setDialogsPanel(DialogsPanel dialogsPanel) {
743 this.dialogsPanel = dialogsPanel;
744 }
745
746 /**
747 * Replies the name of this toggle dialog
748 */
749 @Override
750 public String getName() {
751 return "toggleDialog." + preferencePrefix;
752 }
753
754 /**
755 * Sets the title.
756 * @param title The dialog's title
757 */
758 public void setTitle(String title) {
759 titleBar.setTitle(title);
760 if (detachedDialog != null) {
761 detachedDialog.setTitle(title);
762 }
763 }
764
765 protected void setIsShowing(boolean val) {
766 isShowing = val;
767 Main.pref.put(preferencePrefix+".visible", val);
768 stateChanged();
769 }
770
771 protected void setIsDocked(boolean val) {
772 if (buttonsPanel != null) {
773 buttonsPanel.setVisible(!val || buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
774 }
775 isDocked = val;
776 Main.pref.put(preferencePrefix+".docked", val);
777 stateChanged();
778 }
779
780 protected void setIsCollapsed(boolean val) {
781 isCollapsed = val;
782 Main.pref.put(preferencePrefix+".minimized", val);
783 stateChanged();
784 }
785
786 protected void setIsButtonHiding(ButtonHidingType val) {
787 buttonHiding = val;
788 propButtonHiding.put(val);
789 refreshHidingButtons();
790 }
791
792 /**
793 * Returns the preferred height of this dialog.
794 * @return The preferred height if the toggle dialog is expanded
795 */
796 public int getPreferredHeight() {
797 return preferredHeight;
798 }
799
800 @Override
801 public String helpTopic() {
802 String help = getClass().getName();
803 help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
804 return "Dialog/"+help;
805 }
806
807 @Override
808 public String toString() {
809 return name;
810 }
811
812 /**
813 * Determines if this dialog is showing either as docked or as detached dialog.
814 * @return {@code true} if this dialog is showing either as docked or as detached dialog
815 */
816 public boolean isDialogShowing() {
817 return isShowing;
818 }
819
820 /**
821 * Determines if this dialog is docked and expanded.
822 * @return {@code true} if this dialog is docked and expanded
823 */
824 public boolean isDialogInDefaultView() {
825 return isShowing && isDocked && (!isCollapsed);
826 }
827
828 /**
829 * Determines if this dialog is docked and collapsed.
830 * @return {@code true} if this dialog is docked and collapsed
831 */
832 public boolean isDialogInCollapsedView() {
833 return isShowing && isDocked && isCollapsed;
834 }
835
836 /**
837 * Sets the button from the button list that is used to display this dialog.
838 * <p>
839 * Note: This is ignored by the {@link ToggleDialog} for now.
840 * @param button The button for this dialog.
841 */
842 public void setButton(JToggleButton button) {
843 this.button = button;
844 }
845
846 /**
847 * Gets the button from the button list that is used to display this dialog.
848 * @return button The button for this dialog.
849 */
850 public JToggleButton getButton() {
851 return button;
852 }
853
854 /*
855 * The following methods are intended to be overridden, in order to customize
856 * the toggle dialog behavior.
857 */
858
859 /**
860 * Returns the default size of the detached dialog.
861 * Override this method to customize the initial dialog size.
862 * @return the default size of the detached dialog
863 */
864 protected Dimension getDefaultDetachedSize() {
865 return new Dimension(dialogsPanel.getWidth(), preferredHeight);
866 }
867
868 /**
869 * Do something when the toggleButton is pressed.
870 */
871 protected void toggleButtonHook() {
872 // Do nothing
873 }
874
875 protected boolean dockWhenClosingDetachedDlg() {
876 return true;
877 }
878
879 /**
880 * primitive stateChangedListener for subclasses
881 */
882 protected void stateChanged() {
883 // Do nothing
884 }
885
886 /**
887 * Create a component with the given layout for this component.
888 * @param data The content to be displayed
889 * @param scroll <code>true</code> if it should be wrapped in a {@link JScrollPane}
890 * @param buttons The buttons to add.
891 * @return The component.
892 */
893 protected Component createLayout(Component data, boolean scroll, Collection<SideButton> buttons) {
894 return createLayout(data, scroll, buttons, (Collection<SideButton>[]) null);
895 }
896
897 @SafeVarargs
898 protected final Component createLayout(Component data, boolean scroll, Collection<SideButton> firstButtons,
899 Collection<SideButton>... nextButtons) {
900 if (scroll) {
901 JScrollPane sp = new JScrollPane(data);
902 if (!(data instanceof Scrollable)) {
903 GuiHelper.setDefaultIncrement(sp);
904 }
905 data = sp;
906 }
907 LinkedList<Collection<SideButton>> buttons = new LinkedList<>();
908 buttons.addFirst(firstButtons);
909 if (nextButtons != null) {
910 buttons.addAll(Arrays.asList(nextButtons));
911 }
912 add(data, BorderLayout.CENTER);
913 if (!buttons.isEmpty() && buttons.get(0) != null && !buttons.get(0).isEmpty()) {
914 buttonsPanel = new JPanel(new GridLayout(buttons.size(), 1));
915 for (Collection<SideButton> buttonRow : buttons) {
916 if (buttonRow == null) {
917 continue;
918 }
919 final JPanel buttonRowPanel = new JPanel(Main.pref.getBoolean("dialog.align.left", false)
920 ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1, buttonRow.size()));
921 buttonsPanel.add(buttonRowPanel);
922 for (SideButton button : buttonRow) {
923 buttonRowPanel.add(button);
924 javax.swing.Action action = button.getAction();
925 if (action != null) {
926 buttonActions.add(action);
927 } else {
928 Main.warn("Button " + button + " doesn't have action defined");
929 Main.error(new Exception());
930 }
931 }
932 }
933 add(buttonsPanel, BorderLayout.SOUTH);
934 dynamicButtonsPropertyChanged();
935 } else {
936 titleBar.buttonsHide.setVisible(false);
937 }
938
939 // Register title bar mouse listener only after buttonActions has been initialized to have a complete popup menu
940 titleBar.registerMouseListener();
941
942 return data;
943 }
944
945 @Override
946 public void eventDispatched(AWTEvent event) {
947 if (isShowing() && !isCollapsed && isDocked && buttonHiding == ButtonHidingType.DYNAMIC) {
948 if (buttonsPanel != null) {
949 Rectangle b = this.getBounds();
950 b.setLocation(getLocationOnScreen());
951 if (b.contains(((MouseEvent) event).getLocationOnScreen())) {
952 if (!buttonsPanel.isVisible()) {
953 buttonsPanel.setVisible(true);
954 }
955 } else if (buttonsPanel.isVisible()) {
956 buttonsPanel.setVisible(false);
957 }
958 }
959 }
960 }
961
962 @Override
963 public void preferenceChanged(PreferenceChangeEvent e) {
964 if (e.getKey().equals(PROP_DYNAMIC_BUTTONS.getKey())) {
965 dynamicButtonsPropertyChanged();
966 }
967 }
968
969 private void dynamicButtonsPropertyChanged() {
970 boolean propEnabled = PROP_DYNAMIC_BUTTONS.get();
971 if (propEnabled) {
972 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK);
973 } else {
974 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
975 }
976 titleBar.buttonsHide.setVisible(propEnabled);
977 refreshHidingButtons();
978 }
979
980 private void refreshHidingButtons() {
981 titleBar.buttonsHide.setIcon(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
982 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
983 titleBar.buttonsHide.setEnabled(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
984 if (buttonsPanel != null) {
985 buttonsPanel.setVisible(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN || !isDocked);
986 }
987 stateChanged();
988 }
989}
Note: See TracBrowser for help on using the repository browser.