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

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

fix #16935 - simplify/cleanup help topics of ToggleDialog/ToggleDialogAction

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