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

Last change on this file since 18066 was 18066, checked in by Don-vip, 3 years ago

fix #21134 - add help button to access toggle dialog help from its title bar

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