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

Last change on this file since 12301 was 11885, checked in by Don-vip, 7 years ago

add basic unit tests for MergeLayerAction + improve javadoc, code cleanup

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