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

Last change on this file since 12881 was 12881, checked in by bastiK, 7 years ago

see #15229 - move remaining classes to spi.preferences package, to make it self-contained

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