/* * Bibliothek - DockingFrames * Library built on Java/Swing, allows the user to "drag and drop" * panels containing any Swing-Component the developer likes to add. * * Copyright (C) 2009 Benjamin Sigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.common.action; import java.awt.Component; import java.awt.Point; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import bibliothek.gui.DockController; import bibliothek.gui.Dockable; import bibliothek.gui.dock.action.ActionType; import bibliothek.gui.dock.action.view.ActionViewConverter; import bibliothek.gui.dock.action.view.ViewTarget; import bibliothek.gui.dock.common.action.core.CommonSimpleButtonAction; import bibliothek.gui.dock.common.action.panel.DialogWindow; import bibliothek.gui.dock.common.action.panel.MenuWindow; import bibliothek.gui.dock.common.action.panel.PanelPopupWindow; import bibliothek.gui.dock.common.action.panel.PanelPopupWindowListener; import bibliothek.gui.dock.common.intern.action.CDecorateableAction; import bibliothek.gui.dock.title.DockTitle; import bibliothek.gui.dock.title.DockTitle.Orientation; /** * This action shows some kind of popup (for example a {@link JDialog}) filled * with any content the client likes. This action is intended to be shown * as button in a title, but can also be used as menu item in a menu.<br> * Clients may override the various <code>onXYZ</code>-methods and create * and show their custom popup. In such a case they should call {@link #openPopup(PanelPopupWindow)} * to ensure that only one window is open at a time.<br> * As long as the user works on the popup-window it does not close automatically, * clients can call {@link #closePopup()} to explicitly close it. The * window closes automatically if it loses the focus, clients can call * {@link #setCloseOnFocusLost(boolean)} to change that behavior. * <br> * <b>Note:</b> this action does not support being child of a drop down menu * @author Benjamin Sigg */ public class CPanelPopup extends CDecorateableAction<CPanelPopup.PanelPopup>{ /** the kind of action this class represents */ public static final ActionType<CPanelPopup.PanelPopup> PANEL_POPUP = new ActionType<PanelPopup>( "panel popup" ); /** * Tells how a {@link CPanelPopup} behaves if it is a child * of a menu. * @author Benjamin Sigg */ public static enum MenuBehavior{ /** the action remains invisible */ HIDE, /** the action pushes the custom component on a {@link JMenu} */ SUBMENU, /** the action shows an undecorated dialog if triggered */ UNDECORATED_DIALOG, /** the action shows a decorated dialog if triggered */ DECORATED_DIALOG } /** * When the popup should show up if the action is displayed as button. * @author Benjamin Sigg */ public static enum ButtonBehavior{ /** the popup shows up if the mouse is pressed */ OPEN_ON_PRESS, /** the popup shows up if the mouse is released */ OPEN_ON_CLICK } /** how to handle the case where this action is child of a menu */ private MenuBehavior menu = MenuBehavior.UNDECORATED_DIALOG; /** when to show the popup */ private ButtonBehavior button = ButtonBehavior.OPEN_ON_CLICK; /** the content of this {@link CPanelPopup} */ private JComponent content; /** current open window */ private PanelPopupWindow window; /** whether the window is closed automatically if focus is lost */ private boolean closeOnFocusLost = true; /** a listener to {@link #window} */ private PanelPopupWindowListener listener = new PanelPopupWindowListener(){ public void closed( PanelPopupWindow window ){ window.removeListener( listener ); window = null; } }; /** * Creates a new action. */ public CPanelPopup(){ super( null ); init( new PanelPopup() ); } /** * Sets the component that is shown on a popup dialog/menu/window... by * this {@link CPanelPopup}. * @param content the content, may be <code>null</code> */ public void setContent( JComponent content ){ this.content = content; } /** * Gets the contents of this action. * @return the contents */ public JComponent getContent(){ return content; } /** * Tells this action how to behave if it is in a menu. This may not have an effect * if the action already is part of a menu. * @param menu the behavior, not <code>null</code> */ public void setMenuBehavior( MenuBehavior menu ){ if( menu == null ) throw new IllegalArgumentException( "menu must not be null" ); this.menu = menu; } /** * Tells how this action behaves if in a menu. * @return the behavior, not <code>null</code> */ public MenuBehavior getMenuBehavior(){ return menu; } /** * Tells this action how to handle buttons. * @param button when to open a popup */ public void setButtonBehavior( ButtonBehavior button ){ if( button == null ) throw new IllegalArgumentException( "button must not be null" ); this.button = button; } /** * Tells how this action behaves if displayed as button. * @return the behavior, not <code>null</code> */ public ButtonBehavior getButtonBehavior(){ return button; } /** * Whether the window should be closed if focus is lost. * @param closeOnFocusLost <code>true</code> if it should close automatically */ public void setCloseOnFocusLost( boolean closeOnFocusLost ){ this.closeOnFocusLost = closeOnFocusLost; } /** * Tells whether the window is automatically closed if the focus is lost. * @return whether the popup is closed automatically */ public boolean isCloseOnFocusLost(){ return closeOnFocusLost; } /** * Informs this {@link CPanelPopup} that its content is shown and * allows this to handle the closing event. * @param window the window * @throws IllegalArgumentException if {@link PanelPopupWindow#isOpen()} * return <code>false</code> */ public void openPopup( PanelPopupWindow window ){ if( !window.isOpen() ){ throw new IllegalArgumentException( "window is not open" ); } closePopup(); this.window = window; this.window.addListener( listener ); } /** * Makes the current popup invisible. */ public void closePopup(){ if( window != null ){ window.close(); } } /** * Tells whether the content of this action is currently being showed. * @return <code>true</code> if the content is visible */ public boolean isOpen(){ return window != null && window.isOpen(); } /** * Called if the mouse is pressed on the button <code>item</code> of * of a {@link DockTitle} which has orientation <code>orientation</code>. * @param dockable the element for which this panel is shown * @param item the pressed component * @param orientation the orientation of the title */ protected void onMousePressed( Dockable dockable, JComponent item, Orientation orientation ){ if( getButtonBehavior() == ButtonBehavior.OPEN_ON_PRESS ){ openDialog( dockable, item, orientation ); } } /** * Called if the mouse is released of the button <code>item</code> of * of a {@link DockTitle} which has orientation <code>orientation</code>. * @param dockable the element for which this panel is shown * @param item the released component * @param orientation the orientation of the title */ protected void onMouseReleased( Dockable dockable, JComponent item, Orientation orientation ){ if( getButtonBehavior() == ButtonBehavior.OPEN_ON_CLICK ){ openDialog( dockable, item, orientation ); } } /** * Called if the button <code>item</code> of a {@link DockTitle} which has * orientation <code>orientation</code> was triggered. * @param dockable the element for which this panel is shown * @param item the triggered button * @param orientation the orientation of the title */ protected void onTrigger( Dockable dockable, JComponent item, Orientation orientation ){ openDialog( dockable, item, orientation ); } /** * Opens a new undecorated dialog below or aside of <code>item</code>. This method * does nothing if {@link #isOpen()} return <code>true</code>. * @param dockable the element for which this panel is shown * @param item the owner of the new dialog * @param orientation the orientation of the title which shows <code>item</code> */ protected void openDialog( Dockable dockable, final JComponent item, Orientation orientation ){ if( isOpen() || content == null ) return; final Point location = new Point(); if( orientation.isHorizontal() ){ location.y = item.getHeight(); } else{ location.x = item.getWidth(); } SwingUtilities.convertPointToScreen( location, item ); executeOneDockableHasFocus( dockable, new Runnable() { public void run() { DialogWindow window = createDialogWindow( item ); window.setUndecorated( true ); window.setContent( getContent() ); window.open( location.x, location.y ); openPopup( window ); } }); } /** * Called if the menu-item representing this action has been * hit. * @param dockable the source of the event */ protected void onMenuItemTrigger( final Dockable dockable ){ if( content == null ) return; closePopup(); executeOneDockableHasFocus( dockable, new Runnable() { public void run() { DialogWindow window = createDialogWindow( dockable.getComponent() ); window.setUndecorated( getMenuBehavior() == MenuBehavior.UNDECORATED_DIALOG ); window.setContent( getContent() ); window.open( dockable.getComponent() ); openPopup( window ); } }); } /** * Creates a new window which will be used as popup for this {@link CPanelPopup}. * @param owner the owner of the window * @return the new window, not <code>null</code> */ protected DialogWindow createDialogWindow( Component owner ){ return new DialogWindow( owner, this ); } /** * Called if a menu is opening a submenu in which {@link #getContent() the content} * is to be shown. * @param menu the new parent of the content */ protected void onMenuTrigger( JPopupMenu menu ){ if( content == null ) return; menu.add( content ); MenuWindow window = createMenuWindow( menu ); openPopup( window ); } /** * Creates a new window which will be used as popup for this {@link CPanelPopup}. * @param menu the owner of the window * @return the new window, not <code>null</code> */ protected MenuWindow createMenuWindow( JPopupMenu menu ){ return new MenuWindow( menu ); } /** * Calls <code>run</code> once the owning {@link Dockable} of this action has the focus * @param dockable the element for which this panel is shown * @param run some piece of code to run, usually it will open the popup-dialog created by {@link #createDialogWindow(Component)}. * Should be called by the <code>EDT</code>. */ protected void executeOneDockableHasFocus( Dockable dockable, Runnable run ){ DockController controller = dockable.getController(); if( controller != null ){ controller.getFocusController().onFocusRequestCompletion( run ); } else { run.run(); } } /** * A custom action shows some dialog or window when triggered * @author Benjamin Sigg * */ public class PanelPopup extends CommonSimpleButtonAction{ /** * Creates a new action */ public PanelPopup(){ super( CPanelPopup.this ); } public <V> V createView( ViewTarget<V> target, ActionViewConverter converter, Dockable dockable ){ return converter.createView( PANEL_POPUP, this, target, dockable ); } public boolean trigger( Dockable dockable ){ return false; } /** * Gets the {@link CAction} that is represented by this action. * @return the action, never <code>null</code> */ public CPanelPopup getAction(){ return CPanelPopup.this; } /** * Called if the mouse is pressed on the button <code>item</code> of * of a {@link DockTitle} which has orientation <code>orientation</code>. * @param dockable the element for which this panel is shown * @param item the pressed component * @param orientation the orientation of the title */ public void onMousePressed( Dockable dockable, JComponent item, Orientation orientation ){ CPanelPopup.this.onMousePressed( dockable, item, orientation ); } /** * Called if the mouse is released of the button <code>item</code> of * of a {@link DockTitle} which has orientation <code>orientation</code>. * @param dockable the element for which this panel is shown * @param item the released component * @param orientation the orientation of the title */ public void onMouseReleased( Dockable dockable, JComponent item, Orientation orientation ){ CPanelPopup.this.onMouseReleased( dockable, item, orientation ); } /** * Called if the button <code>item</code> of a {@link DockTitle} which has * orientation <code>orientation</code> was triggered. * @param dockable the element for which this panel is shown * @param item the triggered button * @param orientation the orientation of the title */ public void onTrigger( Dockable dockable, JComponent item, Orientation orientation ){ CPanelPopup.this.onTrigger( dockable, item, orientation ); } /** * Called if the menu-item representing this action has been * hit. * @param dockable the source of the event */ public void onMenuItemTrigger( Dockable dockable ){ CPanelPopup.this.onMenuItemTrigger( dockable ); } /** * Called if a menu is opening a submenu in which {@link #getContent() the content} * is to be shown. * @param menu the new parent of the content */ public void onMenuTrigger( JPopupMenu menu ){ CPanelPopup.this.onMenuTrigger( menu ); } } }