Finding a specific Dockable

I have several nested DockStations (possibly re-arranged by user) and one of them contains a particular JFrame. I need to find the Dockable that contains that particular JFrame. Is there a method/utility to do that, or do I have to walk all DockStations recursively looking for a Dockable that has my JFrame as a child?

Thanks.

Alla

contains a particular JFrame

Doubtful, as a JFrame cannot have a parent… :wink:

You’ll have to walk through the tree, „DockUtilities.visit“ may help in that. But at some time your application adds the „JFrame“ to the Dockable, maybe you can store a reference to the Dockable directly in your „JFrame“?

Beni, thanks.

[QUOTE=Beni]Doubtful, as a JFrame cannot have a parent… :wink:
[/QUOTE]
My bad, I meant JPanel. :frowning:

You’ll have to walk through the tree, „DockUtilities.visit“ may help in that. But at some time your application adds the „JFrame“ to the Dockable, maybe you can store a reference to the Dockable directly in your „JFrame“?
I’ll have a look at DockUtilities.visit, thanks.

Keeping the references somewhere is another possibility I was thinking of.

What I need to do it bring up docked tabs in response to some user actions. I am switching over from another docking framework, so currently the tabs are identified by the component they are hosting.

By the way, is there a way to tell a DefaultDockable inside a StackDockStation to come to front and take focus? It does not have a toFront() method. Should I use DefaultCommonDockable instead?

Yeah, and while I am asking, what’s the best way to make a theme feel like Eclipse, but look like Smooth or Flat? I mean having tabs on top, no title bar on tabs and no bar on the left in StackedDockStation. I tried this:

   private void initDocking() {
        dockController = new DockController();
/* ... */
        dockController.setTheme(new MtDockingTheme(new SmoothTheme()));
        dockController.getProperties().set(StackDockStation.TAB_PLACEMENT, TabPlacement.TOP_OF_DOCKABLE);
/*...*/
}

    public class MtDockingTheme extends NoStackTheme {

        DockTheme baseTheme;
        DockTitleFactory titleFactory;

        public MtDockingTheme(DockTheme base) {
            super(base);

            titleFactory = new NoStackTitleFactory(base.getTitleFactory(dockController)) {

                @Override
                public void request(DockTitleRequest request) {
                    Dockable dockable = request.getTarget();
                    if (dockable.getDockParent() instanceof StackDockStation) {
                        request.answer(null);
                    } else {
                        super.request(request);
                    }
                }
            };
        }

        @Override
        public DockTitleFactory getTitleFactory(DockController controller) {
            return titleFactory;
        }
    }

It looks the way I want it, but if I start dragging the tabs to other dock stations, they suddenly acquire title bars.

Thanks in advance.

Alla

What I need to do it bring up docked tabs in response to some user actions. I am switching over from another docking framework, so currently the tabs are identified by the component they are hosting.

Ok. I think both solutions - visiting and a reference - are valid options. Depends on your application what the better solution is.

By the way, is there a way to tell a DefaultDockable inside a StackDockStation to come to front and take focus? It does not have a toFront() method. Should I use DefaultCommonDockable instead?

I always advise to use the Common project because all the advanced features can only be found there. That would also mean, that you have access to the existing „toFront“ method of CDockable.

On the other hand, the „toFront“ method consists of only one line: „DockController.setFocusedDockable( dockable, null, false )“. Any code that has access to a Dockable and its controller is allowed to call that method.

Yeah, and while I am asking, what’s the best way to make a theme feel like Eclipse, but look like Smooth or Flat? I mean having tabs on top, no title bar on tabs and no bar on the left in StackedDockStation. I tried this:

It looks the way I want it, but if I start dragging the tabs to other dock stations, they suddenly acquire title bars.

