/* * 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) 2011 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.station.split; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Cursor; import java.awt.Graphics; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.HashMap; import java.util.Map; import javax.swing.SwingUtilities; import bibliothek.gui.DockController; import bibliothek.gui.dock.SplitDockStation; import bibliothek.gui.dock.SplitDockStation.Orientation; import bibliothek.gui.dock.event.DockHierarchyEvent; import bibliothek.gui.dock.event.DockHierarchyListener; import bibliothek.gui.dock.security.GlassedPane; import bibliothek.gui.dock.util.PropertyValue; /** * The default implementation of {@link SplitDividerStrategy} * @author Benjamin Sigg */ public class DefaultSplitDividerStrategy implements SplitDividerStrategy { private Map<SplitDockStation, Handler> handlers = new HashMap<SplitDockStation, Handler>(); public void install( SplitDockStation station, Component container ){ Handler handler = createHandlerFor( station ); handler.install( container ); handlers.put( station, handler ); } public void uninstall( SplitDockStation station ){ Handler handler = handlers.remove( station ); if( handler != null ){ handler.destroy(); } } public void paint( SplitDockStation station, Graphics g ){ Handler handler = handlers.get( station ); if( handler != null ){ handler.paint( g ); } } /** * Creates a new {@link Handler} for <code>station</code>. * @param station the station which is to be monitored * @return the new handler, not <code>null</code> */ protected Handler createHandlerFor( SplitDockStation station ){ return new Handler( station ); } /** * A {@link Handler} is responsible for handling the needs of one {@link SplitDockStation}. * @author Benjamin Sigg */ public static class Handler extends MouseAdapter implements MouseListener, MouseMotionListener, AWTEventListener,DockHierarchyListener{ private PropertyValue<Boolean> restricted = new PropertyValue<Boolean>(DockController.RESTRICTED_ENVIRONMENT) { @Override protected void valueChanged(Boolean oldValue, Boolean newValue) { updateEventListener(); } }; /** the currently known {@link DockController} */ private DockController controller; /** the node of the currently selected divider */ private Divideable current; /** the current location of the divider */ private double divider; /** the current state of the mouse: pressed or not pressed */ private boolean pressed = false; /** Will be set to true when mouse is over divider, and set to false when exited. (see AWTListener method below for more details). */ private boolean withinBounds = false; /** Flag indicating if AWTEventListener is registered successfully. */ private boolean awtListenerEnabled = false; /** the current bounds of the divider */ private Rectangle bounds = new Rectangle(); /** * A small modification of the position of the mouse. The modification * is the distance to the center of the divider. */ private int deltaX; /** * A small modification of the position of the mouse. The modification * is the distance to the center of the divider. */ private int deltaY; /** The station which is monitored by this strategy */ private SplitDockStation station; /** The component to which this strategy added a {@link MouseListener} */ private Component container; /** * Creates a new strategy that will monitor <code>station</code>. * @param station the station to monitor */ public Handler( SplitDockStation station ){ this.station = station; } /** * Gets the station which is monitored by this strategy * @return the owner of this strategy */ public SplitDockStation getStation(){ return station; } public void install( Component container ){ if( this.container != null ){ throw new IllegalStateException( "already initialized" ); } this.container = container; container.addMouseListener( this ); container.addMouseMotionListener( this ); station.addDockHierarchyListener(this); setController( station.getController() ); } public void hierarchyChanged( DockHierarchyEvent event ){ // nothing } public void controllerChanged( DockHierarchyEvent event ){ setController( station.getController() ); } private void setController( DockController controller ){ if( this.controller != controller ){ this.controller = controller; restricted.setProperties( controller ); updateEventListener(); } } private void updateEventListener(){ boolean expected = controller != null && !controller.isRestrictedEnvironment(); if( expected != awtListenerEnabled ){ awtListenerEnabled = expected; if( expected ){ // if this goes wrong, the offending client rightly gets an exception. It's his fault because he did set the "restricted environment" property wrong. Toolkit.getDefaultToolkit().addAWTEventListener( Handler.this, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK ); } else{ Toolkit.getDefaultToolkit().removeAWTEventListener( Handler.this ); } } } /** * AWT event listener. * Used to reset the mouse cursor when divider was changed and mouse exited event had not occurred normally. * @param event */ public void eventDispatched(AWTEvent event) { if (event.getID() == MouseEvent.MOUSE_MOVED || event.getID() == MouseEvent.MOUSE_RELEASED) { MouseEvent mev = (MouseEvent)event; if( mev.getSource() != Handler.this.container && withinBounds ){ if( mev.getSource() instanceof GlassedPane.GlassPane ){ // on glass pane -> check with traditional method // Question by Beni: does this ever happen? checkMousePositionAsync(); } else{ // mouse is over another component which is not the registered container and the mouse cursor had not been reseted yet -> reset mouse cursor Point p = SwingUtilities.convertPoint(mev.getComponent(), mev.getPoint(), station); if (station.getBounds().contains(p)) { // only if mouse is within our station setCursor(null); withinBounds = false; } } } } } /** * Gets the {@link Component} with which this strategy was {@link #install(Component) initialized}. * @return the argument from {@link #install(Component)} */ public Component getContainer(){ return container; } /** * Disposes all resources that are used by this handler. */ public void destroy(){ if( container != null ){ setCursor( null ); current = null; container.removeMouseListener( this ); container.removeMouseMotionListener( this ); container = null; try { java.awt.Toolkit.getDefaultToolkit().removeAWTEventListener(this); } catch (Throwable e) { e.printStackTrace(); } setController( null ); station.removeDockHierarchyListener( this ); } } /** * Changes the cursor of {@link #getContainer() the base component}. Subclasses may override this * method to use custom cursors. * @param cursor the cursor to set, may be <code>null</code> */ protected void setCursor( Cursor cursor ){ container.setCursor( cursor ); } /** * Repaints parts of the {@link #getContainer() base component}. * @param x x coordinate * @param y y coordinate * @param width the width of the are to repaint * @param height the height of the are to repaint */ protected void repaint( int x, int y, int width, int height ){ container.repaint( x, y, width, height ); } /** * Gets the node whose divider contains <code>x, y</code>. * @param x the x coordinate * @param y the y coordinate * @return the node containing <code>x, y</code> */ protected Divideable getDividerNode( int x, int y ){ return station.getRoot().getDividerNode( x, y ); } /** * Asynchronously checks the current position of the mouse and updates the cursor * if necessary. */ protected void checkMousePositionAsync(){ DockController controller = station.getController(); if( controller != null && !awtListenerEnabled ){ SwingUtilities.invokeLater( new Runnable(){ public void run(){ if( container != null ){ PointerInfo p = MouseInfo.getPointerInfo(); Point e = p.getLocation(); SwingUtilities.convertPointFromScreen(e, container); current = getDividerNode( e.x, e.y ); if( current == null ) { mouseExited( null ); } else { // check bounds with one pixel delta -> divider needs to be greater than 2 pixels, because divider bounds will be shrinked by 1 pixel at each side if( bounds.width > 2 && bounds.height > 2 ){ if( e.x <= bounds.x || e.x >= bounds.x+bounds.width-1 || e.y <= bounds.y || e.y >= bounds.y+bounds.height-1 ){ // mouse is likely to be not on divider anymore mouseExited( null ); } } } } } }); } } @Override public void mousePressed( MouseEvent e ){ if( station.isResizingEnabled() && !station.isDisabled() ) { if( !pressed ) { pressed = true; mouseMoved( e ); if( current != null ) { divider = current.getDividerAt( e.getX() + deltaX, e.getY() + deltaY ); divider = current.validateDivider( divider ); repaint( bounds.x, bounds.y, bounds.width, bounds.height ); bounds = current.getDividerBounds( divider, bounds ); repaint( bounds.x, bounds.y, bounds.width, bounds.height ); } } } } public void mouseDragged( MouseEvent e ){ if( station.isResizingEnabled() && !station.isDisabled() ) { if( pressed && current != null ) { divider = current.getDividerAt( e.getX() + deltaX, e.getY() + deltaY ); divider = current.validateDivider( divider ); repaint( bounds.x, bounds.y, bounds.width, bounds.height ); bounds = current.getDividerBounds( divider, bounds ); repaint( bounds.x, bounds.y, bounds.width, bounds.height ); if( station.isContinousDisplay() && current != null ) { setDivider( current, divider ); station.updateBounds(); } } } } @Override public void mouseReleased( MouseEvent e ){ if( pressed ) { pressed = false; if( current != null ) { setDivider( current, divider ); repaint( bounds.x, bounds.y, bounds.width, bounds.height ); station.updateBounds(); } setCursor( null ); mouseMoved( e ); if( controller != null && !controller.isRestrictedEnvironment() && awtListenerEnabled ) { // new solution eventDispatched(e); } else { // old solution with a little tweaking checkMousePositionAsync(); } } } /** * Called if the divider of <code>node</code> needs to be changed. * @param node the node whose divider changes * @param divider the new divider */ protected void setDivider( Divideable node, double divider ){ node.setDivider( divider ); } public void mouseMoved( MouseEvent e ){ if( station.isResizingEnabled() && !station.isDisabled() ) { current = getDividerNode( e.getX(), e.getY() ); if( current == null ) setCursor( null ); else if( current.getOrientation() == Orientation.HORIZONTAL ) setCursor( Cursor.getPredefinedCursor( Cursor.W_RESIZE_CURSOR ) ); else setCursor( Cursor.getPredefinedCursor( Cursor.N_RESIZE_CURSOR ) ); if( current != null ) { bounds = current.getDividerBounds( current.getActualDivider(), bounds ); deltaX = bounds.width / 2 + bounds.x - e.getX(); deltaY = bounds.height / 2 + bounds.y - e.getY(); // mouse is over divider withinBounds = true; } else { // mouse is not over divider anymore withinBounds = false; } } } @Override public void mouseExited( MouseEvent e ){ if( !pressed ) { current = null; setCursor( null ); // mouse exited divider normally withinBounds = false; } } /** * Paints a line at the current location of the divider. * @param g the Graphics used to paint */ public void paint( Graphics g ){ if( station.isResizingEnabled() && !station.isDisabled() ) { if( current != null && pressed ) { station.getPaint().drawDivider( g, bounds ); } } } } }