Requesting focus on dockable contents

Hi,

Thanks for the framework, it is really good. I have an issue I would like some help with please.

I am using the 1.0.8-preview3 version and using the Eclipse Theme (though I have tried others to no avail).

I have a JTree in a dockable and I am trying to pass the focus on to the tree whenever the dockable gets the focus. I tried adding a CFocusListener (below) which requests the focus however, the focus always seems to remain with the tab label.

Is there something else I should be doing instead?

Thanks

    public void focusGained(CDockable dockable) {
	if(dockable instanceof DefaultSingleCDockable) {
		((DefaultSingleCDockable)dockable).getContentPane().requestFocus();
	}
    }
				
    public void focusLost(CDockable dockable) { }
};```

The framework also calls “requestFocus” on some components to ensure that the focus does not jump away (the focus system of Swing is not very developer-friendly. Some events are delayed, information outdated, requests ignored…).

What if you do something like this:

  public void run(){
    ((DefaultSingleCDockable)dockable).getContentPane().requestFocus();
  }
});```
?

[Edit: you might want to have a look at the source code of "DockController.setFocusedDockable". There you see how these focus-events get fired, but also that the focused component might be changed afterwards again]

Hi Beni,

Many thanks for your suggestions, works lovely now :slight_smile:

Just in case someone else needs this, I changed stop() in DockSelector so that ensureFocusSet is now false which causes ensureFocusSet() be called inline in DockController.setFocusedDockable().

    public void stop( Dockable dockable ){
        close();
        controller.setFocusedDockable( dockable, false, false);
    }```

Hi all,

I tried the above suggestions and it works for me in about 80% of the cases.
In 20% of the cases the dockable tab gets the focus back.
Would it be possible to build into the framework a mechanism to focus the content of dockables?
I am not sure what would be the best way to do it.
The framework could remember the lastFocusOwner (like JInternalFrame)
or maybe the dockable could have a method to set the desired focus owner inside it…

best regards,
Maciej Modelski

That’s a good suggestion. I’ll add some kind of “FocusStrategy” which can decide for any Dockable what the focused Component should be.

There is now an interface “FocusStrategy” that tells the framework which Component of a Dockable to focus. The strategy can be set using “DockController.getFocusController().setStrategy(…)”. Also each AbstractCDockable has a method “setFocusComponent” to set the default component for focusing (CControl set a FocusStrategy that reads this component).

Currently I’m on adding some code to track the last focused Component of each Dockable.

Hi Beni,

I didn’t have time to test it yet but it sounds like exactly what I need!
Thank you very much!

best regards,
Maciej Modelski

Hi Beni,

I think the focus mechanism got broken in 1.1.0.p5(a)…
I run the following code in 1.1.0p4 and 1.1.0.p5a.
There are two text fields on each dockable
the top one is supposed to get focus.
In p4 the topField always gets focus in p5a it doesn’t…

kind regards,
MM

package test;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.theme.ThemeMap;

public class TestFocus
{

  public static void main(String[] args)
  {

    JFrame frame = new JFrame();
    CControl control = new CControl(frame);

    control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);


    DefaultSingleCDockable red = create(control, "Red", Color.RED);
    DefaultSingleCDockable green = create(control, "Green", Color.GREEN);
    DefaultSingleCDockable blue = create(control, "Blue", Color.BLUE);
    DefaultSingleCDockable black = create(control, "Black", Color.BLACK);


    CGrid grid = new CGrid(control);
    grid.add(0, 0, 5, 5, red);
    grid.add(10, 0, 5, 5, green);
    grid.add(10, 5, 5, 5, blue);
    grid.add(0, 5, 5, 5, black);

    control.getContentArea().deploy(grid);

    frame.getContentPane().add(control.getContentArea(), BorderLayout.CENTER);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(new Dimension(600, 600));
    frame.setVisible(true);
  }

  public static DefaultSingleCDockable create(CControl control, String title, Color color)
  {
    JPanel panel = new JPanel(new BorderLayout());
    panel.setOpaque(true);
    panel.setBackground(color);

    JTextField topField = new JTextField();
    panel.add(topField, BorderLayout.NORTH);
    panel.add(new JTextField(), BorderLayout.SOUTH);

    DefaultSingleCDockable singleDockable = new DefaultSingleCDockable(title, title, panel);
    singleDockable.setFocusComponent(topField);
    
    return singleDockable;
  }

}

Hm, that’s because I slightly changed the strategy in order to fix this bug.

The JComboBox does not response happy if the focus changes after someone clicks on its (unfocusable, look-and-feel dependent) button. So I added some code that prevents focus transfer if clicking on a Component that is already a child of a Dockable. But in your case that is exactly what should happen… kind of a contradiction.

I’ll think up some solution (it’s going to be ugly) and fix this in the next version.

A new version is online. It should now handle both issues correctly.

Currently I’m on adding some code to track the last focused Component of each Dockable.

Is this supposed to work? I’ve got 2 Dockables and when one Dockable gets selected, the last focused component of this Dockable should get the focus.

By now that code is active. Usually the focus falls to the Component on which you clicked with the mouse. The tracking system is only used if you select a Dockable through other means (e.g. closing a Dockable, using the keyboard).

Does not work for me, unfortunately.
Would you take a look at this example?

http://pastebin.com/ra7xJw1v
http://pastebin.com/zcSYXrFA
http://pastebin.com/Bd3Bt6Kx

You are clicking onto the tabs? The main issue is, that they are focusable and hence they gain the focus. If you use “ctrl+shift+e” to select another Dockable, then the focus is transferred to one of the textareas.

As a quick workaround you can specify a custom FocusStrategy to prevent that from happening (see code below).

The current behavior is the intended behavior, but I’m no longer sure if this is really good. I think I’ll modify the behavior a bit, it does not make much sense to focus a tab or title, even if the user clicked on it. So in the next version the default behavior and the behavior from the workaround will be almost the same.

dfControl.getController().getFocusController().setStrategy(new CustomFocusStrategy(dfControl.getController()));

	/**
	 * The {@link CustomFocusStrategy} keeps track of the last focused {@link Component} of any
	 * {@link Dockable} that is registered at a {@link DockController}.
	 * @author Benjamin Sigg
	 */
	public static class CustomFocusStrategy implements FocusStrategy{
		/** the controller to monitor for new {@link Dockable}s */
		private DockController controller;
		
		/** all the currently observed {@link Dockable}s */
		private Map<Component, Tracker> trackers = new HashMap<Component, Tracker>(); 
		
		/** is informed about new {@link Dockable}s */
		private DockRegisterListener listener = new DockRegisterAdapter(){
			public void dockableRegistered( DockController controller, Dockable dockable ){
				add( dockable );
			}
			
			public void dockableUnregistered( DockController controller, Dockable dockable ){
				remove( dockable );
			}
		};
		
		public CustomFocusStrategy( DockController controller ){
			this.controller = controller;
		}
		
		/**
		 * Tells whether the non-focusable <code>component</code> in reality is focusable. This is <code>true</code>
		 * for example for any child of a {@link JComboBox}.
		 * @param component the component which seems to be not focusable, but in reality is focusable
		 * @return <code>true</code> if <code>component</code> should be treated as if it would be focusable
		 */
		protected boolean focusable( Component component ){
			while( component != null ){
				if( component instanceof JComboBox ){
					return true;
				}
				component = component.getParent();
			}
			return false;
		}
		
		public Component getFocusComponent( Dockable dockable, Component mouseClicked ){
			if( mouseClicked != null && SwingUtilities.isDescendingFrom(mouseClicked, dockable.getComponent())){
				if( mouseClicked.isFocusable() || focusable( mouseClicked )){
					return mouseClicked;
				}
			}
			
			Tracker tracker = trackers.get( dockable.getComponent() );
			if( tracker == null ){
				return null;
			}
			return tracker.getLastFocused();
		}
		
		public void bind(){
			DockRegister register = controller.getRegister();
			register.addDockRegisterListener( listener );
			for( int i = 0, n = register.getDockableCount(); i<n; i++ ){
				add( register.getDockable( i ));
			}
		}
		
		public void unbind(){
			controller.getRegister().removeDockRegisterListener( listener );
			for( Tracker tracker : trackers.values() ){
				tracker.destroy();
			}
			trackers.clear();
		}
		
		private void add( Dockable dockable ){
			Tracker tracker = new Tracker( dockable );
			for( Tracker other : trackers.values() ){
				if( SwingUtilities.isDescendingFrom( dockable.getComponent(), other.dockable.getComponent() )){
					other.remove( dockable.getComponent() );	
				}
			}
			trackers.put( dockable.getComponent(), tracker );
		}
		
		private void remove( Dockable dockable ){
			Tracker tracker = trackers.remove( dockable.getComponent() );
			if( tracker != null ){
				tracker.destroy();
			}
		}
		
		private class Tracker extends FocusTracker{
			private Dockable dockable;
			
			public Tracker( Dockable dockable ){
				super( dockable.getComponent() );
				this.dockable = dockable;
			}
			
			@Override
			protected void add( Component component ){
				if( !trackers.containsKey( component )){
					super.add( component );
				}
			}
			
			@Override
			protected void remove(Component component) {
				super.remove(component);
			}
		}
	}

Yes, I’m clicking on the tabs.
CControl.getController() does not exist. I’m using DockingFrames 1.1.1p3

Now that is something I’m certain that it exists in 1.1.1p3, especially since I downloaded the zip File just to make sure it does exists. You must be using some older version. :wink:

[Edit: or you just wait for 1.1.1p4, which I will upload this weekend, and which already contains the modified behavior]

You’re right, it’s there. Got confused with a few more dependend projects that use different Versions of DockingFrames.
I’ll just wait for 1.1.1p4.
Thank you!