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