/* * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.apple.laf; import java.awt.*; import java.awt.event.*; import java.beans.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.MouseInputAdapter; import javax.swing.plaf.*; import javax.swing.plaf.basic.BasicInternalFrameUI; import apple.laf.*; import apple.laf.JRSUIConstants.*; import com.apple.laf.AquaUtils.*; import com.apple.laf.AquaUtils.Painter; import sun.lwawt.macosx.CPlatformWindow; /** * From AquaInternalFrameUI * * InternalFrame implementation for Aqua LAF * * We want to inherit most of the inner classes, but not the base class, * so be very careful about subclassing so you know you get what you want * */ public class AquaInternalFrameUI extends BasicInternalFrameUI implements SwingConstants { protected static final String IS_PALETTE_PROPERTY = "JInternalFrame.isPalette"; private static final String FRAME_TYPE = "JInternalFrame.frameType"; private static final String NORMAL_FRAME = "normal"; private static final String PALETTE_FRAME = "palette"; private static final String OPTION_DIALOG = "optionDialog"; // Instance variables PropertyChangeListener fPropertyListener; protected Color fSelectedTextColor; protected Color fNotSelectedTextColor; AquaInternalFrameBorder fAquaBorder; // for button tracking boolean fMouseOverPressedButton; int fWhichButtonPressed = -1; boolean fRollover = false; boolean fDocumentEdited = false; // to indicate whether we should use the dirty document red dot. boolean fIsPallet; public int getWhichButtonPressed() { return fWhichButtonPressed; } public boolean getMouseOverPressedButton() { return fMouseOverPressedButton; } public boolean getRollover() { return fRollover; } // ComponentUI Interface Implementation methods public static ComponentUI createUI(final JComponent b) { return new AquaInternalFrameUI((JInternalFrame)b); } public AquaInternalFrameUI(final JInternalFrame b) { super(b); } /// Inherit (but be careful to check everything they call): public void installUI(final JComponent c) { // super.installUI(c); // Swing 1.1.1 has a bug in installUI - it doesn't check for null northPane frame = (JInternalFrame)c; frame.add(frame.getRootPane(), "Center"); installDefaults(); installListeners(); installComponents(); installKeyboardActions(); Object paletteProp = c.getClientProperty(IS_PALETTE_PROPERTY); if (paletteProp != null) { setPalette(((Boolean)paletteProp).booleanValue()); } else { paletteProp = c.getClientProperty(FRAME_TYPE); if (paletteProp != null) { setFrameType((String)paletteProp); } else { setFrameType(NORMAL_FRAME); } } // We only have a southPane, for grow box room, created in setFrameType frame.setMinimumSize(new Dimension(fIsPallet ? 120 : 150, fIsPallet ? 39 : 65)); frame.setOpaque(false); c.setBorder(new CompoundUIBorder(fIsPallet ? paletteWindowShadow.get() : documentWindowShadow.get(), c.getBorder())); } protected void installDefaults() { super.installDefaults(); fSelectedTextColor = UIManager.getColor("InternalFrame.activeTitleForeground"); fNotSelectedTextColor = UIManager.getColor("InternalFrame.inactiveTitleForeground"); } public void setSouthPane(final JComponent c) { if (southPane != null) { frame.remove(southPane); deinstallMouseHandlers(southPane); } if (c != null) { frame.add(c); installMouseHandlers(c); } southPane = c; } static final RecyclableSingleton closeIcon = new RecyclableSingleton() { protected Icon getInstance() { return new AquaInternalFrameButtonIcon(Widget.TITLE_BAR_CLOSE_BOX); } }; public static Icon exportCloseIcon() { return closeIcon.get(); } static final RecyclableSingleton minimizeIcon = new RecyclableSingleton() { protected Icon getInstance() { return new AquaInternalFrameButtonIcon(Widget.TITLE_BAR_COLLAPSE_BOX); } }; public static Icon exportMinimizeIcon() { return minimizeIcon.get(); } static final RecyclableSingleton zoomIcon = new RecyclableSingleton() { protected Icon getInstance() { return new AquaInternalFrameButtonIcon(Widget.TITLE_BAR_ZOOM_BOX); } }; public static Icon exportZoomIcon() { return zoomIcon.get(); } static class AquaInternalFrameButtonIcon extends AquaIcon.JRSUIIcon { public AquaInternalFrameButtonIcon(final Widget widget) { painter.state.set(widget); } public void paintIcon(final Component c, final Graphics g, final int x, final int y) { painter.state.set(getStateFor(c)); super.paintIcon(c, g, x, y); } State getStateFor(final Component c) { return State.ROLLOVER; } public int getIconWidth() { return 19; } public int getIconHeight() { return 19; } } protected void installKeyboardActions() { } //$ Not Mac-ish - should we support? protected ResizeBox resizeBox; protected void installComponents() { final JLayeredPane layeredPane = frame.getLayeredPane(); if (resizeBox != null) { resizeBox.removeListeners(); layeredPane.removeComponentListener(resizeBox); layeredPane.remove(resizeBox); resizeBox = null; } resizeBox = new ResizeBox(layeredPane); resizeBox.repositionResizeBox(); layeredPane.add(resizeBox); layeredPane.setLayer(resizeBox, JLayeredPane.DRAG_LAYER); layeredPane.addComponentListener(resizeBox); resizeBox.addListeners(); resizeBox.setVisible(frame.isResizable()); } /// Inherit all the listeners - that's the main reason we subclass Basic! protected void installListeners() { fPropertyListener = new PropertyListener(); frame.addPropertyChangeListener(fPropertyListener); super.installListeners(); } // uninstallDefaults // uninstallComponents protected void uninstallListeners() { super.uninstallListeners(); frame.removePropertyChangeListener(fPropertyListener); } protected void uninstallKeyboardActions() { } // Called when a DesktopIcon replaces an InternalFrame & vice versa //protected void replacePane(JComponent currentPane, JComponent newPane) {} protected void installMouseHandlers(final JComponent c) { c.addMouseListener(borderListener); c.addMouseMotionListener(borderListener); } protected void deinstallMouseHandlers(final JComponent c) { c.removeMouseListener(borderListener); c.removeMouseMotionListener(borderListener); } ActionMap createActionMap() { final ActionMap map = new ActionMapUIResource(); // add action for the system menu // Set the ActionMap's parent to the Auditory Feedback Action Map final AquaLookAndFeel lf = (AquaLookAndFeel)UIManager.getLookAndFeel(); final ActionMap audioMap = lf.getAudioActionMap(); map.setParent(audioMap); return map; } public Dimension getPreferredSize(JComponent x) { Dimension preferredSize = super.getPreferredSize(x); Dimension minimumSize = frame.getMinimumSize(); if (preferredSize.width < minimumSize.width) { preferredSize.width = minimumSize.width; } if (preferredSize.height < minimumSize.height) { preferredSize.height = minimumSize.height; } return preferredSize; } public void setNorthPane(final JComponent c) { replacePane(northPane, c); northPane = c; } /** * Installs necessary mouse handlers on newPane * and adds it to the frame. * Reverse process for the currentPane. */ protected void replacePane(final JComponent currentPane, final JComponent newPane) { if (currentPane != null) { deinstallMouseHandlers(currentPane); frame.remove(currentPane); } if (newPane != null) { frame.add(newPane); installMouseHandlers(newPane); } } // Our "Border" listener is shared by the AquaDesktopIcon protected MouseInputAdapter createBorderListener(final JInternalFrame w) { return new AquaBorderListener(); } /** * Mac-specific stuff begins here */ void setFrameType(final String frameType) { // Basic sets the background of the contentPane to null so it can inherit JInternalFrame.setBackground // but if *that's* null, we get the JDesktop, which makes ours look invisible! // So JInternalFrame has to have a background color // See Sun bugs 4268949 & 4320889 final Color bg = frame.getBackground(); final boolean replaceColor = (bg == null || bg instanceof UIResource); final Font font = frame.getFont(); final boolean replaceFont = (font == null || font instanceof UIResource); boolean isPalette = false; if (frameType.equals(OPTION_DIALOG)) { fAquaBorder = AquaInternalFrameBorder.dialog(); if (replaceColor) frame.setBackground(UIManager.getColor("InternalFrame.optionDialogBackground")); if (replaceFont) frame.setFont(UIManager.getFont("InternalFrame.optionDialogTitleFont")); } else if (frameType.equals(PALETTE_FRAME)) { fAquaBorder = AquaInternalFrameBorder.utility(); if (replaceColor) frame.setBackground(UIManager.getColor("InternalFrame.paletteBackground")); if (replaceFont) frame.setFont(UIManager.getFont("InternalFrame.paletteTitleFont")); isPalette = true; } else { fAquaBorder = AquaInternalFrameBorder.window(); if (replaceColor) frame.setBackground(UIManager.getColor("InternalFrame.background")); if (replaceFont) frame.setFont(UIManager.getFont("InternalFrame.titleFont")); } // We don't get the borders from UIManager, in case someone changes them - this class will not work with // different borders. If they want different ones, they have to make their own InternalFrameUI class fAquaBorder.setColors(fSelectedTextColor, fNotSelectedTextColor); frame.setBorder(fAquaBorder); fIsPallet = isPalette; } public void setPalette(final boolean isPalette) { setFrameType(isPalette ? PALETTE_FRAME : NORMAL_FRAME); } public boolean isDocumentEdited() { return fDocumentEdited; } public void setDocumentEdited(final boolean flag) { fDocumentEdited = flag; } /* // helpful debug drawing, shows component and content bounds public void paint(final Graphics g, final JComponent c) { super.paint(g, c); g.setColor(Color.green); g.drawRect(0, 0, frame.getWidth() - 1, frame.getHeight() - 1); final Insets insets = frame.getInsets(); g.setColor(Color.orange); g.drawRect(insets.left - 2, insets.top - 2, frame.getWidth() - insets.left - insets.right + 4, frame.getHeight() - insets.top - insets.bottom + 4); } */ // Border Listener Class /** * Listens for border adjustments. */ protected class AquaBorderListener extends MouseInputAdapter { // _x & _y are the mousePressed location in absolute coordinate system int _x, _y; // __x & __y are the mousePressed location in source view's coordinate system int __x, __y; Rectangle startingBounds; boolean fDraggingFrame; int resizeDir; protected final int RESIZE_NONE = 0; private boolean discardRelease = false; public void mouseClicked(final MouseEvent e) { if (didForwardEvent(e)) return; if (e.getClickCount() <= 1 || e.getSource() != getNorthPane()) return; if (frame.isIconifiable() && frame.isIcon()) { try { frame.setIcon(false); } catch(final PropertyVetoException e2) {} } else if (frame.isMaximizable()) { if (!frame.isMaximum()) try { frame.setMaximum(true); } catch(final PropertyVetoException e2) {} else try { frame.setMaximum(false); } catch(final PropertyVetoException e3) {} } } public void updateRollover(final MouseEvent e) { final boolean oldRollover = fRollover; final Insets i = frame.getInsets(); fRollover = (isTitleBarDraggableArea(e) && fAquaBorder.getWithinRolloverArea(i, e.getX(), e.getY())); if (fRollover != oldRollover) { repaintButtons(); } } public void repaintButtons() { fAquaBorder.repaintButtonArea(frame); } public void mouseReleased(final MouseEvent e) { if (didForwardEvent(e)) return; fDraggingFrame = false; if (fWhichButtonPressed != -1) { final int newButton = fAquaBorder.getWhichButtonHit(frame, e.getX(), e.getY()); final int buttonPresed = fWhichButtonPressed; fWhichButtonPressed = -1; fMouseOverPressedButton = false; if (buttonPresed == newButton) { fMouseOverPressedButton = false; fRollover = false; // not sure if this is needed? fAquaBorder.doButtonAction(frame, buttonPresed); } updateRollover(e); repaintButtons(); return; } if (discardRelease) { discardRelease = false; return; } if (resizeDir == RESIZE_NONE) getDesktopManager().endDraggingFrame(frame); else { final Container c = frame.getTopLevelAncestor(); if (c instanceof JFrame) { ((JFrame)frame.getTopLevelAncestor()).getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); ((JFrame)frame.getTopLevelAncestor()).getGlassPane().setVisible(false); } else if (c instanceof JApplet) { ((JApplet)c).getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); ((JApplet)c).getGlassPane().setVisible(false); } else if (c instanceof JWindow) { ((JWindow)c).getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); ((JWindow)c).getGlassPane().setVisible(false); } else if (c instanceof JDialog) { ((JDialog)c).getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); ((JDialog)c).getGlassPane().setVisible(false); } getDesktopManager().endResizingFrame(frame); } _x = 0; _y = 0; __x = 0; __y = 0; startingBounds = null; resizeDir = RESIZE_NONE; } public void mousePressed(final MouseEvent e) { if (didForwardEvent(e)) return; final Point p = SwingUtilities.convertPoint((Component)e.getSource(), e.getX(), e.getY(), null); __x = e.getX(); __y = e.getY(); _x = p.x; _y = p.y; startingBounds = frame.getBounds(); resizeDir = RESIZE_NONE; if (updatePressed(e)) { return; } if (!frame.isSelected()) { try { frame.setSelected(true); } catch(final PropertyVetoException e1) {} } if (isTitleBarDraggableArea(e)) { getDesktopManager().beginDraggingFrame(frame); fDraggingFrame = true; return; } if (e.getSource() == getNorthPane()) { getDesktopManager().beginDraggingFrame(frame); return; } if (!frame.isResizable()) { return; } if (e.getSource() == frame) { discardRelease = true; return; } } // returns true if we have handled the pressed public boolean updatePressed(final MouseEvent e) { // get the component. fWhichButtonPressed = getButtonHit(e); fMouseOverPressedButton = true; repaintButtons(); return (fWhichButtonPressed >= 0); // e.getX(), e.getY() } public int getButtonHit(final MouseEvent e) { return fAquaBorder.getWhichButtonHit(frame, e.getX(), e.getY()); } public boolean isTitleBarDraggableArea(final MouseEvent e) { if (e.getSource() != frame) return false; final Point point = e.getPoint(); final Insets insets = frame.getInsets(); if (point.y < insets.top - fAquaBorder.getTitleHeight()) return false; if (point.y > insets.top) return false; if (point.x < insets.left) return false; if (point.x > frame.getWidth() - insets.left - insets.right) return false; return true; } public void mouseDragged(final MouseEvent e) { // do not forward drags // if (didForwardEvent(e)) return; if (startingBounds == null) { // (STEVE) Yucky work around for bug ID 4106552 return; } if (fWhichButtonPressed != -1) { // track the button we started on. final int newButton = getButtonHit(e); fMouseOverPressedButton = (fWhichButtonPressed == newButton); repaintButtons(); return; } final Point p = SwingUtilities.convertPoint((Component)e.getSource(), e.getX(), e.getY(), null); final int deltaX = _x - p.x; final int deltaY = _y - p.y; int newX, newY; // Handle a MOVE if (!fDraggingFrame && e.getSource() != getNorthPane()) return; if (frame.isMaximum() || ((e.getModifiers() & InputEvent.BUTTON1_MASK) != InputEvent.BUTTON1_MASK)) { // don't allow moving of frames if maximixed or left mouse // button was not used. return; } final Dimension s = frame.getParent().getSize(); final int pWidth = s.width; final int pHeight = s.height; final Insets i = frame.getInsets(); newX = startingBounds.x - deltaX; newY = startingBounds.y - deltaY; // Make sure we stay in-bounds if (newX + i.left <= -__x) newX = -__x - i.left; if (newY + i.top <= -__y) newY = -__y - i.top; if (newX + __x + i.right > pWidth) newX = pWidth - __x - i.right; if (newY + __y + i.bottom > pHeight) newY = pHeight - __y - i.bottom; getDesktopManager().dragFrame(frame, newX, newY); return; } public void mouseMoved(final MouseEvent e) { if (didForwardEvent(e)) return; updateRollover(e); } // guards against accidental infinite recursion boolean isTryingToForwardEvent = false; boolean didForwardEvent(final MouseEvent e) { if (isTryingToForwardEvent) return true; // we didn't actually...but we wound up back where we started. isTryingToForwardEvent = true; final boolean didForwardEvent = didForwardEventInternal(e); isTryingToForwardEvent = false; return didForwardEvent; } boolean didForwardEventInternal(final MouseEvent e) { if (fDraggingFrame) return false; final Point originalPoint = e.getPoint(); if (!isEventInWindowShadow(originalPoint)) return false; final Container parent = frame.getParent(); if (!(parent instanceof JDesktopPane)) return false; final JDesktopPane pane = (JDesktopPane)parent; final Point parentPoint = SwingUtilities.convertPoint(frame, originalPoint, parent); /* // debug drawing Graphics g = parent.getGraphics(); g.setColor(Color.red); g.drawLine(parentPoint.x, parentPoint.y, parentPoint.x, parentPoint.y); */ final Component hitComponent = findComponentToHitBehindMe(pane, parentPoint); if (hitComponent == null || hitComponent == frame) return false; final Point hitComponentPoint = SwingUtilities.convertPoint(pane, parentPoint, hitComponent); hitComponent.dispatchEvent(new MouseEvent(hitComponent, e.getID(), e.getWhen(), e.getModifiers(), hitComponentPoint.x, hitComponentPoint.y, e.getClickCount(), e.isPopupTrigger(), e.getButton())); return true; } Component findComponentToHitBehindMe(final JDesktopPane pane, final Point parentPoint) { final JInternalFrame[] allFrames = pane.getAllFrames(); boolean foundSelf = false; for (final JInternalFrame f : allFrames) { if (f == frame) { foundSelf = true; continue; } if (!foundSelf) continue; final Rectangle bounds = f.getBounds(); if (bounds.contains(parentPoint)) return f; } return pane; } boolean isEventInWindowShadow(final Point point) { final Rectangle bounds = frame.getBounds(); final Insets insets = frame.getInsets(); insets.top -= fAquaBorder.getTitleHeight(); if (point.x < insets.left) return true; if (point.x > bounds.width - insets.right) return true; if (point.y < insets.top) return true; if (point.y > bounds.height - insets.bottom) return true; return false; } } static void updateComponentTreeUIActivation(final Component c, final Object active) { if (c instanceof javax.swing.JComponent) { ((javax.swing.JComponent)c).putClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY, active); } Component[] children = null; if (c instanceof javax.swing.JMenu) { children = ((javax.swing.JMenu)c).getMenuComponents(); } else if (c instanceof Container) { children = ((Container)c).getComponents(); } if (children != null) { for (final Component element : children) { updateComponentTreeUIActivation(element, active); } } } class PropertyListener implements PropertyChangeListener { public void propertyChange(final PropertyChangeEvent e) { final String name = e.getPropertyName(); if (FRAME_TYPE.equals(name)) { if (e.getNewValue() instanceof String) { setFrameType((String)e.getNewValue()); } } else if (IS_PALETTE_PROPERTY.equals(name)) { if (e.getNewValue() != null) { setPalette(((Boolean)e.getNewValue()).booleanValue()); } else { setPalette(false); } // TODO: CPlatformWindow? } else if ("windowModified".equals(name) || CPlatformWindow.WINDOW_DOCUMENT_MODIFIED.equals(name)) { // repaint title bar setDocumentEdited(((Boolean)e.getNewValue()).booleanValue()); frame.repaint(0, 0, frame.getWidth(), frame.getBorder().getBorderInsets(frame).top); } else if ("resizable".equals(name) || "state".equals(name) || "iconable".equals(name) || "maximizable".equals(name) || "closable".equals(name)) { if ("resizable".equals(name)) { frame.revalidate(); } frame.repaint(); } else if ("title".equals(name)) { frame.repaint(); } else if ("componentOrientation".equals(name)) { frame.revalidate(); frame.repaint(); } else if (JInternalFrame.IS_SELECTED_PROPERTY.equals(name)) { final Component source = (Component)(e.getSource()); updateComponentTreeUIActivation(source, frame.isSelected() ? Boolean.TRUE : Boolean.FALSE); } } } // end class PaletteListener static final InternalFrameShadow documentWindowShadow = new InternalFrameShadow() { Border getForegroundShadowBorder() { return new AquaUtils.SlicedShadowBorder(new Painter() { public void paint(final Graphics g, final int x, final int y, final int w, final int h) { g.setColor(new Color(0, 0, 0, 196)); g.fillRoundRect(x, y, w, h, 16, 16); g.fillRect(x, y + h - 16, w, 16); } }, new Painter() { public void paint(final Graphics g, int x, int y, int w, int h) { g.setColor(new Color(0, 0, 0, 64)); g.drawLine(x + 2, y - 8, x + w - 2, y - 8); } }, 0, 7, 1.1f, 1.0f, 24, 51, 51, 25, 25, 25, 25); } Border getBackgroundShadowBorder() { return new AquaUtils.SlicedShadowBorder(new Painter() { public void paint(final Graphics g, final int x, final int y, final int w, final int h) { g.setColor(new Color(0, 0, 0, 128)); g.fillRoundRect(x - 3, y - 8, w + 6, h, 16, 16); g.fillRect(x - 3, y + h - 20, w + 6, 19); } }, new Painter() { public void paint(final Graphics g, int x, int y, int w, int h) { g.setColor(new Color(0, 0, 0, 32)); g.drawLine(x, y - 11, x + w - 1, y - 11); } }, 0, 0, 3.0f, 1.0f, 10, 51, 51, 25, 25, 25, 25); } }; static final InternalFrameShadow paletteWindowShadow = new InternalFrameShadow() { Border getForegroundShadowBorder() { return new AquaUtils.SlicedShadowBorder(new Painter() { public void paint(final Graphics g, final int x, final int y, final int w, final int h) { g.setColor(new Color(0, 0, 0, 128)); g.fillRect(x, y + 3, w, h - 3); } }, null, 0, 3, 1.0f, 1.0f, 10, 25, 25, 12, 12, 12, 12); } Border getBackgroundShadowBorder() { return getForegroundShadowBorder(); } }; @SuppressWarnings("serial") // Superclass is not serializable across versions static class CompoundUIBorder extends CompoundBorder implements UIResource { public CompoundUIBorder(final Border inside, final Border outside) { super(inside, outside); } } abstract static class InternalFrameShadow extends RecyclableSingleton { abstract Border getForegroundShadowBorder(); abstract Border getBackgroundShadowBorder(); protected Border getInstance() { final Border fgShadow = getForegroundShadowBorder(); final Border bgShadow = getBackgroundShadowBorder(); return new Border() { public Insets getBorderInsets(final Component c) { return fgShadow.getBorderInsets(c); } public boolean isBorderOpaque() { return false; } public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) { if (((JInternalFrame)c).isSelected()) { fgShadow.paintBorder(c, g, x, y, w, h); } else { bgShadow.paintBorder(c, g, x, y, w, h); } } }; } } static final RecyclableSingleton RESIZE_ICON = new RecyclableSingleton() { protected Icon getInstance() { return new AquaIcon.CachableJRSUIIcon(11, 11) { public void initIconPainter(final AquaPainter iconState) { iconState.state.set(Widget.GROW_BOX_TEXTURED); iconState.state.set(WindowType.UTILITY); } }; } }; @SuppressWarnings("serial") // Superclass is not serializable across versions class ResizeBox extends JLabel implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener, PropertyChangeListener, UIResource { final JLayeredPane layeredPane; Dimension originalSize; Point originalLocation; public ResizeBox(final JLayeredPane layeredPane) { super(RESIZE_ICON.get()); setSize(11, 11); this.layeredPane = layeredPane; addMouseListener(this); addMouseMotionListener(this); addMouseWheelListener(this); } void addListeners() { frame.addPropertyChangeListener("resizable", this); } void removeListeners() { frame.removePropertyChangeListener("resizable", this); } void repositionResizeBox() { if (frame == null) { setSize(0, 0); } else { setSize(11, 11); } setLocation(layeredPane.getWidth() - 12, layeredPane.getHeight() - 12); } void resizeInternalFrame(final Point pt) { if (originalLocation == null || frame == null) return; final Container parent = frame.getParent(); if (!(parent instanceof JDesktopPane)) return; final Point newPoint = SwingUtilities.convertPoint(this, pt, frame); int deltaX = originalLocation.x - newPoint.x; int deltaY = originalLocation.y - newPoint.y; final Dimension min = frame.getMinimumSize(); final Dimension max = frame.getMaximumSize(); final int newX = frame.getX(); final int newY = frame.getY(); int newW = frame.getWidth(); int newH = frame.getHeight(); final Rectangle parentBounds = parent.getBounds(); if (originalSize.width - deltaX < min.width) { deltaX = originalSize.width - min.width; } else if (originalSize.width - deltaX > max.width) { deltaX = -(max.width - originalSize.width); } if (newX + originalSize.width - deltaX > parentBounds.width) { deltaX = newX + originalSize.width - parentBounds.width; } if (originalSize.height - deltaY < min.height) { deltaY = originalSize.height - min.height; } else if (originalSize.height - deltaY > max.height) { deltaY = -(max.height - originalSize.height); } if (newY + originalSize.height - deltaY > parentBounds.height) { deltaY = newY + originalSize.height - parentBounds.height; } newW = originalSize.width - deltaX; newH = originalSize.height - deltaY; getDesktopManager().resizeFrame(frame, newX, newY, newW, newH); } boolean testGrowboxPoint(final int x, final int y, final int w, final int h) { return (w - x) + (h - y) < 12; } void forwardEventToFrame(final MouseEvent e) { final Point pt = new Point(); final Component c = getComponentToForwardTo(e, pt); if (c == null) return; c.dispatchEvent(new MouseEvent(c, e.getID(), e.getWhen(), e.getModifiers(), pt.x, pt.y, e.getClickCount(), e.isPopupTrigger(), e.getButton())); } Component getComponentToForwardTo(final MouseEvent e, final Point dst) { if (frame == null) return null; final Container contentPane = frame.getContentPane(); if (contentPane == null) return null; Point pt = SwingUtilities.convertPoint(this, e.getPoint(), contentPane); final Component c = SwingUtilities.getDeepestComponentAt(contentPane, pt.x, pt.y); if (c == null) return null; pt = SwingUtilities.convertPoint(contentPane, pt, c); if (dst != null) dst.setLocation(pt); return c; } public void mouseClicked(final MouseEvent e) { forwardEventToFrame(e); } public void mouseEntered(final MouseEvent e) { } public void mouseExited(final MouseEvent e) { } public void mousePressed(final MouseEvent e) { if (frame == null) return; if (frame.isResizable() && !frame.isMaximum() && testGrowboxPoint(e.getX(), e.getY(), getWidth(), getHeight())) { originalLocation = SwingUtilities.convertPoint(this, e.getPoint(), frame); originalSize = frame.getSize(); getDesktopManager().beginResizingFrame(frame, SwingConstants.SOUTH_EAST); return; } forwardEventToFrame(e); } public void mouseReleased(final MouseEvent e) { if (originalLocation != null) { resizeInternalFrame(e.getPoint()); originalLocation = null; getDesktopManager().endResizingFrame(frame); return; } forwardEventToFrame(e); } public void mouseDragged(final MouseEvent e) { resizeInternalFrame(e.getPoint()); repositionResizeBox(); } public void mouseMoved(final MouseEvent e) { } public void mouseWheelMoved(final MouseWheelEvent e) { final Point pt = new Point(); final Component c = getComponentToForwardTo(e, pt); if (c == null) return; c.dispatchEvent(new MouseWheelEvent(c, e.getID(), e.getWhen(), e.getModifiers(), pt.x, pt.y, e.getClickCount(), e.isPopupTrigger(), e.getScrollType(), e.getScrollAmount(), e.getWheelRotation())); } public void componentResized(final ComponentEvent e) { repositionResizeBox(); } public void componentShown(final ComponentEvent e) { repositionResizeBox(); } public void componentMoved(final ComponentEvent e) { repositionResizeBox(); } public void componentHidden(final ComponentEvent e) { } public void propertyChange(final PropertyChangeEvent evt) { if (!"resizable".equals(evt.getPropertyName())) return; setVisible(Boolean.TRUE.equals(evt.getNewValue())); } } }