CWorkingAreas and perspectives

Hi Beni, hi everyone,

In my application, I have a perspective/working areas manager but I can’t figure how to make it work properly.

I have a two maps storing the perspectives and the working areas defined as follow:

Map<String, CWorkingPerspective> workingAreasMap;

Then I have a method to create a new working area for a given perspective

        CPerspective perspective =this.perspectivesMap.get(perspectiveName);
        if (perspective == null) {
            perspective = this.perspectives.createEmptyPerspective();
            this.perspectivesMap.put(perspectiveName, perspective);
        }

        this.control.createWorkingArea(workName);

        final CWorkingPerspective work = (CWorkingPerspective) perspective.getStation(workName);

        final CGridPerspective center = perspective.getContentArea().getCenter();
        center.gridAdd(viewPosition.getX(), viewPosition.getY(), viewPosition.getWidth(), viewPosition.getHeight(),
                work);

        return work;
    }

However, (CWorkingPerspective) perspective.getStation(workName); returns null.

What did I miss?

Thanks for your help.

Ben

You do create the perspective before you create the CWorkingArea. During creation the CPerspective is filled with all the stations that currently exist, but your working-area does not (yet) exist…

P.S. I do not know what your maps do, but they may not be needed.
CControlPerspective has a some nice methods like “setPerspective(String,CPerspective)”, “rename”, etc… with these methods you can store perspectives in a map. Or to be more precise: the perspectives are converted into the “intermediate layout format”, and you can apply these stored layouts using “CControl.load(String)”. Changes in the layouts will automatically be stored, and the all the layouts can be written into a file using “CControl.write/readXML”.

So I should just create the CWorkingArea before the perspective?

Yes. That would be a solution that should work.

When working with perspectives, I prefere to install some factories that create stations and dockables automatically when they are needed. You can find an example below using two different perspectives.

package test;

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

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

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CWorkingArea;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;
import bibliothek.gui.dock.common.perspective.CControlPerspective;
import bibliothek.gui.dock.common.perspective.CGridPerspective;
import bibliothek.gui.dock.common.perspective.CPerspective;
import bibliothek.gui.dock.common.perspective.CWorkingPerspective;
import bibliothek.gui.dock.common.perspective.SingleCDockablePerspective;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.util.filter.RegexFilter;

public class PerspectiveTest {
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 400, 400 );
		CControl control = new CControl(frame);
		control.setTheme( ThemeMap.KEY_FLAT_THEME );
		
		// with this menu we can load old layouts
		JMenuBar menuBar = new JMenuBar();
		frame.setJMenuBar( menuBar );
		JMenu menu = new JMenu( "Layouts" );
		menu.add( load( "a", control ) );
		menu.add( load( "b", control ) );
		menuBar.add( menu );
		
		// do not change (or even save) the content of working-areas
		control.setIgnoreWorkingForEntry( true );
		
		// register some factories that will create missing dockables or stations
		control.addSingleDockableFactory( "work", workingAreaFactory( control ) );
		control.addSingleDockableFactory( new RegexFilter( ".+" ), dockableFactory( control ) );
		
		frame.add(control.getContentArea());
		
		// at this point we do set up two completely independent perspectives
		CControlPerspective perspectives = control.getPerspectives();
		CPerspective perspectiveA = perspectives.createEmptyPerspective();
		CPerspective perspectiveB = perspectives.createEmptyPerspective();
		fillPerspectiveA( perspectiveA );
		fillPerspectiveB( perspectiveB );
		perspectives.setPerspective( "a", perspectiveA );
		perspectives.setPerspective( "b", perspectiveB );
		
		// we apply one perspective to setup the initial layout. The parameter "true" indicates, that we
		// want to setup the content of the working-area as well.
		perspectives.setPerspective( perspectiveA, true );
		
		frame.setVisible( true );
	}
	
	private static void fillPerspectiveA( CPerspective perspective ){
		CWorkingPerspective working = new CWorkingPerspective( "work" );
		perspective.addStation( working );
		
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "inside 1", working ) );
		working.gridAdd( 1, 0, 1, 1, createDockablePerspective( "inside 2", working ) );
		working.gridDeploy();
		
		CGridPerspective center = perspective.getContentArea().getCenter();
		center.gridAdd( 1, 0, 1, 2, working );
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "outside 1", null ));
		center.gridAdd( 0, 1, 1, 1, createDockablePerspective( "outside 2", null ));
		center.gridDeploy();	
	}
	
	private static void fillPerspectiveB( CPerspective perspective ){
		CWorkingPerspective working = new CWorkingPerspective( "work" );
		perspective.addStation( working );
		
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "inside 1", working ) );
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "inside 2", working ) );
		working.gridDeploy();
		
		CGridPerspective center = perspective.getContentArea().getCenter();
		center.gridAdd( 1, 0, 1, 1, working );
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "outside 1", null ));
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "outside 2", null ));
		center.gridDeploy();
	}
	
	private static SingleCDockablePerspective createDockablePerspective( String id, CWorkingPerspective working ){
		 SingleCDockablePerspective result = new SingleCDockablePerspective( id );
		 result.setWorkingArea( working );
		 return result;
	}
	
	private static SingleCDockableFactory workingAreaFactory( final CControl control ){
		return new SingleCDockableFactory() {
			@Override
			public SingleCDockable createBackup( String id ) {
				CWorkingArea working = new CWorkingArea( control, id );
				working.setTitleShown( true );
				working.setTitleText( "I am the working area" );
				return working;
			}
		};
	}
	
	private static SingleCDockableFactory dockableFactory( CControl control ){
		return new SingleCDockableFactory() {
			@Override
			public SingleCDockable createBackup( String id ) {
				DefaultSingleCDockable dockable = new DefaultSingleCDockable( id );
				dockable.setTitleText( "Identifier: " + id );
				return dockable;
			}
		};
	}
	
	private static JMenuItem load( final String id, final CControl control ){
		JMenuItem item = new JMenuItem( "Load " + id );
		item.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				// if we want, we can save the current layout
				// control.save();
				
				control.load( id );	
			}
		} );
		return item;
	}
}