I think you mean the title that appears below the mouse cursor and follow the cursor? This title is created by a DockableMovingImageFactory. You can override the method „getMovingImageFactory“ in your theme and return a factory that creates another kind of image (or no image at all (= null), if you prefer that).


        [...]
        
        // this one makes a screenshot of the Dockable but does not show a title.
        @Override
        public DockableMovingImageFactory getMovingImageFactory( DockController controller ){
        	return new ScreencaptureMovingImageFactory( new Dimension( 300, 200 ) );
        }

    }```

BTW. You might also be interested in the method “CDockable.setSingleTabShown”. If enabled, then a tab will appear for the Dockable even if there is only one choice to make (normally such tabs are not painted as they are not necessary).

Or if you are not using CDockables, have a look at “SingleTabDecider”, an interface that can decide for which Dockable an unnecessary tab should be shown.

I think you mean the title that appears below the mouse cursor and follow the cursor? This title is created by a DockableMovingImageFactory. You can override the method „getMovingImageFactory“ in your theme and return a factory that creates another kind of image (or no image at all (= null), if you prefer that).

I think I probably meant something else. :slight_smile: Here is a screenshot:

I meant the blue bar that says „TableView“ below the tabs. I’ll try CDockable.setSingleTabShown and returning null from TitleFactory.request unconditionally, but for that I need first to convert to CDockables from DefaultDockables I was using. Is there an easy way to do this with DefaultDockable?

Thanks for your help regarding selecting the tabs. dockController.setFocusedDockable() does the trick.

Is there a simple way to close a DefaultDockable? I know it is possible with DefaultSingleCDockable using setVisible().

Alla

Ok, well that title should disappear with the TitleFactory that returns always null.

I am a bit confused, are you using the Common framework or not?
If yes, then your application has to create CDockables and must not create any Dockables directly. Any Dockable will be an instance of CommonDockable, and a CommonDockable has a method “getDockable” that returns its CDockable.
If no, then I will change my answers a bit in the future.

About setSingleTabShown: in Core you would use the code below to set the value for all Dockabes in one line:

controller.getProperties().set( SingleTabDecider.SINGLE_TAB_DECIDER, SingleTabDecider.ALWAYS );```

About closing. "dockable.getDockParent().drag( dockable )" has a similar effect than closing. It does however not store the location of the closed item...
If using only the Core library you might be better of creating a DockFrontend and use its "hide" method (the DockFrontend is to be used as replacement to DockController).

Hello Beni,

Thanks for all your help, I got everything working the way I wanted.

I am not using Common framework, only the core.

Are you developing DockingFrames alone?

Alla

Ok, good to hear your problems are solved.

The most part (I’d say >= 95%) of the framework was developed by me. Other people (like Janni, Thomas, Andrei) wrote submodules like the EclipseTheme, the Glass-Tabs or did the maven integration.

Just found a new problem with my app. Hope you can help.

There are three docking stations - one in the bottom, taking the whole width of screen, and two on top of it, on the left and on the right. I create them like this:

        SplitDockStation station = new SplitDockStation();
/*...*/
        leftDock = new StackDockStation();
        bottomDock = new StackDockStation();
        rightDock = new StackDockStation();
/*...*/
        station.drop(rightDock, SplitDockProperty.EAST);
        station.drop(leftDock, SplitDockProperty.WEST);
        station.drop(bottomDock, SplitDockProperty.SOUTH);

The rightDock usually has multiple tabs. Two tabs are added at startup and remain open, and other tabs may be added in response to user actions. The tabs are added by calling addTab():

    public void addTab(Component tabContent, String title, Docks where, int index) {
        DefaultDockable dockable = new DefaultDockable(tabContent, title);
        tabMap.put(tabContent, dockable);
        StackDockStation station;

        switch (where) {
            case TOP:
                throw new RuntimeException("We don't have a top dock");
            case BOTTOM:
                station = bottomDock;
                break;
            case LEFT:
                station = leftDock;
                break;
            case RIGHT:
                station = rightDock;
                break;
            default:
                throw new IllegalArgumentException();
        }

        if (index == -1) {
            index = station.getDockableCount();
        }
        Dockable frontDockable = station.getFrontDockable();
        station.add(dockable, index);
        if (frontDockable != null) {
            station.setFrontDockable(frontDockable); // Adding a tab should not change the currently focused tab
            // call selectTab() if you want the newly added tab to be focused
        }
    }

This all works very well until a user starts dragging the tabs to other dock stations. After that, if a user causes a new tab to open, it, as far as I can see, is created, but isn’t visible.

Can you help me with that? I need the new tabs to show up when requested.

The framework automatically removes and/or replaces StackDockStations that have only one child, and your left/bottom/rightDock-stations will just be deleted after some time (once the user started moving around Dockables, the notion of „left“, „right“ etc. becomes meaningless anyway).

