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