Hi, and thank you very much for the code snippet.

I used it exactly as displayed above with version 1.1.1 of docking frames and got the following error:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: -2
	at java.lang.String.substring(String.java:1937)
	at java.lang.String.substring(String.java:1904)
	at bibliothek.gui.dock.common.intern.DefaultCControlRegister.multiToNormalId(DefaultCControlRegister.java:264)
	at bibliothek.gui.dock.common.CControl.shouldStore(CControl.java:2062)
	at bibliothek.gui.dock.common.CControl.access$100(CControl.java:216)
	at bibliothek.gui.dock.common.CControl$1.shouldStoreShown(CControl.java:495)
	at bibliothek.gui.dock.frontend.DefaultLayoutChangeStrategy.applyLayout(DefaultLayoutChangeStrategy.java:288)
	at bibliothek.gui.dock.frontend.DefaultLayoutChangeStrategy.setLayout(DefaultLayoutChangeStrategy.java:97)
	at bibliothek.gui.DockFrontend.setSetting(DockFrontend.java:1509)
	at bibliothek.gui.dock.common.intern.CDockFrontend.access$001(CDockFrontend.java:48)
	at bibliothek.gui.dock.common.intern.CDockFrontend$1.run(CDockFrontend.java:162)
	at bibliothek.gui.dock.support.mode.ModeManager.runTransaction(ModeManager.java:512)
	at bibliothek.gui.dock.facile.mode.LocationModeManager.runLayoutTransaction(LocationModeManager.java:494)
	at bibliothek.gui.dock.common.intern.CDockFrontend.setSetting(CDockFrontend.java:160)
	at bibliothek.gui.dock.common.perspective.CControlPerspective.setPerspective(CControlPerspective.java:159)
	at test.PerspectiveTest.main(PerspectiveTest.java:60)

Line 60 is:
perspectives.setPerspective(perspectiveA, true);

Is this something that has been fixed in more recent versions?

That version is over a year old! Try the newest version. It contains a few bugfixes… :wink: (ok, it contains a lot bugfixes). The API of 1.1.2 and 1.1.1 are similar, you should not have much issues to switch. You can find the newest version here.

I upgraded to version 1.1.2p9c and it works.

Thanks for your help :slight_smile:

Back at your example.

