/*
* 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) 2010 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.control.focus;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElementRepresentative;
import bibliothek.gui.dock.control.ControllerSetupCollection;
import bibliothek.gui.dock.control.DockRelocator;
import bibliothek.gui.dock.control.GlobalMouseDispatcher;
import bibliothek.gui.dock.control.relocator.DockRelocatorEvent;
import bibliothek.gui.dock.control.relocator.VetoableDockRelocatorAdapter;
import bibliothek.gui.dock.control.relocator.VetoableDockRelocatorListener;
import bibliothek.gui.dock.event.ControllerSetupListener;
import bibliothek.gui.dock.event.FocusVetoListener;
import bibliothek.gui.dock.event.FocusVetoListener.FocusVeto;
import bibliothek.gui.dock.util.DockUtilities;
/**
* This implementation of a {@link MouseFocusObserver} offers methods to handle
* {@link FocusVetoListener}s and registers a {@link VetoableDockRelocatorListener}. On a
* {@link VetoableDockRelocatorListener#dropped(DockRelocatorEvent) drop event} this observer
* will automatically transfer the focus to the dropped {@link Dockable}.
* @author Benjamin Sigg
*/
public class DefaultMouseFocusObserver implements MouseFocusObserver{
/** The controller to be informed about changes */
private DockController controller;
/**
* Listener added to the {@link DockRelocator}, updates the focused {@link Dockable} after a
* drag and drop operation completed.
*/
private VetoableDockRelocatorListener relocatorListener = new VetoableDockRelocatorAdapter(){
public void dropped( final DockRelocatorEvent event ){
EventQueue.invokeLater( new Runnable(){
public void run(){
FocusController focus = controller.getFocusController();
FocusStrategy strategy = focus.getStrategy();
if( strategy == null || strategy.shouldFocusAfterDrop( event.getDockable() )){
controller.setFocusedDockable( new DefaultFocusRequest( event.getDockable(), null, true ));
}
}
});
};
};
/** Listener added to the {@link GlobalMouseDispatcher} for registering any {@link MouseEvent} */
private GlobalMouseListener listener = new GlobalMouseListener();
/**
* Creates a new FocusController.
* @param controller the controller which will be informed about
* focus-changes
* @param setup an observable informing this object when <code>controller</code>
* is set up.
*/
public DefaultMouseFocusObserver( DockController controller, ControllerSetupCollection setup ){
this.controller = controller;
setup.add( new ControllerSetupListener(){
public void done( DockController controller ) {
controller.getRelocator().addVetoableDockRelocatorListener( relocatorListener );
GlobalMouseDispatcher dispatcher = controller.getGlobalMouseDispatcher();
dispatcher.addMouseListener( listener );
dispatcher.addMouseMotionListener( listener );
dispatcher.addMouseWheelListener( listener );
}
});
}
/**
* Stops this FocusController. This controller will remove all
* its listeners and become ready for the garbage collector.
*/
public void kill(){
getController().getRelocator().removeVetoableDockRelocatorListener( relocatorListener );
GlobalMouseDispatcher dispatcher = controller.getGlobalMouseDispatcher();
dispatcher.removeMouseListener( listener );
dispatcher.removeMouseMotionListener( listener );
dispatcher.removeMouseWheelListener( listener );
}
/**
* Gets the affected controller.
* @return the controller
*/
public DockController getController() {
return controller;
}
/**
* This method may be called at any time by any component that received
* the {@link MouseEvent} <code>event</code>. This observer may transfer the
* focus because of this call.<br>
* If this application runs in a {@link DockController#isRestrictedEnvironment() restricted environment}
* than any {@link DockStation} of this framework will call this method.
* @param event the event to check
*/
public void check( MouseEvent event ){
if( interact( event )){
check( (AWTEvent)event );
}
}
/**
* This method may be called at any time by any component that received
* the {@link MouseWheelEvent} <code>event</code>. This observer may transfer the
* focus because of this call.<br>
* If this application runs in a {@link DockController#isRestrictedEnvironment() restricted environment}
* than any {@link DockStation} of this framework will call this method.
* @param event the event to check
*/
public void check( MouseWheelEvent event ){
if( interact( event )){
check( (AWTEvent)event );
}
}
/**
* Lets check this controller whether the focus should change, or not. Clients
* invoking this method should first check whether <code>event</code> is
* allowed to change the focus or not. This check can be done through the
* method {@link #interact(AWTEvent)}.
* @param event The event to react on.
* @see #interact(AWTEvent)
*/
protected void check( AWTEvent event ){
if( controller.getRelocator().isOnPut() || controller.isOnFocusing() )
return;
Object source = event.getSource();
if( source instanceof Component ){
Component component = (Component)source;
if( event.getID() == MouseEvent.MOUSE_PRESSED ){
if( component.isFocusable() && component.isEnabled()){
check( component, false, true, event );
}
else{
check( component, true, false, event );
}
}
else{
check( component, event );
}
}
}
/**
* Tells whether this event should change the focus.
* @param event the event
* @return <code>true</code> if the focus could be changed
*/
protected boolean interact( AWTEvent event ){
int id = event.getID();
return id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_WHEEL;
}
/**
* Handles the veto that was given when trying to forward
* <code>event</code>. The default implementation calls
* {@link InputEvent#consume()} to get rid of the event.
* @param event the event to handle
* @param veto which veto was called by a {@link FocusVetoListener}
*/
protected void handleVeto( AWTEvent event, FocusVeto veto ){
if( veto == FocusVeto.VETO ){
if( event instanceof InputEvent ){
((InputEvent)event).consume();
}
}
}
/**
* Tries to find the Dockable which owns <code>component</code>
* and sets this Dockable to the focusedDockable. The method
* only succeeds if no veto-listener reacts.
* @param component the component whose dockable parent is to set
* focused
* @param event the event that causes this check
*/
protected void check( Component component, AWTEvent event ){
check( component, true, false, event );
}
/**
* Tries to find the Dockable which owns <code>component</code>
* and sets this Dockable to the focusedDockable. The method
* only succeeds if no veto-listener reacts.
* @param component the component whose dockable parent is to set
* focused
* @param ensureFocus whether the DockController should ensure
* that the focus is set correctly or not.
* @param requestFocusInWindow whether {@link Component#requestFocusInWindow()} should be
* called or not
* @param event the event that causes this check
*/
protected void check( final Component component, final boolean ensureFocus, boolean requestFocusInWindow, final AWTEvent event ){
DockElementRepresentative element = getDockable( component, event );
if( element == null ){
return;
}
Dockable dock = element.getElement().asDockable();
if( dock == null ){
return;
}
Dockable focused = controller.getFocusedDockable();
boolean change = true;
if( focused != null )
change = !DockUtilities.isAncestor( dock, focused );
if( change ){
if( component instanceof FocusAwareComponent ){
FocusAwareComponent aware = (FocusAwareComponent)component;
if( requestFocusInWindow ){
aware.maybeRequestFocus();
}
aware.invokeOnFocusRequest(new Runnable(){
public void run(){
DockElementRepresentative element = getDockable( component, event );
if( element != null ){
Dockable dock = element.getElement().asDockable();
if( dock != null ){
controller.setFocusedDockable( new DefaultFocusRequest( dock, component, false, ensureFocus, element.shouldTransfersFocus() ));
}
}
}
});
}
else{
if( requestFocusInWindow ){
component.requestFocusInWindow();
}
controller.setFocusedDockable( new DefaultFocusRequest( dock, component, false, ensureFocus, element.shouldTransfersFocus() ));
}
}
}
/**
* Gets the top-dockable which has <code>component</code> or
* parent of <code>component</code> as base Component.
* @param component a Component
* @param event the event that causes this check
* @return a Dockable or <code>null</code> if nothing was found
*/
protected DockElementRepresentative getDockable( Component component, AWTEvent event ){
DockElementRepresentative element = controller.searchElement( component );
if( element == null )
return null;
if( event instanceof MouseEvent || event instanceof MouseWheelEvent ){
if( !element.shouldFocus() ){
return null;
}
}
Dockable dockable = element.getElement().asDockable();
if( dockable == null )
return null;
FocusVeto veto = controller.getFocusController().checkFocusedDockable( element );
if( veto != null && veto != FocusVeto.NONE ){
handleVeto( event, veto );
return null;
}
return element;
}
/**
* This listener forwards all {@link MouseEvent}s to the {@link DefaultMouseFocusObserver#check(MouseEvent)}
* and {@link DefaultMouseFocusObserver#check(MouseWheelEvent)}.
* @author Benjamin Sigg
*/
private class GlobalMouseListener implements MouseListener, MouseMotionListener, MouseWheelListener{
public void mouseWheelMoved( MouseWheelEvent e ){
check( e );
}
public void mouseDragged( MouseEvent e ){
check( e );
}
public void mouseMoved( MouseEvent e ){
check( e );
}
public void mouseClicked( MouseEvent e ){
check( e );
}
public void mousePressed( MouseEvent e ){
check( e );
}
public void mouseReleased( MouseEvent e ){
check( e );
}
public void mouseEntered( MouseEvent e ){
check( e );
}
public void mouseExited( MouseEvent e ){
check( e );
}
}
}