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

Last change on this file since 13724 was 13649, checked in by Don-vip, 6 years ago

fix #16204 - allow to create a new layer, draw, drag, open a few windows. Nothing more to hope in sandbox mode. At least JOSM is now more robust than ever.

  • Property svn:eol-style set to native
File size: 35.1 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.BooleanProperty;
53import org.openstreetmap.josm.data.preferences.ParametrizedEnumProperty;
54import org.openstreetmap.josm.gui.MainApplication;
55import org.openstreetmap.josm.gui.MainMenu;
56import org.openstreetmap.josm.gui.ShowHideButtonListener;
57import org.openstreetmap.josm.gui.SideButton;
58import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
59import org.openstreetmap.josm.gui.help.HelpUtil;
60import org.openstreetmap.josm.gui.help.Helpful;
61import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
62import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
63import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
64import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
65import org.openstreetmap.josm.gui.util.GuiHelper;
66import org.openstreetmap.josm.gui.util.WindowGeometry;
67import org.openstreetmap.josm.gui.util.WindowGeometry.WindowGeometryException;
68import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
69import org.openstreetmap.josm.spi.preferences.Config;
70import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
71import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
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 <code>selected</code>:
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, 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 try {
471 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
472 } catch (SecurityException e) {
473 Logging.log(Logging.LEVEL_ERROR, "Unable to remove AWT event listener", e);
474 }
475 Config.getPref().removePreferenceChangeListener(this);
476 destroyComponents(this, false);
477 }
478
479 private static void destroyComponents(Component component, boolean destroyItself) {
480 if (component instanceof Container) {
481 for (Component c: ((Container) component).getComponents()) {
482 destroyComponents(c, true);
483 }
484 }
485 if (destroyItself && component instanceof Destroyable) {
486 ((Destroyable) component).destroy();
487 }
488 }
489
490 /**
491 * Closes the detached dialog if this toggle dialog is currently displayed in a detached dialog.
492 */
493 public void closeDetachedDialog() {
494 if (detachedDialog != null) {
495 detachedDialog.setVisible(false);
496 detachedDialog.getContentPane().removeAll();
497 detachedDialog.dispose();
498 }
499 }
500
501 /**
502 * Called when toggle dialog is shown (after it was created or expanded). Descendants may overwrite this
503 * method, it's a good place to register listeners needed to keep dialog updated
504 */
505 public void showNotify() {
506 // Do nothing
507 }
508
509 /**
510 * Called when toggle dialog is hidden (collapsed, removed, MapFrame is removed, ...). Good place to unregister listeners
511 */
512 public void hideNotify() {
513 // Do nothing
514 }
515
516 /**
517 * The title bar displayed in docked mode
518 */
519 protected class TitleBar extends JPanel {
520 /** the label which shows whether the toggle dialog is expanded or collapsed */
521 private final JLabel lblMinimized;
522 /** the label which displays the dialog's title **/
523 private final JLabel lblTitle;
524 private final JComponent lblTitleWeak;
525 /** the button which shows whether buttons are dynamic or not */
526 private final JButton buttonsHide;
527 /** the contextual menu **/
528 private DialogPopupMenu popupMenu;
529
530 @SuppressWarnings("unchecked")
531 public TitleBar(String toggleDialogName, String iconName) {
532 setLayout(new GridBagLayout());
533
534 lblMinimized = new JLabel(ImageProvider.get("misc", "normal"));
535 add(lblMinimized);
536
537 // scale down the dialog icon
538 ImageIcon icon = ImageProvider.get("dialogs", iconName, ImageProvider.ImageSizes.SMALLICON);
539 lblTitle = new JLabel("", icon, JLabel.TRAILING);
540 lblTitle.setIconTextGap(8);
541
542 JPanel conceal = new JPanel();
543 conceal.add(lblTitle);
544 conceal.setVisible(false);
545 add(conceal, GBC.std());
546
547 // Cannot add the label directly since it would displace other elements on resize
548 lblTitleWeak = new JComponent() {
549 @Override
550 public void paintComponent(Graphics g) {
551 lblTitle.paint(g);
552 }
553 };
554 lblTitleWeak.setPreferredSize(new Dimension(Integer.MAX_VALUE, 20));
555 lblTitleWeak.setMinimumSize(new Dimension(0, 20));
556 add(lblTitleWeak, GBC.std().fill(GBC.HORIZONTAL));
557
558 buttonsHide = new JButton(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
559 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
560 buttonsHide.setToolTipText(tr("Toggle dynamic buttons"));
561 buttonsHide.setBorder(BorderFactory.createEmptyBorder());
562 buttonsHide.addActionListener(e -> {
563 JRadioButtonMenuItem item = (buttonHiding == ButtonHidingType.DYNAMIC) ? alwaysShown : dynamic;
564 item.setSelected(true);
565 item.getAction().actionPerformed(null);
566 });
567 add(buttonsHide);
568
569 // show the pref button if applicable
570 if (preferenceClass != null) {
571 JButton pref = new JButton(ImageProvider.get("preference", ImageProvider.ImageSizes.SMALLICON));
572 pref.setToolTipText(tr("Open preferences for this panel"));
573 pref.setBorder(BorderFactory.createEmptyBorder());
574 pref.addActionListener(e -> {
575 final PreferenceDialog p = new PreferenceDialog(Main.parent);
576 if (TabPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
577 p.selectPreferencesTabByClass((Class<? extends TabPreferenceSetting>) preferenceClass);
578 } else if (SubPreferenceSetting.class.isAssignableFrom(preferenceClass)) {
579 p.selectSubPreferencesTabByClass((Class<? extends SubPreferenceSetting>) preferenceClass);
580 }
581 p.setVisible(true);
582 });
583 add(pref);
584 }
585
586 // show the sticky button
587 JButton sticky = new JButton(ImageProvider.get("misc", "sticky"));
588 sticky.setToolTipText(tr("Undock the panel"));
589 sticky.setBorder(BorderFactory.createEmptyBorder());
590 sticky.addActionListener(e -> {
591 detach();
592 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
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(e -> {
601 hideDialog();
602 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
603 hideNotify();
604 });
605 add(close);
606 setToolTipText(tr("Click to minimize/maximize the panel content"));
607 setTitle(toggleDialogName);
608 }
609
610 public void setTitle(String title) {
611 lblTitle.setText(title);
612 lblTitleWeak.repaint();
613 }
614
615 public String getTitle() {
616 return lblTitle.getText();
617 }
618
619 /**
620 * This is the popup menu used for the title bar.
621 */
622 public class DialogPopupMenu extends JPopupMenu {
623
624 /**
625 * Constructs a new {@code DialogPopupMenu}.
626 */
627 DialogPopupMenu() {
628 alwaysShown.setSelected(buttonHiding == ButtonHidingType.ALWAYS_SHOWN);
629 dynamic.setSelected(buttonHiding == ButtonHidingType.DYNAMIC);
630 alwaysHidden.setSelected(buttonHiding == ButtonHidingType.ALWAYS_HIDDEN);
631 ButtonGroup buttonHidingGroup = new ButtonGroup();
632 JMenu buttonHidingMenu = new JMenu(tr("Side buttons"));
633 for (JRadioButtonMenuItem rb : new JRadioButtonMenuItem[]{alwaysShown, dynamic, alwaysHidden}) {
634 buttonHidingGroup.add(rb);
635 buttonHidingMenu.add(rb);
636 }
637 add(buttonHidingMenu);
638 for (javax.swing.Action action: buttonActions) {
639 add(action);
640 }
641 }
642 }
643
644 /**
645 * Registers the mouse listeners.
646 * <p>
647 * Should be called once after this title was added to the dialog.
648 */
649 public final void registerMouseListener() {
650 popupMenu = new DialogPopupMenu();
651 addMouseListener(new MouseEventHandler());
652 }
653
654 class MouseEventHandler extends PopupMenuLauncher {
655 /**
656 * Constructs a new {@code MouseEventHandler}.
657 */
658 MouseEventHandler() {
659 super(popupMenu);
660 }
661
662 @Override
663 public void mouseClicked(MouseEvent e) {
664 if (SwingUtilities.isLeftMouseButton(e)) {
665 if (isCollapsed) {
666 expand();
667 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this);
668 } else {
669 collapse();
670 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null);
671 }
672 }
673 }
674 }
675 }
676
677 /**
678 * The dialog class used to display toggle dialogs in a detached window.
679 *
680 */
681 private class DetachedDialog extends JDialog {
682 DetachedDialog() {
683 super(GuiHelper.getFrameForComponent(Main.parent));
684 getContentPane().add(ToggleDialog.this);
685 addWindowListener(new WindowAdapter() {
686 @Override public void windowClosing(WindowEvent e) {
687 rememberGeometry();
688 getContentPane().removeAll();
689 dispose();
690 if (dockWhenClosingDetachedDlg()) {
691 dock();
692 if (isDialogInCollapsedView()) {
693 expand();
694 }
695 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this);
696 } else {
697 hideDialog();
698 hideNotify();
699 }
700 }
701 });
702 addComponentListener(new ComponentAdapter() {
703 @Override
704 public void componentMoved(ComponentEvent e) {
705 rememberGeometry();
706 }
707
708 @Override
709 public void componentResized(ComponentEvent e) {
710 rememberGeometry();
711 }
712 });
713
714 try {
715 new WindowGeometry(preferencePrefix+".geometry").applySafe(this);
716 } catch (WindowGeometryException e) {
717 Logging.debug(e);
718 ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize());
719 pack();
720 setLocationRelativeTo(Main.parent);
721 }
722 super.setTitle(titleBar.getTitle());
723 HelpUtil.setHelpContext(getRootPane(), helpTopic());
724 }
725
726 protected void rememberGeometry() {
727 if (detachedDialog != null && detachedDialog.isShowing()) {
728 new WindowGeometry(detachedDialog).remember(preferencePrefix+".geometry");
729 }
730 }
731 }
732
733 /**
734 * Replies the action to toggle the visible state of this toggle dialog
735 *
736 * @return the action to toggle the visible state of this toggle dialog
737 */
738 public AbstractAction getToggleAction() {
739 return toggleAction;
740 }
741
742 /**
743 * Replies the prefix for the preference settings of this dialog.
744 *
745 * @return the prefix for the preference settings of this dialog.
746 */
747 public String getPreferencePrefix() {
748 return preferencePrefix;
749 }
750
751 /**
752 * Sets the dialogsPanel managing all toggle dialogs.
753 * @param dialogsPanel The panel managing all toggle dialogs
754 */
755 public void setDialogsPanel(DialogsPanel dialogsPanel) {
756 this.dialogsPanel = dialogsPanel;
757 }
758
759 /**
760 * Replies the name of this toggle dialog
761 */
762 @Override
763 public String getName() {
764 return "toggleDialog." + preferencePrefix;
765 }
766
767 /**
768 * Sets the title.
769 * @param title The dialog's title
770 */
771 public void setTitle(String title) {
772 titleBar.setTitle(title);
773 if (detachedDialog != null) {
774 detachedDialog.setTitle(title);
775 }
776 }
777
778 protected void setIsShowing(boolean val) {
779 isShowing = val;
780 Config.getPref().putBoolean(preferencePrefix+".visible", val);
781 stateChanged();
782 }
783
784 protected void setIsDocked(boolean val) {
785 if (buttonsPanel != null) {
786 buttonsPanel.setVisible(!val || buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
787 }
788 isDocked = val;
789 Config.getPref().putBoolean(preferencePrefix+".docked", val);
790 stateChanged();
791 }
792
793 protected void setIsCollapsed(boolean val) {
794 isCollapsed = val;
795 Config.getPref().putBoolean(preferencePrefix+".minimized", val);
796 stateChanged();
797 }
798
799 protected void setIsButtonHiding(ButtonHidingType val) {
800 buttonHiding = val;
801 propButtonHiding.put(val);
802 refreshHidingButtons();
803 }
804
805 /**
806 * Returns the preferred height of this dialog.
807 * @return The preferred height if the toggle dialog is expanded
808 */
809 public int getPreferredHeight() {
810 return preferredHeight;
811 }
812
813 @Override
814 public String helpTopic() {
815 String help = getClass().getName();
816 help = help.substring(help.lastIndexOf('.')+1, help.length()-6);
817 return "Dialog/"+help;
818 }
819
820 @Override
821 public String toString() {
822 return name;
823 }
824
825 /**
826 * Determines if this dialog is showing either as docked or as detached dialog.
827 * @return {@code true} if this dialog is showing either as docked or as detached dialog
828 */
829 public boolean isDialogShowing() {
830 return isShowing;
831 }
832
833 /**
834 * Determines if this dialog is docked and expanded.
835 * @return {@code true} if this dialog is docked and expanded
836 */
837 public boolean isDialogInDefaultView() {
838 return isShowing && isDocked && (!isCollapsed);
839 }
840
841 /**
842 * Determines if this dialog is docked and collapsed.
843 * @return {@code true} if this dialog is docked and collapsed
844 */
845 public boolean isDialogInCollapsedView() {
846 return isShowing && isDocked && isCollapsed;
847 }
848
849 /**
850 * Sets the button from the button list that is used to display this dialog.
851 * <p>
852 * Note: This is ignored by the {@link ToggleDialog} for now.
853 * @param button The button for this dialog.
854 */
855 public void setButton(JToggleButton button) {
856 this.button = button;
857 }
858
859 /**
860 * Gets the button from the button list that is used to display this dialog.
861 * @return button The button for this dialog.
862 */
863 public JToggleButton getButton() {
864 return button;
865 }
866
867 /*
868 * The following methods are intended to be overridden, in order to customize
869 * the toggle dialog behavior.
870 */
871
872 /**
873 * Returns the default size of the detached dialog.
874 * Override this method to customize the initial dialog size.
875 * @return the default size of the detached dialog
876 */
877 protected Dimension getDefaultDetachedSize() {
878 return new Dimension(dialogsPanel.getWidth(), preferredHeight);
879 }
880
881 /**
882 * Do something when the toggleButton is pressed.
883 */
884 protected void toggleButtonHook() {
885 // Do nothing
886 }
887
888 protected boolean dockWhenClosingDetachedDlg() {
889 return true;
890 }
891
892 /**
893 * primitive stateChangedListener for subclasses
894 */
895 protected void stateChanged() {
896 // Do nothing
897 }
898
899 /**
900 * Create a component with the given layout for this component.
901 * @param data The content to be displayed
902 * @param scroll <code>true</code> if it should be wrapped in a {@link JScrollPane}
903 * @param buttons The buttons to add.
904 * @return The component.
905 */
906 protected Component createLayout(Component data, boolean scroll, Collection<SideButton> buttons) {
907 return createLayout(data, scroll, buttons, (Collection<SideButton>[]) null);
908 }
909
910 @SafeVarargs
911 protected final Component createLayout(Component data, boolean scroll, Collection<SideButton> firstButtons,
912 Collection<SideButton>... nextButtons) {
913 if (scroll) {
914 JScrollPane sp = new JScrollPane(data);
915 if (!(data instanceof Scrollable)) {
916 GuiHelper.setDefaultIncrement(sp);
917 }
918 data = sp;
919 }
920 LinkedList<Collection<SideButton>> buttons = new LinkedList<>();
921 buttons.addFirst(firstButtons);
922 if (nextButtons != null) {
923 buttons.addAll(Arrays.asList(nextButtons));
924 }
925 add(data, BorderLayout.CENTER);
926 if (!buttons.isEmpty() && buttons.get(0) != null && !buttons.get(0).isEmpty()) {
927 buttonsPanel = new JPanel(new GridLayout(buttons.size(), 1));
928 for (Collection<SideButton> buttonRow : buttons) {
929 if (buttonRow == null) {
930 continue;
931 }
932 final JPanel buttonRowPanel = new JPanel(Config.getPref().getBoolean("dialog.align.left", false)
933 ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1, buttonRow.size()));
934 buttonsPanel.add(buttonRowPanel);
935 for (SideButton button : buttonRow) {
936 buttonRowPanel.add(button);
937 javax.swing.Action action = button.getAction();
938 if (action != null) {
939 buttonActions.add(action);
940 } else {
941 Logging.warn("Button " + button + " doesn't have action defined");
942 Logging.error(new Exception());
943 }
944 }
945 }
946 add(buttonsPanel, BorderLayout.SOUTH);
947 dynamicButtonsPropertyChanged();
948 } else {
949 titleBar.buttonsHide.setVisible(false);
950 }
951
952 // Register title bar mouse listener only after buttonActions has been initialized to have a complete popup menu
953 titleBar.registerMouseListener();
954
955 return data;
956 }
957
958 @Override
959 public void eventDispatched(AWTEvent event) {
960 if (event instanceof MouseEvent && isShowing() && !isCollapsed && isDocked && buttonHiding == ButtonHidingType.DYNAMIC
961 && buttonsPanel != null) {
962 Rectangle b = this.getBounds();
963 b.setLocation(getLocationOnScreen());
964 if (b.contains(((MouseEvent) event).getLocationOnScreen())) {
965 if (!buttonsPanel.isVisible()) {
966 buttonsPanel.setVisible(true);
967 }
968 } else if (buttonsPanel.isVisible()) {
969 buttonsPanel.setVisible(false);
970 }
971 }
972 }
973
974 @Override
975 public void preferenceChanged(PreferenceChangeEvent e) {
976 if (e.getKey().equals(PROP_DYNAMIC_BUTTONS.getKey())) {
977 dynamicButtonsPropertyChanged();
978 }
979 }
980
981 private void dynamicButtonsPropertyChanged() {
982 boolean propEnabled = PROP_DYNAMIC_BUTTONS.get();
983 try {
984 if (propEnabled) {
985 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK);
986 } else {
987 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
988 }
989 } catch (SecurityException e) {
990 Logging.log(Logging.LEVEL_ERROR, "Unable to add/remove AWT event listener", e);
991 }
992 titleBar.buttonsHide.setVisible(propEnabled);
993 refreshHidingButtons();
994 }
995
996 private void refreshHidingButtons() {
997 titleBar.buttonsHide.setIcon(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN
998 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow"));
999 titleBar.buttonsHide.setEnabled(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN);
1000 if (buttonsPanel != null) {
1001 buttonsPanel.setVisible(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN || !isDocked);
1002 }
1003 stateChanged();
1004 }
1005}
Note: See TracBrowser for help on using the repository browser.