It runs, but its behaviour is not what I expected:

  • Perspective A shows two dockables on the left side of the frame (called “outside 1” and “outside 2”, vertically aligned). On the right, there is the working area called “I am the working area” with two dockables “inside 1” and “inside 2”, horizontally aligned.

  • Perspective B modifies the left side of the frame, where “outside 1” and “outside 2” are on top of each other. They are called the same, but I guess they are different instances (are they?). The working area is unchanged.

I modified the code as follows:

public class PerspectiveTest {
    public static void main(final String[] args) {
        [... same as your example...]

        // register some factories that will create missing dockables or stations
        control.addSingleDockableFactory("workA", workingAreaAFactory(control));
        control.addSingleDockableFactory("workB", workingAreaBFactory(control));
        control.addSingleDockableFactory(new RegexFilter(".+"), dockableFactory(control));

        [... same as your example...]
    }

    private static void fillPerspectiveA(final CPerspective perspective) {
        final CWorkingPerspective working = new CWorkingPerspective("workA");
        perspective.addStation(working);

        working.gridAdd(0, 0, 1, 1, createDockablePerspective("inside 1A", working));
        working.gridAdd(1, 0, 1, 1, createDockablePerspective("inside 2A", working));
        working.gridDeploy();

        final CGridPerspective center = perspective.getContentArea().getCenter();
        center.gridAdd(1, 0, 1, 2, working);
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 1A", null));
        center.gridAdd(0, 1, 1, 1, createDockablePerspective("outside 2A", null));
        center.gridDeploy();
    }

    private static void fillPerspectiveB(final CPerspective perspective) {
        final CWorkingPerspective working = new CWorkingPerspective("workB");
        perspective.addStation(working);

        working.gridAdd(0, 0, 1, 1, createDockablePerspective("inside 1B", working));
        working.gridAdd(0, 0, 1, 1, createDockablePerspective("inside 2B", working));
        working.gridDeploy();

        final CGridPerspective center = perspective.getContentArea().getCenter();
        center.gridAdd(1, 0, 1, 1, working);
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 1B", null));
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 2B", null));
        center.gridDeploy();
    }

    private static SingleCDockablePerspective createDockablePerspective(final String id,
            final CWorkingPerspective working) {
        [... same as your example...]
    }

    private static SingleCDockableFactory workingAreaAFactory(final CControl control) {
        return new SingleCDockableFactory() {
            @Override
            public SingleCDockable createBackup(final String id) {
                final CWorkingArea working = new CWorkingArea(control, id);
                working.setTitleShown(true);
                working.setTitleText("I am the working area A");
                return working;
            }
        };
    }

    private static SingleCDockableFactory workingAreaBFactory(final CControl control) {
        return new SingleCDockableFactory() {
            @Override
            public SingleCDockable createBackup(final String id) {
                final CWorkingArea working = new CWorkingArea(control, id);
                working.setTitleShown(true);
                working.setTitleText("I am the working area B");
                return working;
            }
        };
    }

    private static SingleCDockableFactory dockableFactory(final CControl control) {
        [... same as your example...]
    }

    private static JMenuItem load(final String id, final CControl control) {
        [... same as your example...]
    }
}

What I want to obtain is:

  • Perspective A shows two dockables on the left side, renamed “outside 1A” and “outside 2A”. The working area “I am the working area A” contains two dockables “inside 1A” and “inside 2A”
  • Perspective B shows two dockables on the left side, named “outside 1B” and “outside 2B”, on top of each other. The working area “I am the working area B” contains two dockables “inside 1B” and “inside 2B”

But the result is not what I expected:

  • Perspective A is correct
  • Perspective B still has working area A displayed, and no dockables in it. The rest is correct (cf. screenshot).

How can I achieve what I described above?

But your screenshot shows working area B, and I see the same when I execute your application.

The default behavior of the framework is to ignore the contents of working areas. The reason for this behavior is, that working areas usually contain open documents (e.g. editors showing some text files). And the user probably does not want to close some files and open other files just because he loads a new layout.

But this behavior can be changed:
[ul]
[li]Call control.setIgnoreWorkingForEntry, it will ensure that the conversion from perspectives to the intermediate layout format does not delete working ares[/li][li]When loading (or saving) a layout, use the methods with the parameter includeWorkingAreas and set it to true[/li][/ul]

