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

Last change on this file was 18871, checked in by taylor.smock, 6 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

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