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

Last change on this file since 12841 was 12841, checked in by bastiK, 5 weeks ago

see #15229 - fix deprecations caused by [12840]

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