Your application will look a little bit like this:

	public static void main( String[] args ) {
		... as before
		
		// do change (and save) the content of working-areas
		control.setIgnoreWorkingForEntry( false );
		
		// register some factories that will create missing dockables or stations
		... as before
	}
	
    private static void fillPerspectiveA(final CPerspective perspective) {
		... as before
    }

    private static void fillPerspectiveB(final CPerspective perspective) {
		... as before
    }
	
	... as before
    
	private static JMenuItem load( final String id, final CControl control ){
		JMenuItem item = new JMenuItem( "Load " + id );
		item.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				// if we want, we can save the current layout
				// control.save();
				
				// <<<<*****>>> here is a change
				control.load( id, true );
				// <<<<*****>>> here is a change
			}
		} );
		return item;
	}
}

Indeed, I meant working area B.

I changed the code as you mentioned, and it works, thanks!

Will try to integrate it in my app now :slight_smile:

Hi Beni,

I had to work on another project for the last few weeks. I’m now back at this working area issue and trying new things.

I added in the menu an item to dynamically modify a working area (adding new tabs in the working area). Here is what I did.

final JMenuItem item = new JMenuItem("Add panel to Working area B");
item.addActionListener(new ActionListener() {

@Override
public void actionPerformed(final ActionEvent arg0) {
    final CWorkingPerspective workingB = new CWorkingPerspective("workB");
    perspectiveB.addStation(workingB);

    workingB.gridAdd(0, 0, 1, 1, createDockablePerspective("Panel #" + counter.incrementAndGet(), workingB));
    workingB.gridDeploy();
    }
});
menu.add(item);

Unfortunately, this does not do anything. I really have difficulties understanding the logic of the working areas, sorry about that. It would be interesting to have it detailed a bit more in the user manual.

Kind regards,

Ben

Dockable and DockStations build a set of trees, Dockables are the leafs, DockStations are the roots and the nodes. An example for a root could be the ScreenDockStation which represents the entire screen. An example for a node could be the StackDockStation which represents a stack of items, and paints tabs.

A working-area is just another kind of node. The characteristics of a working-area are:
[ul]
[li]A dockable can be associated with a working-area. If a dockable is associated with a working-area, then it must always be a child of the working-area - or in a special state like “minimized” or “maximized”.
[/li][li]Changing the layout using methods like “CControl.load” does not affect the children of a working-area.
[/li][li]Changing the layout does not create or replace working-areas, neither are Dockable or root-stations replaced - they are just put to new positions.
[/li][/ul]
That’s it, there is nothing more behind working-areas.

But you seem also to mix up the things you see on the screen, and the perspectives.
[ul]
[li]What you see on the screen are DockStations and Dockables.
[/li][li]The word “layout” means the location and size of all the Dockables - but layout is an abstraction, there is no class “layout”, it is just a concept.
[/li][li]What you see on the screen is one instance of a layout, described by actual Swing Components
[/li][li]Perspectives are another kind of layout, this time there are no Swing Components involved, but much simpler objects - that take less time to construct and less memory
[/li][/ul]

If you want to add show a new Dockable together with the existing Dockables, then the easiest way is just to add them. No need to make complex stuff with perspectives:
[ul]
[li]Access your working-area (you may use CControl.getStation to search for it)
[/li][li]Use CWorkingArea.show to add and show the new item
[/li][/ul]
Finally: go to the Docking Frames page, go to “Tutorial” and hit “Launch”. In the application that opens search for “Common API > Basics > Opening a CDockable on a CWorkingArea”. It is a simple example and I hope it helps you.

Thanks for your answer.

What I see in the tutorial “Opening a CDockable on a CWorkingArea” is exactly what I want, but I really want this in a perspective to be able to switch between perspectives.

To be accurate, what I want to do is :

  • A perspective #1 that does exactly the same than the example : some “fixed” dockables at the left (e.g. outline) and bottom (e.g. console) and a working area where new editors can be dynamically added (text editors for instance)
  • A perspective #2 with other fixed dockables (e.g. Package explorer at the right and Search tab on top) and a working area with other types of editors (say 2D viewers).
  • etc.

I want to be able to switch between both perspectives and have the “layout” remembered and the opened editors still opened.

This is why I want to use perspectives, but then you don’t manipulate CGrid objects anymore but CGridPerspectives instead and this is very confusing for me. In my mind, I thought I could add dockables to a perspective the exact same way I add them to CControl.

