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

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

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

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