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

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

fix #13000 - Exception when deleting last layer for 2nd time (patch by michael2402) - gsoc-core

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