So basically you want to have two completely independent layouts? Then you can tell the CControl to save the layout of the working-areas as well using “setIgnoreWorkingForEntry”. And if you want to add dockables two both layouts then you could use the perspective API.

Perhaps this example app is closer to what you want to achieve? It has two layouts, with different editors - but you can add a “shared” editor if you want. You will need to upgrade the framwork to 1.1.2p10a for this properly work - sorry, I found a little bug…


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

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

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CWorkingArea;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.SingleCDockableFactory;
import bibliothek.gui.dock.common.perspective.CControlPerspective;
import bibliothek.gui.dock.common.perspective.CGridPerspective;
import bibliothek.gui.dock.common.perspective.CPerspective;
import bibliothek.gui.dock.common.perspective.CWorkingPerspective;
import bibliothek.gui.dock.common.perspective.SingleCDockablePerspective;
import bibliothek.util.filter.RegexFilter;

public class PerspectiveTest3 {
	private static int next = 0;
	
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		CControl control = new CControl( frame );
		frame.setBounds( 20, 20, 800, 600 );
		frame.add( control.getContentArea() );
		
		// with this menu we can load old layouts
		JMenuBar menuBar = new JMenuBar();
		frame.setJMenuBar( menuBar );
		JMenu menu = new JMenu( "Layouts" );
		menu.add( load( "a", control ) );
		menu.add( load( "b", control ) );
		menu.add( share( control ) );
		menuBar.add( menu );
		
		// do not change (or even save) the content of working-areas
		// control.setIgnoreWorkingForEntry( true );
		control.setIgnoreWorkingForEntry( false );
		
		// register some factories that will create missing dockables or stations
		control.addSingleDockableFactory( "work", workingAreaFactory( control ) );
		control.addSingleDockableFactory( new RegexFilter( ".+" ), dockableFactory( control ) );
		
		frame.add(control.getContentArea());
		
		// at this point we do set up two completely independent perspectives
		CControlPerspective perspectives = control.getPerspectives();
		CPerspective perspectiveA = perspectives.createEmptyPerspective();
		CPerspective perspectiveB = perspectives.createEmptyPerspective();
		fillPerspectiveA( perspectiveA );
		fillPerspectiveB( perspectiveB );
		perspectives.setPerspective( "a", perspectiveA );
		perspectives.setPerspective( "b", perspectiveB );
		
		// we apply one perspective to setup the initial layout. The parameter "true" indicates, that we
		// want to setup the content of the working-area as well.
		perspectives.setPerspective( perspectiveA, true );
		
		frame.setVisible( true );
	}
	
	private static void addShared( CControl control ){
		String randomId = "shared " + (++next);
		
		// save current perspective
		
		CControlPerspective perspectives = control.getPerspectives();
		for( String name : perspectives.getNames() ){
			CPerspective perspective = perspectives.getPerspective( name, true );
			addSharedToPerspectve( perspective, randomId );
			perspectives.setPerspective( name, perspective );
		}
		
		CPerspective current = perspectives.getPerspective( true );
		addSharedToPerspectve( current, randomId );
		perspectives.setPerspective( current, true );
	}
	
	private static void addSharedToPerspectve( CPerspective perspective, String randomId ){
		CWorkingPerspective working = (CWorkingPerspective)perspective.getStation( "work" );
		working.gridAdd( 0, 0, 100, 300, createDockablePerspective( randomId, working ) );
		working.gridDeploy();
	}

	private static void fillPerspectiveA( CPerspective perspective ){
		CWorkingPerspective working = new CWorkingPerspective( "work" );
		perspective.addStation( working );
		
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "text editor 1", working ) );
		working.gridAdd( 1, 0, 1, 1, createDockablePerspective( "text editor 2", working ) );
		working.gridDeploy();
		
		CGridPerspective center = perspective.getContentArea().getCenter();
		center.gridAdd( 1, 0, 1, 2, working );
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "text 1", null ));
		center.gridAdd( 0, 1, 1, 1, createDockablePerspective( "text 2", null ));
		center.gridDeploy();	
	}
	
	private static void fillPerspectiveB( CPerspective perspective ){
		CWorkingPerspective working = new CWorkingPerspective( "work" );
		perspective.addStation( working );
		
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "shape editor 1", working ) );
		working.gridAdd( 0, 0, 1, 1, createDockablePerspective( "shape editor 2", working ) );
		working.gridDeploy();
		
		CGridPerspective center = perspective.getContentArea().getCenter();
		center.gridAdd( 1, 0, 1, 1, working );
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "shape 1", null ));
		center.gridAdd( 0, 0, 1, 1, createDockablePerspective( "shape 2", null ));
		center.gridDeploy();
	}
	
	private static SingleCDockablePerspective createDockablePerspective( String id, CWorkingPerspective working ){
		 SingleCDockablePerspective result = new SingleCDockablePerspective( id );
		 result.setWorkingArea( working );
		 return result;
	}
	
	private static SingleCDockableFactory workingAreaFactory( final CControl control ){
		return new SingleCDockableFactory() {
			@Override
			public SingleCDockable createBackup( String id ) {
				CWorkingArea working = new CWorkingArea( control, id );
				working.setTitleShown( true );
				working.setTitleText( "I am the working area" );
				return working;
			}
		};
	}
	
	private static SingleCDockableFactory dockableFactory( CControl control ){
		return new SingleCDockableFactory() {
			@Override
			public SingleCDockable createBackup( String id ) {
				DefaultSingleCDockable dockable = new DefaultSingleCDockable( id );
				dockable.setTitleText( "Identifier: " + id );
				return dockable;
			}
		};
	}
	
	private static JMenuItem load( final String id, final CControl control ){
		JMenuItem item = new JMenuItem( "Load " + id );
		item.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				// if we want, we can save the current layout
				control.save();
				control.load( id );	
			}
		} );
		return item;
	}
	
	private static JMenuItem share( final CControl control ){
		JMenuItem item = new JMenuItem( "Add shared dockable" );
		item.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed( ActionEvent e ) {
				addShared( control );	
			}
		} );
		return item;
	}
}

