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

Last change on this file since 17318 was 17307, checked in by GerdP, 3 years ago

see #7548: Re-organize the preference dialog

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