One solution could be to call „SplitDockStation.drop“ with your new dockable and a location like „SplitDockProperty.EAST“. This would place your new item somewhere at the east side of the station.

Another solution might be to find the location of a Dockable you already know (see „DockUtilities.getRoot“ and „DockUtilities.getPropertyChain“) and drop your new item at the same location.

I can post an example if you like, but you’ll have to wait until the evening for that :slight_smile:

[QUOTE=Beni]
I can post an example if you like, but you’ll have to wait until the evening for that :)[/QUOTE]

Thanks, I’ll wait for the example. Tried a bit, and can’t figure it out on my own.

Another question. How do I make a Dockable in FlapDockSation to show up in the flap? What I need to do is: when user selects an item from the menu, the Dockable inside FlapDockStation that currently is just a button opens up in the flap window. Sorry if I am not very clear, I read the docs, but I am still not very familiar with the terms.

Part 1: some examples how the position of a Dockable can be set (using Core only).

1: just drop it at the correct location
2: drop it over an existing location
3. predefine location for each Dockable using perspectives (that would be the advanced solution)

I admit, I’ve written these examples in a hurry… If you have only a limited set of Dockables, or know at least some Dockables in advance, then I would strongly suggest to use the 3. option for setting up some of the initial locations.

The answer to your second question will be available soon.


import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.station.split.SplitDockProperty;

/*
 * Method 1: easy, but results are not very good
 */
public class Dock77 {
	private static int index = 0;
	
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		DockFrontend frontend = new DockFrontend( frame );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		
		SplitDockStation station = new SplitDockStation();
		frontend.addRoot( "split", station );
		frame.add( station );
		
		frame.add( create( frontend, station, SplitDockProperty.NORTH ), BorderLayout.NORTH );
		frame.add( create( frontend, station, SplitDockProperty.SOUTH ), BorderLayout.SOUTH );
		frame.add( create( frontend, station, SplitDockProperty.EAST ), BorderLayout.EAST );
		frame.add( create( frontend, station, SplitDockProperty.WEST ), BorderLayout.WEST );
		
		frame.setBounds( 20, 20, 400, 400 );
		frame.setVisible( true );
	}
	
	private static JButton create( final DockFrontend frontend, final SplitDockStation station, final SplitDockProperty location ){
		JButton button = new JButton("add");
		button.addActionListener( new ActionListener(){
			@Override
			public void actionPerformed( ActionEvent e ){
				DefaultDockable dockable = new DefaultDockable( "Title " + index );
				frontend.addDockable( "id " + index, dockable );
				station.drop( dockable, location );
				index++;
			}
		});
		return button;
	}
}```
```package test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.DockStation;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.util.DockUtilities;

/*
 * Method 2: take a Dockable whose location you already know and duplicate that location
 */
public class Dock78 {
	private static int index = 0;
	
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		DockFrontend frontend = new DockFrontend( frame );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		
		SplitDockStation station = new SplitDockStation();
		frontend.addRoot( "split", station );
		frame.add( station );
		
		station.drop( new CustomDockable( frontend ) );
		
		frame.setBounds( 20, 20, 400, 400 );
		frame.setVisible( true );
	}
	
	private static class CustomDockable extends DefaultDockable{
		public CustomDockable( final DockFrontend frontend ){
			super( "Title " + index );
			frontend.addDockable( "id " + index, this );
			index++;
			
			JButton button = new JButton( "clone" );
			button.addActionListener( new ActionListener(){
				@Override
				public void actionPerformed( ActionEvent e ){
					DockStation root = DockUtilities.getRoot( CustomDockable.this );
					DockableProperty location = DockUtilities.getPropertyChain( root, CustomDockable.this );
					
					CustomDockable copy = new CustomDockable( frontend );
					root.drop( copy, location );
				}
			});
			add( button );
		}
	}
}```
```package test;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.frontend.FrontendDockablePerspective;
import bibliothek.gui.dock.frontend.Setting;
import bibliothek.gui.dock.layout.LocationEstimationMap;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.perspective.PredefinedPerspective;
import bibliothek.gui.dock.station.split.PerspectiveSplitDockGrid;
import bibliothek.gui.dock.station.split.SplitDockPerspective;
import bibliothek.gui.dock.station.split.SplitDockPlaceholderProperty;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.support.PlaceholderStrategyListener;
import bibliothek.util.Path;
import bibliothek.util.xml.XElement;