Hi,

I tried your example and it is much better. I still have some issues, but maybe I’m not using DockingFrames the proper way.

Here is the code, I describe the issues afterwards:

public class PerspectiveTest {
    private static final AtomicInteger counter = new AtomicInteger();

    public static void main(final String[] args) {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBounds(20, 20, 1200, 600);
        final CControl control = new CControl(frame);
        final ThemeMap theme = control.getThemes();
        theme.select(ThemeMap.KEY_ECLIPSE_THEME);

        // with this menu we can load old layouts
        final JMenuBar menuBar = new JMenuBar();
        frame.setJMenuBar(menuBar);
        final JMenu menu = new JMenu("Layouts");
        menu.add(load("a", control));
        menu.add(load("b", control));
        menuBar.add(menu);

        // do not change (or even save) the content of working-areas
        control.setIgnoreWorkingForEntry(false);

        // register some factories that will create missing dockables or stations
        control.addSingleDockableFactory("work", workingAreaFactory(control));
        control.addSingleDockableFactory(new RegexFilter(".+"), dockableFactory(control));

        frame.add(control.getContentArea());

        // at this point we do set up two completely independent perspectives
        final CControlPerspective perspectives = control.getPerspectives();
        final CPerspective perspectiveA = perspectives.createEmptyPerspective();
        final CPerspective perspectiveB = perspectives.createEmptyPerspective();
        fillPerspectiveA(perspectiveA);
        fillPerspectiveB(perspectiveB);
        perspectives.setPerspective("a", perspectiveA);
        perspectives.setPerspective("b", perspectiveB);

        final JMenuItem item = new JMenuItem("Add panel to Working area A");
        item.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent arg0) {
                final CWorkingPerspective workingB = (CWorkingPerspective) perspectiveA.getStation("work");

                workingB.gridAdd(0, 0, 1, 1,
                        createDockablePerspective("Panel A#" + counter.incrementAndGet(), workingB));
                perspectives.setPerspective("a", perspectiveA);
                control.load("a");
            }
        });
        menu.add(item);

        // we apply one perspective to setup the initial layout. The parameter "true" indicates, that we
        // want to setup the content of the working-area as well.
        perspectives.setPerspective(perspectiveA, true);

        frame.setVisible(true);
    }

    private static void fillPerspectiveA(final CPerspective perspective) {
        final CWorkingPerspective workingA = new CWorkingPerspective("work");
        perspective.addStation(workingA);
        workingA.gridDeploy();

        final CGridPerspective center = perspective.getContentArea().getCenter();
        center.gridAdd(1, 0, 1, 2, workingA);
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 1A", null));
        center.gridAdd(0, 1, 1, 1, createDockablePerspective("outside 2A", null));
        center.gridDeploy();
    }

    private static void fillPerspectiveB(final CPerspective perspective) {
        final CWorkingPerspective workingB = new CWorkingPerspective("work");
        perspective.addStation(workingB);

        workingB.gridDeploy();

        final CGridPerspective center = perspective.getContentArea().getCenter();
        center.gridAdd(1, 0, 1, 1, workingB);
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 1B", null));
        center.gridAdd(0, 0, 1, 1, createDockablePerspective("outside 2B", null));
        center.gridDeploy();
    }

    private static SingleCDockablePerspective createDockablePerspective(final String id,
            final CWorkingPerspective working) {
        final SingleCDockablePerspective result = new SingleCDockablePerspective(id);
        result.setWorkingArea(working);
        return result;
    }

    private static SingleCDockableFactory workingAreaFactory(final CControl control) {
        return new SingleCDockableFactory() {
            @Override
            public SingleCDockable createBackup(final String id) {
                final CWorkingArea working = new CWorkingArea(control, id);
                working.setTitleShown(true);
                working.setTitleText("I am the working area");
                return working;
            }
        };
    }

    private static SingleCDockableFactory dockableFactory(final CControl control) {
        return new SingleCDockableFactory() {
            @Override
            public SingleCDockable createBackup(final String id) {
                final DefaultSingleCDockable dockable = new DefaultSingleCDockable(id);
                dockable.setCloseable(true);
                dockable.setTitleText("Identifier: " + id);
                return dockable;
            }
        };
    }

    private static JMenuItem load(final String id, final CControl control) {
        final JMenuItem item = new JMenuItem("Load " + id);
        item.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                // if we want, we can save the current layout
                control.save();

                control.load(id);
            }
        });
        return item;
    }
}

