All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.station.split.DefaultSplitDividerStrategy Maven / Gradle / Ivy

/*
 * 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
 * [email protected]
 * 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 handlers = new HashMap();
	
	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 station.
	 * @param station the station which is to be monitored
	 * @return the new handler, not null
	 */
	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 restricted = new PropertyValue(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 station.
		 * @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 null
		 */
		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 x, y.
		 * @param x the x coordinate
		 * @param y the y coordinate
		 * @return the node containing x, y
		 */
		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 node 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 );
				}
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy