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

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

fix #18514 - display changeset toggle dialog entry in the window menu only in expert mode

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