/* -*- mode: JDE; c-basic-offset: 2; indent-tabs-mode: nil -*- * * $Id: DetachableRootPane.java,v 1.6 2005/06/20 22:40:48 ljnelson Exp $ * * Copyright (c) 2003, 2005 Laird Jarrett Nelson. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * The original copy of this license is available at * http://www.opensource.org/license/mit-license.html. */ package ellington; import java.awt.Component; import java.awt.Container; import java.awt.GraphicsConfiguration; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JDesktopPane; import javax.swing.JFrame; import javax.swing.JComponent; import javax.swing.JInternalFrame; import javax.swing.JRootPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; /** * A {@link JRootPane} that is capable of being detached from a {@link * JInternalFrame} into a self-created {@link JFrame}. The visual effect is one * of "popping" the {@link JInternalFrame} "off" of its enclosing {@link * JDesktopPane} and onto the user's desktop. The {@link JFrame} it detaches * "into" as a part of this process, when closed, appears to "sink" back into * the {@link JDesktopPane} as a {@link JInternalFrame}. The whole effect is * considerably less complicated than this description might lead you to * believe. * * @author Laird Nelson * @version $Revision: 1.6 $ $Date: 2005/06/20 22:40:48 $ * @since May 27, 2005 */ public class DetachableRootPane extends JRootPane { /** * The name of a {@linkplain JComponent#getClientProperty(Object) client * property} that indicates that the {@link JInternalFrame} this {@link * DetachableRootPane} is "leaving" was resizable. */ private static final String INTERNAL_FRAME_RESIZABLE = "internalFrameResizable"; /** * The name of a {@linkplain JComponent#getClientProperty(Object) client * property} that indicates that the {@link JInternalFrame} this {@link * DetachableRootPane} is "leaving" was closable. */ private static final String INTERNAL_FRAME_CLOSABLE = "internalFrameClosable"; /** * The name of a {@linkplain JComponent#getClientProperty(Object) client * property} that indicates that the {@link JInternalFrame} this {@link * DetachableRootPane} is "leaving" was maximizable. */ private static final String INTERNAL_FRAME_MAXIMIZABLE = "internalFrameMaximizable"; /** * The name of a {@linkplain JComponent#getClientProperty(Object) client * property} that indicates that the {@link JInternalFrame} this {@link * DetachableRootPane} is "leaving" was iconifiable. */ private static final String INTERNAL_FRAME_ICONIFIABLE = "internalFrameIconifiable"; /** * A {@link PropertyChangeListener} that monitors a {@link JInternalFrame} for * changes in its structural resizability and sets the {@link * #parentIsResizable} field appropriately. This field is assigned a * non-null value in the {@link #addNotify()} method, and is * reset to null in most cases by the {@link #removeNotify()} * method. */ private PropertyChangeListener pcl; /** * Indicates whether the "outgoing" parent of this {@link * DetachableRootPane}—usually a {@link JInternalFrame}—was * structurally resizable. */ private boolean parentIsResizable; /** * Creates a {@link DetachableRootPane}. Typically this will be called in an * overridden {@link JInternalFrame#createRootPane()} method. As a side * effect, installs an {@link AbstractAction} named "detach" in * the {@linkplain JComponent#getActionMap() associated * ActionMap} and associates it with the keystroke * "F2" (in the default {@link InputMap}). */ public DetachableRootPane() { super(); final ActionMap actionMap = this.getActionMap(); assert actionMap != null; actionMap.put("detach", new AbstractAction("Detach") { public final void actionPerformed(final ActionEvent event) { detach(); } }); final InputMap inputMap = this.getInputMap(); assert inputMap != null; inputMap.put(KeyStroke.getKeyStroke("F2"), "detach"); } /** * Calls the {@linkplain Component#addNotify() superclass implementation} and * then ensures that if the parent this {@link DetachableRootPane} was just * added to is either a {@link JInternalFrame} or a {@link JFrame} its * resizability is monitored via a {@link PropertyChangeListener}. * * @see #removeNotify() */ public void addNotify() { super.addNotify(); final Container parent = this.getParent(); boolean addPropertyChangeListener = true; if (parent instanceof JInternalFrame) { this.parentIsResizable = ((JInternalFrame)parent).isResizable(); } else if (parent instanceof JFrame) { this.parentIsResizable = ((JFrame)parent).isResizable(); } else { addPropertyChangeListener = false; } if (addPropertyChangeListener) { this.pcl = new PropertyChangeListener() { public final void propertyChange(final PropertyChangeEvent event) { // Monitor the "resizable" property of the parent for changes. if (event != null && "resizable".equals(event.getPropertyName())) { final Boolean value = (Boolean)event.getNewValue(); parentIsResizable = value != null && value.booleanValue(); } } }; parent.addPropertyChangeListener("resizable", this.pcl); } } /** * Ensures that the {@link PropertyChangeListener} that might have been * installed on the {@linkplain Component#getParent() parent of this * DetachableRootPane} is removed. */ public void removeNotify() { if (this.pcl != null) { final Container parent = this.getParent(); assert parent != null; parent.removePropertyChangeListener("resizable", this.pcl); } super.removeNotify(); } /** * Detaches this {@link DetachableRootPane} from its current parent * frame—provided that parent is either a {@link JInternalFrame} or a * {@link JFrame}—and installs it in a new frame of the appropriate * type. * *

If the current parent of this {@link DetachableRootPane} is a * non-iconified {@link JInternalFrame}, then the following visual behavior * occurs: *

* *

If the current parent of this {@link DetachableRootPane} is a * non-iconified {@link JFrame}, then the following visual behavior occurs: *

*/ public void detach() { final Component parent = this.getParent(); if (parent instanceof JInternalFrame) { // Leaving JInternalFrame; going into JFrame final JInternalFrame internalFrame = (JInternalFrame)parent; final Rectangle bounds = internalFrame.getBounds(); assert bounds != null; final Point point = internalFrame.getLocationOnScreen(); assert point != null; bounds.x = point.x; bounds.y = point.y; final JDesktopPane pane = internalFrame.getDesktopPane(); if (pane != null) { pane.remove(internalFrame); this.putClientProperty("desktop", pane); } // Store the structural state of the "outgoing" JInternalFrame. This is // so that we can create another JInternalFrame, if necessary, that will // look and behave like the one that we are currently detaching from. this.putClientProperty(INTERNAL_FRAME_RESIZABLE, new Boolean(this.parentIsResizable)); this.putClientProperty(INTERNAL_FRAME_CLOSABLE, Boolean.valueOf(internalFrame.isClosable())); this.putClientProperty(INTERNAL_FRAME_MAXIMIZABLE, Boolean.valueOf(internalFrame.isMaximizable())); this.putClientProperty(INTERNAL_FRAME_ICONIFIABLE, Boolean.valueOf(internalFrame.isIconifiable())); internalFrame.setVisible(false); // Create a new JFrame that has us as its root pane, and that calls this // method (detach()) when closed. final JFrame frame = new JFrame(internalFrame.getTitle()) { protected final void frameInit() { super.frameInit(); // Ensure that however the window is closed, it actually causes this // detach() method to be fired instead. this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); this.addWindowListener(new WindowAdapter() { public final void windowClosing(final WindowEvent event) { detach(); } }); } protected final JRootPane createRootPane() { // Ensure that this DetachableRootPane instance is the root pane for // any JFrames it creates. return DetachableRootPane.this; } }; frame.setBounds(bounds); frame.setVisible(true); } else if (parent instanceof JFrame) { // Leaving JFrame; going into JInternalFrame final JFrame frame = (JFrame)parent; final String title = frame.getTitle(); final Rectangle bounds = frame.getBounds(); final JDesktopPane pane = (JDesktopPane)this.getClientProperty("desktop"); frame.dispose(); if (pane != null) { final JInternalFrame internalFrame = new JInternalFrame(title, ((Boolean)this.getClientProperty(INTERNAL_FRAME_RESIZABLE)).booleanValue(), ((Boolean)this.getClientProperty(INTERNAL_FRAME_CLOSABLE)).booleanValue(), ((Boolean)this.getClientProperty(INTERNAL_FRAME_MAXIMIZABLE)).booleanValue(), ((Boolean)this.getClientProperty(INTERNAL_FRAME_ICONIFIABLE)).booleanValue()) { protected final JRootPane createRootPane() { return DetachableRootPane.this; } }; pane.add(internalFrame); final Point point = new Point(bounds.x, bounds.y); SwingUtilities.convertPointFromScreen(point, pane); bounds.x = point.x; bounds.y = point.y; internalFrame.setBounds(bounds); internalFrame.setVisible(true); } } } /** * Convenience method that creates a {@link JFrame} with a {@link * DetachableRootPane} instance as {@linkplain JFrame#getRootPane() its root * pane}. This method never returns null. * * @return a new {@link JFrame}; never null */ public static JFrame createJFrame() { return new JFrame() { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Convenience method that creates a {@link JFrame} with a {@link * DetachableRootPane} instance as {@linkplain JFrame#getRootPane() its root * pane}. This method never returns null. * * @param configuration * the supplied {@link GraphicsConfiguration}; supplied to the * new {@link JFrame}'s {@linkplain * JFrame#JFrame(GraphicsConfiguration) constructor}; may be * null * @return a new {@link JFrame}; never null */ public static JFrame createJFrame(final GraphicsConfiguration configuration) { return new JFrame(configuration) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Convenience method that creates a {@link JFrame} with a {@link * DetachableRootPane} instance as {@linkplain JFrame#getRootPane() its root * pane}. This method never returns null. * * @param title * the supplied {@linkplain JFrame#getTitle() title}; supplied * to the new {@link JFrame}'s {@linkplain JFrame#JFrame(String) * constructor}; may be null * @return a new {@link JFrame}; never null */ public static JFrame createJFrame(final String title) { return new JFrame(title) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Convenience method that creates a {@link JFrame} with a {@link * DetachableRootPane} instance as {@linkplain JFrame#getRootPane() its root * pane}. This method never returns null. * * @param title * the supplied {@linkplain JFrame#getTitle() title}; supplied * to the new {@link JFrame}'s {@linkplain JFrame#JFrame(String, * GraphicsConfiguration) constructor}; may be null * @param configuration * the supplied {@link GraphicsConfiguration}; supplied to the * new {@link JFrame}'s {@linkplain JFrame#JFrame(String, * GraphicsConfiguration) constructor}; may be null * @return a new {@link JFrame}; never null */ public static JFrame createJFrame(final String title, final GraphicsConfiguration configuration) { return new JFrame(title, configuration) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame() { return new JInternalFrame() { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @param title * the supplied {@linkplain JInternalFrame#getTitle() title}; * supplied to the new {@link JInternalFrame}'s {@linkplain * JInternalFrame#JInternalFrame(String) constructor}; may be * null * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame(final String title) { return new JInternalFrame(title) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @param title * the supplied {@linkplain JInternalFrame#getTitle() title}; * supplied to the new {@link JInternalFrame}'s {@linkplain * JInternalFrame#JInternalFrame(String) constructor}; may be * null * @param resizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isResizable() structurally * resizable} * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame(final String title, final boolean resizable) { return new JInternalFrame(title, resizable) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @param title * the supplied {@linkplain JInternalFrame#getTitle() title}; * supplied to the new {@link JInternalFrame}'s {@linkplain * JInternalFrame#JInternalFrame(String) constructor}; may be * null * @param resizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isResizable() structurally * resizable} * @param closable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isClosable() closable} * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame(final String title, final boolean resizable, final boolean closable) { return new JInternalFrame(title, resizable, closable) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @param title * the supplied {@linkplain JInternalFrame#getTitle() title}; * supplied to the new {@link JInternalFrame}'s {@linkplain * JInternalFrame#JInternalFrame(String) constructor}; may be * null * @param resizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isResizable() structurally * resizable} * @param closable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isClosable() closable} * @param maximizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isMaximizable() maximizable} * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame(final String title, final boolean resizable, final boolean closable, final boolean maximizable) { return new JInternalFrame(title, resizable, closable, maximizable) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } /** * Creates a new {@link JInternalFrame} with a {@link DetachableRootPane} * instance as {@linkplain JFrame#getRootPane() its root pane}. This method * never returns null. * * @param title * the supplied {@linkplain JInternalFrame#getTitle() title}; * supplied to the new {@link JInternalFrame}'s {@linkplain * JInternalFrame#JInternalFrame(String) constructor}; may be * null * @param resizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isResizable() structurally * resizable} * @param closable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isClosable() closable} * @param maximizable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isMaximizable() maximizable} * @param iconifiable * whether or not the new {@link JInternalFrame} is supposed to * be {@linkplain JInternalFrame#isIconifiable() iconifiable} * @return a new {@link JInternalFrame}; never null */ public static JInternalFrame createJInternalFrame(final String title, final boolean resizable, final boolean closable, final boolean maximizable, final boolean iconifiable) { return new JInternalFrame(title, resizable, closable, maximizable, iconifiable) { protected final JRootPane createRootPane() { return new DetachableRootPane(); } }; } }