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 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 lblTitle_weak.repaint(); 600 } 601 602 public String getTitle() { 603 return lblTitle.getText(); 604 } 605 606 public class DialogPopupMenu extends JPopupMenu { 607 private final ButtonGroup buttonHidingGroup = new ButtonGroup(); 608 private final JMenu buttonHidingMenu = new JMenu(tr("Side buttons")); 609 610 public DialogPopupMenu() { 611 alwaysShown.setSelected(buttonHiding == ButtonHidingType.ALWAYS_SHOWN); 612 dynamic.setSelected(buttonHiding == ButtonHidingType.DYNAMIC); 613 alwaysHidden.setSelected(buttonHiding == ButtonHidingType.ALWAYS_HIDDEN); 614 for (JRadioButtonMenuItem rb : new JRadioButtonMenuItem[]{alwaysShown, dynamic, alwaysHidden}) { 615 buttonHidingGroup.add(rb); 616 buttonHidingMenu.add(rb); 617 } 618 add(buttonHidingMenu); 619 for (javax.swing.Action action: buttonActions) { 620 add(action); 621 } 622 } 623 } 624 625 public final void registerMouseListener() { 626 popupMenu = new DialogPopupMenu(); 627 addMouseListener(new MouseEventHandler()); 628 } 629 630 class MouseEventHandler extends PopupMenuLauncher { 631 public MouseEventHandler() { 632 super(popupMenu); 633 } 634 @Override 635 public void mouseClicked(MouseEvent e) { 636 if (SwingUtilities.isLeftMouseButton(e)) { 637 if (isCollapsed) { 638 expand(); 639 dialogsPanel.reconstruct(Action.COLLAPSED_TO_DEFAULT, ToggleDialog.this); 640 } else { 641 collapse(); 642 dialogsPanel.reconstruct(Action.ELEMENT_SHRINKS, null); 643 } 644 } 645 } 646 } 647 } 648 649 /** 650 * The dialog class used to display toggle dialogs in a detached window. 651 * 652 */ 653 private class DetachedDialog extends JDialog { 654 public DetachedDialog() { 655 super(JOptionPane.getFrameForComponent(Main.parent)); 656 getContentPane().add(ToggleDialog.this); 657 addWindowListener(new WindowAdapter(){ 658 @Override public void windowClosing(WindowEvent e) { 659 rememberGeometry(); 660 getContentPane().removeAll(); 661 dispose(); 662 if (dockWhenClosingDetachedDlg()) { 663 dock(); 664 if (isDialogInCollapsedView()) { 665 expand(); 666 } 667 dialogsPanel.reconstruct(Action.INVISIBLE_TO_DEFAULT, ToggleDialog.this); 668 } else { 669 hideDialog(); 670 hideNotify(); 671 } 672 } 673 }); 674 addComponentListener(new ComponentAdapter() { 675 @Override public void componentMoved(ComponentEvent e) { 676 rememberGeometry(); 677 } 678 @Override public void componentResized(ComponentEvent e) { 679 rememberGeometry(); 680 } 681 }); 682 683 try { 684 new WindowGeometry(preferencePrefix+".geometry").applySafe(this); 685 } catch (WindowGeometryException e) { 686 ToggleDialog.this.setPreferredSize(ToggleDialog.this.getDefaultDetachedSize()); 687 pack(); 688 setLocationRelativeTo(Main.parent); 689 } 690 setTitle(titleBar.getTitle()); 691 HelpUtil.setHelpContext(getRootPane(), helpTopic()); 692 } 693 694 protected void rememberGeometry() { 695 if (detachedDialog != null) { 696 new WindowGeometry(detachedDialog).remember(preferencePrefix+".geometry"); 697 } 698 } 699 } 700 701 /** 702 * Replies the action to toggle the visible state of this toggle dialog 703 * 704 * @return the action to toggle the visible state of this toggle dialog 705 */ 706 public AbstractAction getToggleAction() { 707 return toggleAction; 708 } 709 710 /** 711 * Replies the prefix for the preference settings of this dialog. 712 * 713 * @return the prefix for the preference settings of this dialog. 714 */ 715 public String getPreferencePrefix() { 716 return preferencePrefix; 717 } 718 719 /** 720 * Sets the dialogsPanel managing all toggle dialogs. 721 * @param dialogsPanel The panel managing all toggle dialogs 722 */ 723 public void setDialogsPanel(DialogsPanel dialogsPanel) { 724 this.dialogsPanel = dialogsPanel; 725 } 726 727 /** 728 * Replies the name of this toggle dialog 729 */ 730 @Override 731 public String getName() { 732 return "toggleDialog." + preferencePrefix; 733 } 734 735 /** 736 * Sets the title. 737 * @param title The dialog's title 738 */ 739 public void setTitle(String title) { 740 titleBar.setTitle(title); 741 if (detachedDialog != null) { 742 detachedDialog.setTitle(title); 743 } 744 } 745 746 protected void setIsShowing(boolean val) { 747 isShowing = val; 748 Main.pref.put(preferencePrefix+".visible", val); 749 stateChanged(); 750 } 751 752 protected void setIsDocked(boolean val) { 753 if (buttonsPanel != null) { 754 buttonsPanel.setVisible(val ? buttonHiding != ButtonHidingType.ALWAYS_HIDDEN : true); 755 } 756 isDocked = val; 757 Main.pref.put(preferencePrefix+".docked", val); 758 stateChanged(); 759 } 760 761 protected void setIsCollapsed(boolean val) { 762 isCollapsed = val; 763 Main.pref.put(preferencePrefix+".minimized", val); 764 stateChanged(); 765 } 766 767 protected void setIsButtonHiding(ButtonHidingType val) { 768 buttonHiding = val; 769 PROP_BUTTON_HIDING.put(val); 770 refreshHidingButtons(); 771 } 772 773 /** 774 * Returns the preferred height of this dialog. 775 * @return The preferred height if the toggle dialog is expanded 776 */ 777 public int getPreferredHeight() { 778 return preferredHeight; 779 } 780 781 @Override 782 public String helpTopic() { 783 String help = getClass().getName(); 784 help = help.substring(help.lastIndexOf('.')+1, help.length()-6); 785 return "Dialog/"+help; 786 } 787 788 @Override 789 public String toString() { 790 return name; 791 } 792 793 /** 794 * Determines if this dialog is showing either as docked or as detached dialog. 795 * @return {@code true} if this dialog is showing either as docked or as detached dialog 796 */ 797 public boolean isDialogShowing() { 798 return isShowing; 799 } 800 801 /** 802 * Determines if this dialog is docked and expanded. 803 * @return {@code true} if this dialog is docked and expanded 804 */ 805 public boolean isDialogInDefaultView() { 806 return isShowing && isDocked && (! isCollapsed); 807 } 808 809 /** 810 * Determines if this dialog is docked and collapsed. 811 * @return {@code true} if this dialog is docked and collapsed 812 */ 813 public boolean isDialogInCollapsedView() { 814 return isShowing && isDocked && isCollapsed; 815 } 816 817 public void setButton(JToggleButton button) { 818 this.button = button; 819 } 820 821 public JToggleButton getButton() { 822 return button; 823 } 824 825 /*** 826 * The following methods are intended to be overridden, in order to customize 827 * the toggle dialog behavior. 828 **/ 829 830 /** 831 * Change the Geometry of the detached dialog to better fit the content. 832 */ 833 protected Rectangle getDetachedGeometry(Rectangle last) { 834 return last; 835 } 836 837 /** 838 * Default size of the detached dialog. 839 * Override this method to customize the initial dialog size. 840 */ 841 protected Dimension getDefaultDetachedSize() { 842 return new Dimension(dialogsPanel.getWidth(), preferredHeight); 843 } 844 845 /** 846 * Do something when the toggleButton is pressed. 847 */ 848 protected void toggleButtonHook() { 849 } 850 851 protected boolean dockWhenClosingDetachedDlg() { 852 return true; 853 } 854 855 /** 856 * primitive stateChangedListener for subclasses 857 */ 858 protected void stateChanged() { 859 } 860 861 protected Component createLayout(Component data, boolean scroll, Collection<SideButton> buttons) { 862 return createLayout(data, scroll, buttons, (Collection<SideButton>[]) null); 863 } 864 865 @SafeVarargs 866 protected final Component createLayout(Component data, boolean scroll, Collection<SideButton> firstButtons, Collection<SideButton>... nextButtons) { 867 if (scroll) { 868 data = new JScrollPane(data); 869 } 870 LinkedList<Collection<SideButton>> buttons = new LinkedList<>(); 871 buttons.addFirst(firstButtons); 872 if (nextButtons != null) { 873 buttons.addAll(Arrays.asList(nextButtons)); 874 } 875 add(data, BorderLayout.CENTER); 876 if (!buttons.isEmpty() && buttons.get(0) != null && !buttons.get(0).isEmpty()) { 877 buttonsPanel = new JPanel(new GridLayout(buttons.size(), 1)); 878 for (Collection<SideButton> buttonRow : buttons) { 879 if (buttonRow == null) { 880 continue; 881 } 882 final JPanel buttonRowPanel = new JPanel(Main.pref.getBoolean("dialog.align.left", false) 883 ? new FlowLayout(FlowLayout.LEFT) : new GridLayout(1, buttonRow.size())); 884 buttonsPanel.add(buttonRowPanel); 885 for (SideButton button : buttonRow) { 886 buttonRowPanel.add(button); 887 javax.swing.Action action = button.getAction(); 888 if (action != null) { 889 buttonActions.add(action); 890 } else { 891 Main.warn("Button " + button + " doesn't have action defined"); 892 Main.error(new Exception()); 893 } 894 } 895 } 896 add(buttonsPanel, BorderLayout.SOUTH); 897 dynamicButtonsPropertyChanged(); 898 } else { 899 titleBar.buttonsHide.setVisible(false); 900 } 901 902 // Register title bar mouse listener only after buttonActions has been initialized to have a complete popup menu 903 titleBar.registerMouseListener(); 904 905 return data; 906 } 907 908 @Override 909 public void eventDispatched(AWTEvent event) { 910 if(isShowing() && !isCollapsed && isDocked && buttonHiding == ButtonHidingType.DYNAMIC) { 911 if (buttonsPanel != null) { 912 Rectangle b = this.getBounds(); 913 b.setLocation(getLocationOnScreen()); 914 if (b.contains(((MouseEvent)event).getLocationOnScreen())) { 915 if(!buttonsPanel.isVisible()) { 916 buttonsPanel.setVisible(true); 917 } 918 } else if (buttonsPanel.isVisible()) { 919 buttonsPanel.setVisible(false); 920 } 921 } 922 } 923 } 924 925 @Override 926 public void preferenceChanged(PreferenceChangeEvent e) { 927 if (e.getKey().equals(PROP_DYNAMIC_BUTTONS.getKey())) { 928 dynamicButtonsPropertyChanged(); 929 } 930 } 931 932 private void dynamicButtonsPropertyChanged() { 933 boolean propEnabled = PROP_DYNAMIC_BUTTONS.get(); 934 if (propEnabled) { 935 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_MOTION_EVENT_MASK); 936 } else { 937 Toolkit.getDefaultToolkit().removeAWTEventListener(this); 938 } 939 titleBar.buttonsHide.setVisible(propEnabled); 940 refreshHidingButtons(); 941 } 942 943 private void refreshHidingButtons() { 944 titleBar.buttonsHide.setIcon(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN 945 ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow")); 946 titleBar.buttonsHide.setEnabled(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN); 947 if (buttonsPanel != null) { 948 buttonsPanel.setVisible(buttonHiding != ButtonHidingType.ALWAYS_HIDDEN || !isDocked); 949 } 950 stateChanged(); 951 } 952}