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

Last change on this file since 12542 was 11885, checked in by Don-vip, 7 years ago

add basic unit tests for MergeLayerAction + improve javadoc, code cleanup

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