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

Last change on this file since 12841 was 12841, checked in by bastiK, 7 years ago

see #15229 - fix deprecations caused by [12840]

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