/*
 * Method 3: if you know some of the Dockables in advance, you can make use of perspectives to set their initial position
 */
public class Dock79 {
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		final DockFrontend frontend = new DockFrontend( frame );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		
		SplitDockStation station = new SplitDockStation();
		frontend.addRoot( "split", station );
		frame.add( station );
		
		frontend.registerFactory( new CustomDockableFactory(), true );
		
		JMenu menu = new JMenu( "Dockables" );
		menu.add( createItem( frontend, "a" ) );
		menu.add( createItem( frontend, "b" ) );
		menu.add( createItem( frontend, "c" ) );
		menu.add( createItem( frontend, "d" ) );
		menu.add( createItem( frontend, "e" ) );
		menu.add( createItem( frontend, "f" ) );
		menu.add( createItem( frontend, "g" ) );
		menu.add( createItem( frontend, "h" ) );
		JMenuBar bar = new JMenuBar();
		bar.add( menu );
		frame.setJMenuBar( bar );
		
		// enable placeholders
		allowPlaceholders( frontend );
		
		// setup the layout
		setupInitialLayout( frontend );
		
		frame.setBounds( 20, 20, 400, 400 );
		frame.setVisible( true );
	}
	
	private static JMenuItem createItem( final DockFrontend frontend, final String id ){
		JMenuItem item = new JMenuItem( id );
		item.addActionListener(  new ActionListener(){
			@Override
			public void actionPerformed( ActionEvent e ){
				// switches the visibility of the dockable with id "id". Create new dockables if required
				
				Dockable dockable = frontend.getDockable( id );
				if( dockable == null ){
					dockable = new CustomDockable( id );
					
					// new dockables need to be registered...
					frontend.addDockable( id, dockable );
					// ... and their initial location must be associated with the correct placeholder
					frontend.getFrontendEntry( id ).setLocation( "split", new SplitDockPlaceholderProperty( new Path( "custom", id ) ) );
					frontend.setHideable( dockable, true );
				}
				if( frontend.isShown( dockable )){
					frontend.hide( dockable );
				}
				else{
					frontend.show( dockable );
				}
			}
		});
		return item;
	}
	
	private static void allowPlaceholders( final DockFrontend frontend ){
		// add a simple placeholder-strategy that enables all placeholders
		frontend.getController().getProperties().set( PlaceholderStrategy.PLACEHOLDER_STRATEGY, new PlaceholderStrategy(){
			@Override
			public void uninstall( DockStation station ){
				// ignore
			}
			
			@Override
			public void removeListener( PlaceholderStrategyListener listener ){
				// ignore
			}
			
			@Override
			public boolean isValidPlaceholder( Path placeholder ){
				return true;
			}
			
			@Override
			public void install( DockStation station ){
				// ignore	
			}
			
			@Override
			public Path getPlaceholderFor( Dockable dockable ){
				String name = frontend.getNameOf( dockable );
				if( name == null ){
					return null;
				}
				return new Path( "custom", name );
			}
			
			@Override
			public void addListener( PlaceholderStrategyListener listener ){
				// ignore
			}
		});
	}
	
	private static void setupInitialLayout( DockFrontend frontend ){
		// Admitted, the next two lines are a hack...
		PredefinedPerspective perspective = (PredefinedPerspective)frontend.getPerspective( false );
		SplitDockPerspective split = (SplitDockPerspective)perspective.get( "rootsplit" );
		
		// build up the initial layout
		PerspectiveSplitDockGrid grid = new PerspectiveSplitDockGrid();
		
		grid.addDockable( 0, 0, 1, 1, new CustomDockablePerspective( "a" ) );
		grid.addDockable( 0, 0, 1, 1, new CustomDockablePerspective( "b" ) );
		grid.addPlaceholders( 0, 0, 1, 1, new Path( "custom", "c" ) );
		grid.addPlaceholders( 0, 0, 1, 1, new Path( "custom", "d" ) );
		
		grid.addDockable( 1, 0, 1, 1, new CustomDockablePerspective( "e" ) );
		grid.addDockable( 1, 0, 1, 1, new CustomDockablePerspective( "f" ) );
		grid.addPlaceholders( 1, 0, 1, 1, new Path( "custom", "g" ) );
		grid.addPlaceholders( 1, 0, 1, 1, new Path( "custom", "h" ) );		
	
		// deploy layout on station
		split.read( grid.toTree(), null );
		
		// convert the perspective into the intermediate format and apply the layout
		Setting setting = new Setting();
		
		setting.putRoot( "split", perspective.convert( split ) );
		
		frontend.setSetting( setting, false );
		
		for( Dockable dockable : frontend.listDockables() ){
			frontend.setHideable( dockable, true );
		}
	}
	
	// a simple representation of our CustomDockable
	private static class CustomDockablePerspective extends FrontendDockablePerspective{
		public CustomDockablePerspective( String id ){
			super( id );
		}
		
		@Override
		public String getFactoryID(){
			return "custom";
		}
	}
	
	// Our custom implementation of a dockable, notice that it has a custom "factory id"
	private static class CustomDockable extends DefaultDockable{
		private String id;
		
		public CustomDockable( String id ){
			setTitleText( "Title " + id );
			setFactoryID( "custom" );
			this.id = id;
		}
		
		public String getId(){
			return id;
		}
	}
	
	// this factory will read/write instances of our CustomDockable
	private static class CustomDockableFactory implements DockFactory<CustomDockable, CustomDockablePerspective, String>{

		@Override
		public void estimateLocations( String layout, LocationEstimationMap children ){
			// ignore
		}

		@Override
		public CustomDockable layout( String layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
			return new CustomDockable( layout );
		}

		@Override
		public CustomDockable layout( String layout, PlaceholderStrategy placeholders ){
			return new CustomDockable( layout );
		}

		@Override
		public CustomDockablePerspective layoutPerspective( String layout, Map<Integer, PerspectiveDockable> children ){
			return new CustomDockablePerspective( layout );
		}

		@Override
		public void layoutPerspective( CustomDockablePerspective perspective, String layout, Map<Integer, PerspectiveDockable> children ){
			// nothing to do
		}

		@Override
		public String getID(){
			return "custom";
		}

		@Override
		public String getLayout( CustomDockable element, Map<Dockable, Integer> children ){
			return element.getId();
		}

		@Override
		public String getPerspectiveLayout( CustomDockablePerspective element, Map<PerspectiveDockable, Integer> children ){
			return element.getId();
		}

		@Override
		public String read( DataInputStream in, PlaceholderStrategy placeholders ) throws IOException{
			return in.readUTF();
		}

		@Override
		public String read( XElement element, PlaceholderStrategy placeholders ){
			return element.getString();
		}

		@Override
		public void setLayout( CustomDockable element, String layout, Map<Integer, Dockable> children, PlaceholderStrategy placeholders ){
			// nothing to do
		}

		@Override
		public void setLayout( CustomDockable element, String layout, PlaceholderStrategy placeholders ){
			// nothing to do
		}

		@Override
		public void write( String layout, DataOutputStream out ) throws IOException{
			out.writeUTF( layout );
		}

		@Override
		public void write( String layout, XElement element ){
			element.setString( layout );
		}
	}
}```

Part 2

As for the question about FlapDockStation:

  • You can focus the Dockable using DockController.setFocusedDockable. As soon as the Dockable has the focus it should pop up automatically
  • You can call “FlapDockStation.setFrontDockable” which will pop up the Dockable as well. However, the behavior is unspecified if the Dockable is not “sticked”, the window may just collapse again. You should call “FlapDockStation.setHold( dockable, true );” first, that will allow the Dockable to remain visible even if it is not focused.

Hello Beni,

Thanks a lot! That’s a lot of work you did. Do you accept donations for this project? Otherwise if you happen to pass by Belgium, let me know at alla [at] gremwell [dot] com, I owe you a beer (probably more than one ;)).

I implemented the second solution because it is the only one I managed to understand. :frowning:

Showing the dockable in FlapDockStation also works.

Cheers,
Alla

Donations: telling people about the framework (e.g. in a forum or a blog) is a nice way to say thanks. :wink:

What do you not understand? Maybe I can explain…