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

Last change on this file since 14589 was 14589, checked in by GerdP, 5 years ago

#see #17040 implement Destroyable in ToggleDialog.TitleBar and use it in ToggleDialog.destroy()

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