This is the use case I made of this code and why it does not completely answer my problem:

  1. Open the application. Perspective A is shown. Two dockables on the left, an empty working area on the right
  2. In the menu, add one panel to working area of perspective A (via the menu).
  • It is added in the working area → OK
  1. Resize the working area (make it 1/4 of total width for instance)
  2. In the menu, switch to Perspective B and go back to perspective A.
  • The working area size has been saved → OK
  • The PanelA#1 is still there → OK
  1. Now add 2 more panels :
  • Panel A#2 and A#3 are added → OK
  • The width of the working area is resize to 50% of the total width → not OK, I set the width to 1/4
  1. Close panels A#1 and A#2
  2. In the menu, switch to Perspective B and go back to perspective A
  • Only panel A#3 is displayed → OK
  1. Add a new panel
  • The PanelA#4 is added → OK
  • Panels A#1 and A#2 are back → not OK, I closed them, I don’t want them to come back :slight_smile:

Thanks for your help
Kind regards

Both number 5 and 8 are caused by the same misunderstanding.

The misunderstanding is, that modifications made by the user are automatically forwarded to the CPerspectives. That is not the case: a perspective is a snapshot - or a “view” - of the layout, but it is not connected to the internal representation of the layout used by the framework.

See it like this: a “perspective” is very similar to a xml file. The data hidden deep inside the framework is transformed into something that is easy to read. But just like moving around Dockables is not going to change any xml files on your disk, moving around Dockables is also not going to change any perspective. You need to ask the framework for a new snapshot - a new perspective - if you want to have accurate information.

When the application starts you create “perspectiveA”. Then you tell the framework about perspectiveA (with perspectives.setPerspective("a", perspectiveA);), at this moment the framework makes a copy of perspectiveA. Then you push around some Dockables with the mouse, store and load “a” and “b” - but that has no effect on “perspectiveA”, because that perspective is just a snapshot of an old layout. You then modify “perspectiveA” in your ActionListener, and apply your modification - based on an outdated snapshot - to the application. And that makes the framework forget all the changes you made since the application started.

Instead of keeping perspectives, you should ask the framework to provide you with an updated version. You can do this in your ActionListener with code like this:

public void actionPerformed(final ActionEvent arg0) {
    // get a copy of the current layout
    CPerspective perspective = perspectives.getPerspective( true );

    // get a copy of the layout named "a", which does not have to be the layout that is currently shown to the user
    CPerspective perspective = perspectives.getPerspective( "a", true );

   // .. continue changing the layout here```


P.S. when you add a Dockable with `gridAdd`, then you need to provide location and size of the Dockable. When you called `getPerspective` then the initial size of the grid is equal to the size of the CWorkingArea. You can get this information with:
```CWorkingArea work = (CWorkingArea)control.getStation( "work" );
Dimension size = work.getComponent().getSize();```
Also note that the existing Dockables already take up all the space, you may get funny results if you try to add a new Dockable on top of the existing Dockables. Better add them at the side, like this:
```workingB.gridAdd(-size.width/2, 0, size.width/2, size.height, ... ```

Ok, using CPerspective perspective = perspectives.getPerspective( true ); as you suggested did the trick.

And as you said, using gridAdd in this configuration, giving the same size and position for each dockable gives funny results.

But the solution you propose is not what I am looking for. Basically, if you add 4 dockables this way, you end up with a working area divided in 4 as show on picture “wrong.png”.

What I want is really to add the dockables on top of each other and use the tabs to navigate between them as shown on “good.png”

*** Edit ***

Another question comes to my mind: when you add a new dockable, do you really have to reload the full perspective to make it appear?

control.load("a");

Isn’t there another way? In my application, that has heavy 3D views, this makes the user interface flicker.

Thanks for your help and time!

About the second question. If you apply a new or modified perspective to the CControl, then the framework always needs to reload the layout. There may be some tricks like calling one of the other “setPerspective” methods - but the end result will be the same.

If you want to just add a Dockable, without reloading the layout, then you need to add it directly. For example by calling CWorkingArea.show (which is the one method I would recommend) or with code like:

control.addDockable( d );
d.setWorkingArea( working );
d.setLocation( working.getStationLocation() );
d.setVisible( true );```
A Dockable added this way will also show up in the "current perspective" you can get using `CControl.getPerspectives().getPerspective(true)`



About the first question: that is simply not possible with the current API because you cannot read the exact position of the other Dockables. I'll put that on my todo list - but I guess in your case the solution with `CWorkingArea.show` should be enough?

Thanks for the input.

The CWorkingArea.show is indeed what I need.

I have implemented the following code for the ActionListener

// Gets last saved version of perspective A
final CPerspective perspective = perspectives.getPerspective("a");

// Create a unique ID for the new dockable component
final String uniqueId = String.valueOf("Panel A#" + counter.incrementAndGet());

// Create the dockable component
final DefaultSingleCDockable dockable = new DefaultSingleCDockable(uniqueId, uniqueId, new JPanel());

// Component settings
dockable.setExternalizable(true);
dockable.setCloseable(true);
dockable.setTitleIcon(null);

// Adds the configured dockable to the CControl
control.addDockable(dockable);

// Retrieves the working area perspective
final CWorkingPerspective workingArea = (CWorkingPerspective) perspective.getStation("work");

// Create a dockable perspective (that will contain the dockable created earlier because they share the
// same ID).
final SingleCDockablePerspective dockablePerspective = new SingleCDockablePerspective(uniqueId);
// Sets the working area
dockablePerspective.setWorkingArea(workingArea);

// Add the dockable perspective to the grid layout at the desired position
workingArea.gridAdd(0, 0, 1, 1, dockablePerspective);

perspectives.setPerspective("a", perspective);

// Loads the dockable
final CWorkingArea work = (CWorkingArea) control.getStation("work");
work.show(dockable);

Generally, this works exactly as intended. I however found two problems:

Scenario 1:

  • Launch the application, perspective A is displayed
  • Add two dockables to perspective A. They appear correctly (two tabs “on top of each other”) -> OK
  • Switch to perspective B then back to A. The tabs have been moved, tab 2 is above tab 1 (cf. attachement) -> not OK

This can be reproduced. But if you add just one dockable, switch to B, then A, then add the second one, switch to B then A, it works. Is this a bug?

Scenario 2:

  • Launch the application, perspective A is displayed
  • Switch to perspective B, add a dockable. It is added to both perspective A and B -> not OK

I suspect here the problem is in my implementation…

Remove your code that modifies the perspective in this ActionListener: you are already adding your Dockable with “show” to the layout, there is no point in adding it again (maybe even at a slightly differnet location) using the perspective API.