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