001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.AWTEvent;
007import java.awt.BorderLayout;
008import java.awt.Component;
009import java.awt.Container;
010import java.awt.Dimension;
011import java.awt.FlowLayout;
012import java.awt.Graphics;
013import java.awt.GridBagLayout;
014import java.awt.GridLayout;
015import java.awt.Rectangle;
016import java.awt.Toolkit;
017import java.awt.event.AWTEventListener;
018import java.awt.event.ActionEvent;
019import java.awt.event.ActionListener;
020import java.awt.event.ComponentAdapter;
021import java.awt.event.ComponentEvent;
022import java.awt.event.MouseEvent;
023import java.awt.event.WindowAdapter;
024import java.awt.event.WindowEvent;
025import java.beans.PropertyChangeEvent;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.LinkedList;
030import java.util.List;
031
032import javax.swing.AbstractAction;
033import javax.swing.BorderFactory;
034import javax.swing.JButton;
035import javax.swing.JCheckBoxMenuItem;
036import javax.swing.JComponent;
037import javax.swing.JDialog;
038import javax.swing.JLabel;
039import javax.swing.JMenu;
040import javax.swing.JOptionPane;
041import javax.swing.JPanel;
042import javax.swing.JPopupMenu;
043import javax.swing.JRadioButtonMenuItem;
044import javax.swing.JScrollPane;
045import javax.swing.JToggleButton;
046import javax.swing.SwingUtilities;
047
048import org.openstreetmap.josm.Main;
049import org.openstreetmap.josm.actions.JosmAction;
050import org.openstreetmap.josm.data.preferences.ParametrizedEnumProperty;
051import org.openstreetmap.josm.gui.MainMenu;
052import org.openstreetmap.josm.gui.ShowHideButtonListener;
053import org.openstreetmap.josm.gui.SideButton;
054import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
055import org.openstreetmap.josm.gui.help.HelpUtil;
056import org.openstreetmap.josm.gui.help.Helpful;
057import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
058import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
059import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
060import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
061import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
062import org.openstreetmap.josm.tools.Destroyable;
063import org.openstreetmap.josm.tools.GBC;
064import org.openstreetmap.josm.tools.ImageProvider;
065import org.openstreetmap.josm.tools.Shortcut;
066import org.openstreetmap.josm.tools.WindowGeometry;
067import org.openstreetmap.josm.tools.WindowGeometry.WindowGeometryException;
068
069/**
070 * This class is a toggle dialog that can be turned on and off.
071 *
072 */
073public class ToggleDialog extends JPanel implements ShowHideButtonListener, Helpful, AWTEventListener, Destroyable {
074
075    public enum ButtonHiddingType {
076        ALWAYS_SHOWN, ALWAYS_HIDDEN, DYNAMIC
077    }
078
079    private final ParametrizedEnumProperty<ButtonHiddingType> PROP_BUTTON_HIDING = new ParametrizedEnumProperty<ToggleDialog.ButtonHiddingType>(ButtonHiddingType.class, ButtonHiddingType.DYNAMIC) {
080        @Override
081        protected String getKey(String... params) {
082            return preferencePrefix + ".buttonhiding";
083        }
084        @Override
085        protected ButtonHiddingType parse(String s) {
086            try {
087                return super.parse(s);
088            } catch (IllegalArgumentException e) {
089                // Legacy settings
090                return Boolean.parseBoolean(s)?ButtonHiddingType.DYNAMIC:ButtonHiddingType.ALWAYS_SHOWN;
091            }
092        }
093    };
094
095    /** The action to toggle this dialog */
096    protected final ToggleDialogAction toggleAction;
097    protected String preferencePrefix;
098    final protected String name;
099
100    /** DialogsPanel that manages all ToggleDialogs */
101    protected DialogsPanel dialogsPanel;
102
103    protected TitleBar titleBar;
104
105    /**
106     * Indicates whether the dialog is showing or not.
107     */
108    protected boolean isShowing;
109    /**
110     * If isShowing is true, indicates whether the dialog is docked or not, e. g.
111     * shown as part of the main window or as a separate dialog window.
112     */
113    protected boolean isDocked;
114    /**
115     * If isShowing and isDocked are true, indicates whether the dialog is
116     * currently minimized or not.
117     */
118    protected boolean isCollapsed;
119    /**
120     * Indicates whether dynamic button hiding is active or not.
121     */
122    protected ButtonHiddingType buttonHiding;
123
124    /** the preferred height if the toggle dialog is expanded */
125    private int preferredHeight;
126
127    /** the label in the title bar which shows whether the toggle dialog is expanded or collapsed */
128    private JLabel lblMinimized;
129
130    /** the label in the title bar which shows whether buttons are dynamic or not */
131    private JButton buttonsHide = null;
132
133    /** the JDialog displaying the toggle dialog as undocked dialog */
134    protected JDialog detachedDialog;
135
136    protected JToggleButton button;
137    private JPanel buttonsPanel;
138    private List<javax.swing.Action> buttonActions = new ArrayList<javax.swing.Action>();
139
140    /** holds the menu entry in the windows menu. Required to properly
141     * toggle the checkbox on show/hide
142     */
143    protected JCheckBoxMenuItem windowMenuItem;
144
145    /**
146     * The linked preferences class (optional). If set, accessible from the title bar with a dedicated button
147     */
148    protected Class<? extends PreferenceSetting> preferenceClass;
149
150    /**
151     * Constructor
152     *
153     * @param name  the name of the dialog
154     * @param iconName the name of the icon to be displayed
155     * @param tooltip  the tool tip
156     * @param shortcut  the shortcut
157     * @param preferredHeight the preferred height for the dialog
158     */
159    public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight) {
160        this(name, iconName, tooltip, shortcut, preferredHeight, false);
161    }
162    /**
163     * Constructor
164
165     * @param name  the name of the dialog
166     * @param iconName the name of the icon to be displayed
167     * @param tooltip  the tool tip
168     * @param shortcut  the shortcut
169     * @param preferredHeight the preferred height for the dialog
170     * @param defShow if the dialog should be shown by default, if there is no preference
171     */
172    public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow) {
173        this(name, iconName, tooltip, shortcut, preferredHeight, defShow, null);
174    }
175    /**
176     * Constructor
177     *
178     * @param name  the name of the dialog
179     * @param iconName the name of the icon to be displayed
180     * @param tooltip  the tool tip
181     * @param shortcut  the shortcut
182     * @param preferredHeight the preferred height for the dialog
183     * @param defShow if the dialog should be shown by default, if there is no preference
184     * @param prefClass the preferences settings class, or null if not applicable
185     */
186    public ToggleDialog(String name, String iconName, String tooltip, Shortcut shortcut, int preferredHeight, boolean defShow, Class<? extends PreferenceSetting> prefClass) {
187        super(new BorderLayout());
188        this.preferencePrefix = iconName;
189        this.name = name;
190        this.preferenceClass = prefClass;
191
192        /** Use the full width of the parent element */
193        setPreferredSize(new Dimension(0, preferredHeight));
194        /** Override any minimum sizes of child elements so the user can resize freely */
195        setMinimumSize(new Dimension(0,0));
196        this.preferredHeight = preferredHeight;
197        toggleAction = new ToggleDialogAction(name, "dialogs/"+iconName, tooltip, shortcut);
198        String helpId = "Dialog/"+getClass().getName().substring(getClass().getName().lastIndexOf('.')+1);
199        toggleAction.putValue("help", helpId.substring(0, helpId.length()-6));
200
201        isShowing = Main.pref.getBoolean(preferencePrefix+".visible", defShow);
202        isDocked = Main.pref.getBoolean(preferencePrefix+".docked", true);
203        isCollapsed = Main.pref.getBoolean(preferencePrefix+".minimized", false);
204        buttonHiding = PROP_BUTTON_HIDING.get();
205
206        /** show the minimize button */
207        titleBar = new TitleBar(name, iconName);
208        add(titleBar, BorderLayout.NORTH);
209
210        setBorder(BorderFactory.createEtchedBorder());
211
212        Main.redirectToMainContentPane(this);
213
214        windowMenuItem = MainMenu.addWithCheckbox(Main.main.menu.windowMenu,
215                (JosmAction) getToggleAction(),
216                MainMenu.WINDOW_MENU_GROUP.TOGGLE_DIALOG);
217    }
218
219    /**
220     * The action to toggle the visibility state of this toggle dialog.
221     *
222     * Emits {@link PropertyChangeEvent}s for the property <tt>selected</tt>:
223     * <ul>
224     *   <li>true, if the dialog is currently visible</li>
225     *   <li>false, if the dialog is currently invisible</li>
226     * </ul>
227     *
228     */
229    public final class ToggleDialogAction extends JosmAction {
230
231        private ToggleDialogAction(String name, String iconName, String tooltip, Shortcut shortcut) {
232            super(name, iconName, tooltip, shortcut, false);
233        }
234
235        @Override
236        public void actionPerformed(ActionEvent e) {
237            toggleButtonHook();
238            if (getValue("toolbarbutton") instanceof JButton) {
239                ((JButton) getValue("toolbarbutton")).setSelected(!isShowing);
240            }
241            if (isShowing) {
242                hideDialog();
243                if (dialogsPanel != null) {
244                    dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
245                }
246                hideNotify();
247            } else {
248                showDialog();
249                if (isDocked && isCollapsed) {
250                    expand();
251                }
252                if (isDocked && dialogsPanel != null) {
253                    dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
254                }
255                showNotify();
256            }
257        }
258    }
259
260    /**
261     * Shows the dialog
262     */
263    public void showDialog() {
264        setIsShowing(true);
265        if (!isDocked) {
266            detach();
267        } else {
268            dock();
269            this.setVisible(true);
270        }
271        // toggling the selected value in order to enforce PropertyChangeEvents
272        setIsShowing(true);
273        windowMenuItem.setState(true);
274        toggleAction.putValue("selected", false);
275        toggleAction.putValue("selected", true);
276    }
277
278    /**
279     * Changes the state of the dialog such that the user can see the content.
280     * (takes care of the panel reconstruction)
281     */
282    public void unfurlDialog() {
283        if (isDialogInDefaultView())
284            return;
285        if (isDialogInCollapsedView()) {
286            expand();
287            dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, this);
288        } else if (!isDialogShowing()) {
289            showDialog();
290            if (isDocked && isCollapsed) {
291                expand();
292            }
293            if (isDocked) {
294                dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, this);
295            }
296            showNotify();
297        }
298    }
299
300    @Override
301    public void buttonHidden() {
302        if ((Boolean) toggleAction.getValue("selected")) {
303            toggleAction.actionPerformed(null);
304        }
305    }
306
307    @Override
308    public void buttonShown() {
309        unfurlDialog();
310    }
311
312
313    /**
314     * Hides the dialog
315     */
316    public void hideDialog() {
317        closeDetachedDialog();
318        this.setVisible(false);
319        windowMenuItem.setState(false);
320        setIsShowing(false);
321        toggleAction.putValue("selected", false);
322    }
323
324    /**
325     * Displays the toggle dialog in the toggle dialog view on the right
326     * of the main map window.
327     *
328     */
329    protected void dock() {
330        detachedDialog = null;
331        titleBar.setVisible(true);
332        setIsDocked(true);
333    }
334
335    /**
336     * Display the dialog in a detached window.
337     *
338     */
339    protected void detach() {
340        setContentVisible(true);
341        this.setVisible(true);
342        titleBar.setVisible(false);
343        detachedDialog = new DetachedDialog();
344        detachedDialog.setVisible(true);
345        setIsShowing(true);
346        setIsDocked(false);
347    }
348
349    /**
350     * Collapses the toggle dialog to the title bar only
351     *
352     */
353    public void collapse() {
354        if (isDialogInDefaultView()) {
355            setContentVisible(false);
356            setIsCollapsed(true);
357            setPreferredSize(new Dimension(0,20));
358            setMaximumSize(new Dimension(Integer.MAX_VALUE,20));
359            setMinimumSize(new Dimension(Integer.MAX_VALUE,20));
360            lblMinimized.setIcon(ImageProvider.get("misc", "minimized"));
361        }
362        else throw new IllegalStateException();
363    }
364
365    /**
366     * Expands the toggle dialog
367     */
368    protected void expand() {
369        if (isDialogInCollapsedView()) {
370            setContentVisible(true);
371            setIsCollapsed(false);
372            setPreferredSize(new Dimension(0,preferredHeight));
373            setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
374            lblMinimized.setIcon(ImageProvider.get("misc", "normal"));
375        }
376        else throw new IllegalStateException();
377    }
378
379    /**
380     * Sets the visibility of all components in this toggle dialog, except the title bar
381     *
382     * @param visible true, if the components should be visible; false otherwise
383     */
384    protected void setContentVisible(boolean visible) {
385        Component[] comps = getComponents();
386        for (Component comp : comps) {
387            if (comp != titleBar && (!visible || comp != buttonsPanel || buttonHiding != ButtonHiddingType.ALWAYS_HIDDEN)) {
388                comp.setVisible(visible);
389            }
390        }
391    }
392
393    @Override
394    public void destroy() {
395        closeDetachedDialog();
396        hideNotify();
397        Main.main.menu.windowMenu.remove(windowMenuItem);
398        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
399        destroyComponents(this, false);
400    }
401
402    private void destroyComponents(Component component, boolean destroyItself) {
403        if (component instanceof Container) {
404            for (Component c: ((Container)component).getComponents()) {
405                destroyComponents(c, true);
406            }
407        }
408        if (destroyItself && component instanceof Destroyable) {
409            ((Destroyable) component).destroy();
410        }
411    }
412
413    /**
414     * Closes the detached dialog if this toggle dialog is currently displayed
415     * in a detached dialog.
416     *
417     */
418    public void closeDetachedDialog() {
419        if (detachedDialog != null) {
420            detachedDialog.setVisible(false);
421            detachedDialog.getContentPane().removeAll();
422            detachedDialog.dispose();
423        }
424    }
425
426    /**
427     * Called when toggle dialog is shown (after it was created or expanded). Descendants may overwrite this
428     * method, it's a good place to register listeners needed to keep dialog updated
429     */
430    public void showNotify() {
431
432    }
433
434    /**
435     * Called when toggle dialog is hidden (collapsed, removed, MapFrame is removed, ...). Good place to unregister
436     * listeners
437     */
438    public void hideNotify() {
439
440    }
441
442    /**
443     * The title bar displayed in docked mode
444     *
445     */
446    protected class TitleBar extends JPanel {
447        final private JLabel lblTitle;
448        final private JComponent lblTitle_weak;
449
450        public TitleBar(String toggleDialogName, String iconName) {
451            setLayout(new GridBagLayout());
452
453            lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
454            add(lblMinimized);
455
456            // scale down the dialog icon
457            lblTitle = new JLabel("", new ImageProvider("dialogs", iconName).setWidth(16).get(), JLabel.TRAILING);
458            lblTitle.setIconTextGap(8);
459
460            JPanel conceal = new JPanel();
461            conceal.add(lblTitle);
462            conceal.setVisible(false);
463            add(conceal, GBC.std());
464
465            // Cannot add the label directly since it would displace other elements on resize
466            lblTitle_weak = new JComponent() {
467                @Override
468                public void paintComponent(Graphics g) {
469                    lblTitle.paint(g);
470                }
471            };
472            lblTitle_weak.setPreferredSize(new Dimension(Integer.MAX_VALUE,20));
473            lblTitle_weak.setMinimumSize(new Dimension(0,20));
474            add(lblTitle_weak, GBC.std().fill(GBC.HORIZONTAL));
475
476            if(Main.pref.getBoolean("dialog.dynamic.buttons", true)) {
477                buttonsHide = new JButton(ImageProvider.get("misc", buttonHiding != ButtonHiddingType.ALWAYS_SHOWN ? "buttonhide" : "buttonshow"));
478                buttonsHide.setToolTipText(tr("Toggle dynamic buttons"));
479                buttonsHide.setBorder(BorderFactory.createEmptyBorder());
480                buttonsHide.addActionListener(
481                        new ActionListener(){
482                            @Override
483                            public void actionPerformed(ActionEvent e) {
484                                setIsButtonHiding(buttonHiding == ButtonHiddingType.ALWAYS_SHOWN?ButtonHiddingType.DYNAMIC:ButtonHiddingType.ALWAYS_SHOWN);
485                            }
486                        }
487                        );
488                add(buttonsHide);
489            }
490
491            // show the pref button if applicable
492            if (preferenceClass != null) {
493                JButton pref = new JButton(new ImageProvider("preference").setWidth(16).get());
494                pref.setToolTipText(tr("Open preferences for this panel"));
495                pref.setBorder(BorderFactory.createEmptyBorder());
496                pref.addActionListener(
497                        new ActionListener(){
498                            @Override
499                            @SuppressWarnings("unchecked")
500                            public void actionPerformed(ActionEvent e) {
501                                final PreferenceDialog p = new PreferenceDialog(Main.parent);
502                                if (TabPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
503                                    p.selectPreferencesTabByClass((Class<? extends TabPreferenceSetting>) preferenceClass);
504                                } else if (SubPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
505                                    p.selectSubPreferencesTabByClass((Class<? extends SubPreferenceSetting>) preferenceClass);
506                                }
507                                p.setVisible(true);
508                            }
509                        }
510                        );
511                add(pref);
512            }
513
514            // show the sticky button
515            JButton sticky = new JButton(ImageProvider.get("misc", "sticky"));
516            sticky.setToolTipText(tr("Undock the panel"));
517            sticky.setBorder(BorderFactory.createEmptyBorder());
518            sticky.addActionListener(
519                    new ActionListener(){
520                        @Override
521                        public void actionPerformed(ActionEvent e) {
522                            detach();
523                            dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
524                        }
525                    }
526                    );
527            add(sticky);
528
529            // show the close button
530            JButton close = new JButton(ImageProvider.get("misc", "close"));
531            close.setToolTipText(tr("Close this panel. You can reopen it with the buttons in the left toolbar."));
532            close.setBorder(BorderFactory.createEmptyBorder());
533            close.addActionListener(
534                    new ActionListener(){
535                        @Override
536                        public void actionPerformed(ActionEvent e) {
537                            hideDialog();
538                            dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
539                            hideNotify();
540                        }
541                    }
542                    );
543            add(close);
544            setToolTipText(tr("Click to minimize/maximize the panel content"));
545            setTitle(toggleDialogName);
546        }
547
548        public void setTitle(String title) {
549            lblTitle.setText(title);
550            lblTitle_weak.repaint();
551        }
552
553        public String getTitle() {
554            return lblTitle.getText();
555        }
556
557        public class DialogPopupMenu extends JPopupMenu {
558            public final JMenu buttonHidingMenu = new JMenu(tr("Side buttons"));
559            public final JRadioButtonMenuItem alwaysShown = new JRadioButtonMenuItem(new AbstractAction(tr("Always shown")) {
560                @Override public void actionPerformed(ActionEvent e) {
561                    setIsButtonHiding(ButtonHiddingType.ALWAYS_SHOWN);
562                }
563            });
564            public final JRadioButtonMenuItem dynamic = new JRadioButtonMenuItem(new AbstractAction(tr("Dynamic")) {
565                @Override public void actionPerformed(ActionEvent e) {
566                    setIsButtonHiding(ButtonHiddingType.DYNAMIC);
567                }
568            });
569            public final JRadioButtonMenuItem alwaysHidden = new JRadioButtonMenuItem(new AbstractAction(tr("Always hidden")) {
570                @Override public void actionPerformed(ActionEvent e) {
571                    setIsButtonHiding(ButtonHiddingType.ALWAYS_HIDDEN);
572                }
573            });
574            public DialogPopupMenu() {
575                alwaysShown.setSelected(buttonHiding == ButtonHiddingType.ALWAYS_SHOWN);
576                dynamic.setSelected(buttonHiding == ButtonHiddingType.DYNAMIC);
577                alwaysHidden.setSelected(buttonHiding == ButtonHiddingType.ALWAYS_HIDDEN);
578                buttonHidingMenu.add(alwaysShown);
579                buttonHidingMenu.add(dynamic);
580                buttonHidingMenu.add(alwaysHidden);
581                add(buttonHidingMenu);
582                for (javax.swing.Action action: buttonActions) {
583                    add(action);
584                }
585            }
586        }
587
588        public void registerMouseListener() {
589            addMouseListener(new MouseEventHandler());
590        }
591
592        class MouseEventHandler extends PopupMenuLauncher {
593            public MouseEventHandler() {
594                super(new DialogPopupMenu());
595            }
596            @Override public void mouseClicked(MouseEvent e) {
597                if (SwingUtilities.isLeftMouseButton(e)) {
598                    if (isCollapsed) {
599                        expand();
600                        dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
601                    } else {
602                        collapse();
603                        dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
604                    }
605                }
606            }
607        }
608    }
609
610    /**
611     * The dialog class used to display toggle dialogs in a detached window.
612     *
613     */
614    private class DetachedDialog extends JDialog{
615        public DetachedDialog() {
616            super(JOptionPane.getFrameForComponent(Main.parent));
617            getContentPane().add(ToggleDialog.this);
618            addWindowListener(new WindowAdapter(){
619                @Override public void windowClosing(WindowEvent e) {
620                    rememberGeometry();
621                    getContentPane().removeAll();
622                    dispose();
623                    if (dockWhenClosingDetachedDlg()) {
624                        dock();
625                        if (isDialogInCollapsedView()) {
626                            expand();
627                        }
628                        dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
629                    } else {
630                        hideDialog();
631                        hideNotify();
632                    }
633                }
634            });
635            addComponentListener(new ComponentAdapter() {
636                @Override public void componentMoved(ComponentEvent e) {
637                    rememberGeometry();
638                }
639                @Override public void componentResized(ComponentEvent e) {
640                    rememberGeometry();
641                }
642            });
643
644            try {
645                new WindowGeometry(preferencePrefix+".geometry").applySafe(this);
646            } catch (WindowGeometryException e) {
647                ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize());
648                pack();
649                setLocationRelativeTo(Main.parent);
650            }
651            setTitle(titleBar.getTitle());
652            HelpUtil.setHelpContext(getRootPane(), helpTopic());
653        }
654
655        protected void rememberGeometry() {
656            if (detachedDialog != null) {
657                new WindowGeometry(detachedDialog).remember(preferencePrefix+".geometry");
658            }
659        }
660    }
661
662    /**
663     * Replies the action to toggle the visible state of this toggle dialog
664     *
665     * @return the action to toggle the visible state of this toggle dialog
666     */
667    public AbstractAction getToggleAction() {
668        return toggleAction;
669    }
670
671    /**
672     * Replies the prefix for the preference settings of this dialog.
673     *
674     * @return the prefix for the preference settings of this dialog.
675     */
676    public String getPreferencePrefix() {
677        return preferencePrefix;
678    }
679
680    /**
681     * Sets the dialogsPanel managing all toggle dialogs
682     */
683    public void setDialogsPanel(DialogsPanel dialogsPanel) {
684        this.dialogsPanel = dialogsPanel;
685    }
686
687    /**
688     * Replies the name of this toggle dialog
689     */
690    @Override
691    public String getName() {
692        return "toggleDialog." + preferencePrefix;
693    }
694
695    /**
696     * Sets the title
697     */
698    public void setTitle(String title) {
699        titleBar.setTitle(title);
700        if (detachedDialog != null) {
701            detachedDialog.setTitle(title);
702        }
703    }
704
705    protected void setIsShowing(boolean val) {
706        isShowing = val;
707        Main.pref.put(preferencePrefix+".visible", val);
708        stateChanged();
709    }
710
711    protected void setIsDocked(boolean val) {
712        if(buttonsPanel != null && buttonsHide != null) {
713            buttonsPanel.setVisible(val ? buttonHiding == ButtonHiddingType.ALWAYS_SHOWN : true);
714        }
715        isDocked = val;
716        Main.pref.put(preferencePrefix+".docked", val);
717        stateChanged();
718    }
719
720    protected void setIsCollapsed(boolean val) {
721        isCollapsed = val;
722        Main.pref.put(preferencePrefix+".minimized", val);
723        stateChanged();
724    }
725
726    protected void setIsButtonHiding(ButtonHiddingType val) {
727        buttonHiding = val;
728        PROP_BUTTON_HIDING.put(val);
729        if (buttonsHide != null) {
730            buttonsHide.setIcon(ImageProvider.get("misc", val != ButtonHiddingType.ALWAYS_SHOWN ? "buttonhide" : "buttonshow"));
731        }
732        if (buttonsPanel != null) {
733            buttonsPanel.setVisible(val != ButtonHiddingType.ALWAYS_HIDDEN);
734        }
735        stateChanged();
736    }
737
738    public int getPreferredHeight() {
739        return preferredHeight;
740    }
741
742    @Override
743    public String helpTopic() {
744        String help = getClass().getName();
745        help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
746        return "Dialog/"+help;
747    }
748
749    @Override
750    public String toString() {
751        return name;
752    }
753
754    /**
755     * Replies true if this dialog is showing either as docked or as detached dialog
756     */
757    public boolean isDialogShowing() {
758        return isShowing;
759    }
760
761    /**
762     * Replies true if this dialog is docked and expanded
763     */
764    public boolean isDialogInDefaultView() {
765        return isShowing && isDocked && (! isCollapsed);
766    }
767
768    /**
769     * Replies true if this dialog is docked and collapsed
770     */
771    public boolean isDialogInCollapsedView() {
772        return isShowing && isDocked && isCollapsed;
773    }
774
775    public void setButton(JToggleButton button) {
776        this.button = button;
777    }
778
779    public JToggleButton getButton() {
780        return button;
781    }
782
783    /***
784     * The following methods are intended to be overridden, in order to customize
785     * the toggle dialog behavior.
786     **/
787
788    /**
789     * Change the Geometry of the detached dialog to better fit the content.
790     */
791    protected Rectangle getDetachedGeometry(Rectangle last) {
792        return last;
793    }
794
795    /**
796     * Default size of the detached dialog.
797     * Override this method to customize the initial dialog size.
798     */
799    protected Dimension getDefaultDetachedSize() {
800        return new Dimension(dialogsPanel.getWidth(), preferredHeight);
801    }
802
803    /**
804     * Do something when the toggleButton is pressed.
805     */
806    protected void toggleButtonHook() {
807    }
808
809    protected boolean dockWhenClosingDetachedDlg() {
810        return true;
811    }
812
813    /**
814     * primitive stateChangedListener for subclasses
815     */
816    protected void stateChanged() {
817    }
818
819    protected Component createLayout(Component data, boolean scroll, Collection<SideButton> buttons) {
820        return createLayout(data, scroll, buttons, (Collection<SideButton>[]) null);
821    }
822
823    protected Component createLayout(Component data, boolean scroll, Collection<SideButton> firstButtons, Collection<SideButton>... nextButtons) {
824        if (scroll) {
825            data = new JScrollPane(data);
826        }
827        LinkedList<Collection<SideButton>> buttons = new LinkedList<Collection<SideButton>>();
828        buttons.addFirst(firstButtons);
829        if (nextButtons != null) {
830            buttons.addAll(Arrays.asList(nextButtons));
831        }
832        add(data, BorderLayout.CENTER);
833        if (!buttons.isEmpty() && buttons.get(0) != null && !buttons.get(0).isEmpty()) {
834            buttonsPanel = new JPanel(new GridLayout(buttons.size(), 1));
835            for (Collection<SideButton> buttonRow : buttons) {
836                if (buttonRow == null) {
837                    continue;
838                }
839                final JPanel buttonRowPanel = new JPanel(Main.pref.getBoolean("dialog.align.left", false)
840                        ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1, buttonRow.size()));
841                buttonsPanel.add(buttonRowPanel);
842                for (SideButton button : buttonRow) {
843                    buttonRowPanel.add(button);
844                    javax.swing.Action action = button.getAction();
845                    if (action != null) {
846                        buttonActions.add(action);
847                    } else {
848                        Main.warn("Button " + button + " doesn't have action defined");
849                        new Exception().printStackTrace();
850                    }
851                }
852            }
853            add(buttonsPanel, BorderLayout.SOUTH);
854            if (Main.pref.getBoolean("dialog.dynamic.buttons", true)) {
855                Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK);
856                buttonsPanel.setVisible(buttonHiding == ButtonHiddingType.ALWAYS_SHOWN || !isDocked);
857            } else if (buttonHiding == ButtonHiddingType.ALWAYS_HIDDEN) {
858                buttonsPanel.setVisible(false);
859            }
860        } else if (buttonsHide != null) {
861            buttonsHide.setVisible(false);
862        }
863
864        // Register title bar mouse listener only after buttonActions has been initialized to have a complete popup menu
865        titleBar.registerMouseListener();
866
867        return data;
868    }
869
870    @Override
871    public void eventDispatched(AWTEvent event) {
872        if(isShowing() && !isCollapsed && isDocked && buttonHiding == ButtonHiddingType.DYNAMIC) {
873            Rectangle b = this.getBounds();
874            b.setLocation(getLocationOnScreen());
875            if (b.contains(((MouseEvent)event).getLocationOnScreen())) {
876                if(!buttonsPanel.isVisible()) {
877                    buttonsPanel.setVisible(true);
878                }
879            } else if (buttonsPanel.isVisible()) {
880                buttonsPanel.setVisible(false);
881            }
882        }
883    }
884}