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