/*
* 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;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.Timer;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElementRepresentative;
import bibliothek.gui.dock.control.focus.AbstractFocusController;
import bibliothek.gui.dock.control.focus.DefaultFocusRequest;
import bibliothek.gui.dock.control.focus.EnsuringFocusRequest;
import bibliothek.gui.dock.control.focus.FocusController;
import bibliothek.gui.dock.control.focus.FocusRequest;
import bibliothek.gui.dock.control.focus.FocusStrategy;
import bibliothek.gui.dock.control.focus.FocusStrategyRequest;
import bibliothek.gui.dock.event.FocusVetoListener;
import bibliothek.gui.dock.event.FocusVetoListener.FocusVeto;
import bibliothek.gui.dock.title.DockTitle;
/**
* Default implementation of {@link FocusController}.
* @author Benjamin Sigg
*/
public class DefaultFocusController extends AbstractFocusController {
/** the Dockable which has currently the focus, can be <code>null</code> */
private Dockable focusedDockable = null;
/** <code>true</code> while the controller actively changes the focus */
private boolean onFocusing = false;
/** all the requests waiting for their execution */
private List<Request> pendingRequests = new ArrayList<Request>();
/** {@link Runnable}s that will be executed once {@link #pendingRequests} is empty */
private List<Runnable> pendingCompletionRequests = new LinkedList<Runnable>();
/**
* Creates a new focus-controller
* @param controller the owner of this controller
*/
public DefaultFocusController( DockController controller ){
super( controller );
}
public boolean isOnFocusing(){
return onFocusing;
}
public Dockable getFocusedDockable(){
return focusedDockable;
}
public FocusVeto checkFocusedDockable( DockElementRepresentative source ){
if( source == null ){
return null;
}
Dockable dockable = source.getElement().asDockable();
if( dockable == null ){
return null;
}
FocusVeto veto;
if( source instanceof DockTitle ){
veto = fireVetoTitle( (DockTitle)source );
}
else{
veto = fireVetoDockable( dockable );
}
if( veto == null ){
return FocusVeto.NONE;
}
return veto;
}
public FocusVeto setFocusedDockable( DockElementRepresentative source, Component component, boolean force, boolean ensureFocusSet, boolean ensureDockableFocused ){
DefaultFocusRequest request = new DefaultFocusRequest( source, component, force, ensureFocusSet, ensureDockableFocused );
focus( request );
return request.getVeto();
}
public void ensureFocusSet( boolean dockableOnly ){
Dockable dockable = focusedDockable;
if( dockable != null ){
focus( new EnsuringFocusRequest( dockable, dockableOnly ));
}
}
/**
* Requests focus for the {@link Component} that is described by <code>request</code>. The request is either
* executed now (if {@link FocusRequest#getDelay() delay} is 0) or in the near future. The request may be canceled either
* because another request is executed first, because of a {@link FocusVetoListener}, or because the request contains
* invalid data.
* @param request the request
*/
public void focus( FocusRequest request ){
Request next = new Request( request, false );
next.enqueue();
}
public void onFocusRequestCompletion( final Runnable run ) {
if( EventQueue.isDispatchThread() ){
synchronized( pendingRequests ) {
if( pendingRequests.isEmpty() || isFrozen() ){
run.run();
}
else{
pendingCompletionRequests.add( run );
}
}
}
else{
try {
EventQueue.invokeAndWait( new Runnable() {
public void run() {
onFocusRequestCompletion( run );
}
} );
} catch( InterruptedException e ) {
// there really is not much we can do here
e.printStackTrace();
} catch( InvocationTargetException e ) {
// there really is not much we can do here
e.printStackTrace();
}
}
}
private void checkCompletionRequests(){
EventQueue.invokeLater( new Runnable() {
public void run() {
synchronized ( pendingRequests ) {
Iterator<Runnable> completion = pendingCompletionRequests.iterator();
while( pendingRequests.isEmpty() && completion.hasNext() ){
Runnable completionRequest = completion.next();
completion.remove();
completionRequest.run();
}
}
}
});
}
/**
* Decides whether to execute or to refuse <code>request</code>.
* @param request the request to check
* @param dockable the dockable that would receive the focus through this request
* @return the accepted {@link Component} or <code>null</code> if the
* request is to be refused
*/
protected Component accept( final FocusRequest request, final Dockable dockable ){
if( isFrozen() ){
return null;
}
if( !request.validate( this ) ){
return null;
}
FocusVeto veto = checkFocusedDockable( request.getSource() );
if( veto == null ){
veto = FocusVeto.NONE;
}
request.veto( veto );
if( veto != FocusVeto.NONE ){
return null;
}
FocusStrategy strategy = getStrategy();
Component component = request.getComponent();
if( strategy != null && dockable != null ){
Component replacement = strategy.getFocusComponent( new FocusStrategyRequest(){
public Component getMouseClicked(){
return request.getComponent();
}
public Dockable getDockable(){
return dockable;
}
public boolean excluded( Component component ){
return !request.acceptable( component );
}
});
if( replacement != null ){
component = replacement;
}
}
if( component == null && dockable != null ){
component = dockable.getComponent();
}
if( component != null && pendingRequests.size() > 1 ){
if( !request.isHardRequest() ){
if( !component.isVisible() || !component.isShowing() ){
component = null;
}
}
}
return component;
}
/**
* Called if {@link #accept(FocusRequest, Dockable)} accepted <code>request</code>.
* @param request the request to execute
* @param dockable the element that will receive the focus
* @param component the {@link Component} that is to be focused
*/
protected void execute( final FocusRequest request, Dockable dockable, final Component component ){
// clean up
synchronized( pendingRequests ){
for( Request pending : pendingRequests ){
pending.cancel();
}
pendingRequests.clear();
}
boolean active = true;
// execute
if( EventQueue.isDispatchThread() ){
active = grant( request, component );
}
else{
EventQueue.invokeLater( new Runnable() {
public void run(){
grant( request, component );
}
});
}
if( active && dockable != focusedDockable ){
Dockable oldFocused = focusedDockable;
focusedDockable = dockable;
fireDockableFocused( oldFocused, focusedDockable );
}
checkCompletionRequests();
}
private boolean grant( FocusRequest request, Component component ){
FocusRequest next;
try{
onFocusing = true;
next = request.grant( component );
}
finally{
onFocusing = false;
}
if( next != null ){
boolean accepted = request.getSource() == next.getSource() && component == next.getComponent();
Request nextRequest = new Request( next, accepted );
return nextRequest.enqueue();
}
return true;
}
private class Request implements ActionListener{
/** whether this request is accepted and can be executed */
private boolean accepted = false;
/** the request to execute */
private FocusRequest request;
/** whether this request should silently fail */
private boolean canceled = false;
/**
* Creates a new request.
* @param request the request to execute
* @param accepted whether <code>request</code> has already been accepted
*/
public Request( FocusRequest request, boolean accepted ){
this.request = request;
this.accepted = accepted;
synchronized( pendingRequests ) {
pendingRequests.add( this );
}
}
/**
* Starts this request
* @return <code>true</code> if the request has already been handled
* <code>false</code> otherwise
*/
public boolean enqueue(){
if( request.getDelay() <= 0 ){
run();
return true;
}
else{
Timer timer = new Timer( request.getDelay(), this );
timer.setRepeats( false );
timer.start();
return false;
}
}
/**
* Stop this request from ever being executed
*/
public void cancel(){
canceled = true;
}
/**
* Gets the {@link Dockable} which receives the focus through this request.
* @return the dockable or <code>null</code>
*/
public Dockable getDockable(){
DockElementRepresentative source = request.getSource();
if( source == null ){
return null;
}
return source.getElement().asDockable();
}
private Component accept(){
if( accepted ){
return request.getComponent();
}
else{
return DefaultFocusController.this.accept( request, getDockable() );
}
}
public void actionPerformed( ActionEvent e ){
run();
}
private void run(){
if( !canceled ){
Component component = accept();
if( component != null ){
execute( request, getDockable(), component );
}
else if( request.getSource() == null && request.getComponent() == null && pendingRequests.size() == 1 ){
execute( request, null, null );
}
else{
synchronized( pendingRequests ) {
pendingRequests.remove( this );
}
}
}
}
}
}