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

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

fix #13037 - Small fixes for unit tests (patch by michael2402) - gsoc-core

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