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