Losing MultipleCDockables when setting perspective

Hi Beni,

yesterday I figured out, that there is a new release with a cool new feature I was waiting for a long time: perspectives :slight_smile:

So I updated my tool and everythings works fine, then I started to replace my work-around code for having perspectives in my application by using your perspective functionality. But now I have some curious behaviors: when changing the perspective, all my MultipleCDockables inside a working area get lost.

I my tool, I have a central working area with surrounding singleCdockables. It is possible to open an additional singleCdockable which has a preferred position like, e.g., east-top. Because, I only know this preferred position, when the dockable was added, I have to update my perspective by adding a new SingeCDockablePerspective with the identifier of the added dockable. Then, I use CControlPerspective.setPerspective(…) again to update the layout, but now all previously opened multipleCdockables of the central working-area are removed.

What is my fault in using the perspectives? Is it a bug?

Is it possible to have a catch-all SingleCDockablePerspective where all the dockables go, which identifiers are unknown?

Thanks a lot,

Andy

It could be a bug, the API is still a bit new. I’ll do some testing in the evening. Could you give me the code that adds your new SingeCDockablePerspective, so I can put together a test more easily?

Is it possible to have a catch-all SingleCDockablePerspective where all the dockables go, which identifiers are unknown?

Not with the current design. All the internal data structures are built with the assumption that either a Dockable is missing but its id is known, or that when adding a Dockable the client uses some code like „CDockable.setLocation“ to put it at the correct position.

Hi Beni,

thanks, for the fast reply!

This is the code for creating the perspective and where all known dockables are arranged:


        CControlPerspective perspectives = mainWindow.getControl().getPerspectives();
        
        if (perspective == null) {
            perspective = perspectives.createEmptyPerspective();
            
            CGridPerspective center = perspective.getContentArea().getCenter();
            
            for (View view : mainWindow.getViews()) {

                Integer pos = view_position.get(view);

                if (pos == null)
                    pos = view.getPreferredPosition();

                switch (pos.intValue()) {
                case WEST:
                    center.gridAdd(0, 0, 20, 100, new SingleCDockablePerspective(view.getId()));
                    break;
                case SOUTH:
                    center.gridAdd(20, 80, 80, 20, new SingleCDockablePerspective(view.getId()));
                    break;
                case EAST | BOTTOM:
                    center.gridAdd(70, 40, 30, 40, new SingleCDockablePerspective(view.getId()));
                    break;
                case EAST | TOP:
                case EAST:
                    center.gridAdd(70, 0, 30, 40, new SingleCDockablePerspective(view.getId()));
                    break;
                }
            }
            
            CWorkingPerspective wp = (CWorkingPerspective) perspective.getRoot("working_area");
            center.gridAdd(20, 0, 50, 80, wp);

        }
        perspectives.setPerspective( perspective, false );

        
        for (View view : mainWindow.getViews()) {
            DefaultCDockable dockable = (DefaultCDockable) view.getDockable();
            dockable.setLocation(CLocation.base(mainWindow.getContentArea()));
            dockable.setVisible(true);
        }

        // Editors are MultipleCDockables
        for (Editor editor : mainWindow.getEditors()) {
            DefaultCDockable dockable = (DefaultCDockable) editor.getDockable();
            dockable.setLocation(CLocation.working(workingArea));
            dockable.setVisible(true);
        }

And this is the code, which is called after adding a new Dockable and “updates” the current perspective:


        final DefaultCDockable d = (DefaultCDockable) view.getDockable();
        
        if (view instanceof Editor) {
            d.setLocation(CLocation.working(workingArea));
            d.setVisible(true);
            EventQueue.invokeLater(new Runnable() {
                
                @Override
                public void run() {
                    d.getContentPane().validate();
                    d.getContentPane().repaint();        
                }
            });

        } else {
            CGridPerspective center = perspective.getContentArea().getCenter();
            
            Integer pos = view_position.get(view);

            if (pos == null)
                pos = view.getPreferredPosition();
            
            switch (pos.intValue()) {
            case WEST:
                center.gridAdd(0, 0, 20, 100, new SingleCDockablePerspective(view.getId()));
                break;
            case SOUTH:
                center.gridAdd(20, 80, 80, 20, new SingleCDockablePerspective(view.getId()));
                break;
            case EAST | BOTTOM:
                center.gridAdd(70, 40, 30, 40, new SingleCDockablePerspective(view.getId()));
                break;
            case EAST | TOP:
            case EAST:
                center.gridAdd(70, 0, 30, 40, new SingleCDockablePerspective(view.getId()));
                break;
            }
            
            CControlPerspective perspectives = mainWindow.getControl().getPerspectives();

            // and this line will remove all MultipleCDockables from the workingArea (independent of true/false)
            perspectives.setPerspective(perspective, false);
            
            d.setLocation(CLocation.base(mainWindow.getContentArea()));
            d.setVisible(true);
        }

Sorry, for not providing a compiling class…

Best regards,

Andy

Ok. there are two things wrong:

  1. There is a bug in the framework, even if the example would be correct, it would not work. Some Exceptions would be thrown, but nothing would happen. I’ll upload a fixed version within the next hours. Afterwards your and my example below should work.

  2. Perspectives are a momentary snapshot of the layout. And as far as I understand your code you try to create a perspective once and then reuse it. This will work with some limitations: every time you apply the perspective the layout will be reset, and you need to keep the “false” in “perspectives.setPerspective( perspective, false );”, otherwise the editors (which are not part of the perspective) are removed.
    It might be better if you create a new perspective when adding a Dockable, like this: “perspective = control.getPerspectives().getPerspective( false );”

Also your calls…

dockable.setVisible(true);

… are unnecessary: first, “CLocation.base” returns a location that does not yet point anywhere, you would need to call something like “CLocation.base().normal()” to get a result. And in this case the result would be that the settings made by the perspective are overriden. Additionally the dockables already are visible because they are part of the perspective.

Below is the code I used to test:


import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

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.CLocation;
import bibliothek.gui.dock.common.CWorkingArea;
import bibliothek.gui.dock.common.DefaultMultipleCDockable;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.EmptyMultipleCDockableFactory;
import bibliothek.gui.dock.common.MultipleCDockable;
import bibliothek.gui.dock.common.MultipleCDockableFactory;
import bibliothek.gui.dock.common.MultipleCDockableLayout;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.DefaultCDockable;
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;

public class Dock46 {
	public static final int NORTH = 1;
	public static final int SOUTH = 2;
	public static final int EAST = 4;
	public static final int WEST = 8;

	private CControl control;
	private CWorkingArea workingArea;
	
	private List<View> views = new ArrayList<View>();
	private List<Editor> editors = new ArrayList<Editor>();

	public static void main( String[] args ){
		Dock46 test = new Dock46();
		test.start();
	}

	public void start(){
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

		control = new CControl( frame );
		frame.add( control.getContentArea() );
		workingArea = control.createWorkingArea( "working_area" );

		views.add( new View( "South", SOUTH ) );
		views.add( new View( "East", EAST ) );
		views.add( new View( "West", WEST ) );

		Factory factory = new Factory();
		control.addMultipleDockableFactory( "factory", factory );

		editors.add( new Editor( "a", EAST, factory ) );
		editors.add( new Editor( "b", EAST, factory ) );
		editors.add( new Editor( "c", EAST, factory ) );

		create();

		JMenuBar bar = new JMenuBar();
		JMenu menu = new JMenu( "Test" );
		bar.add( menu );
		JMenuItem item = new JMenuItem( "Test" );
		menu.add( item );
		frame.setJMenuBar( bar );
		item.addActionListener( new ActionListener(){
			@Override
			public void actionPerformed( ActionEvent e ){
				insert( new View( String.valueOf( Math.random() ), EAST ) );
			}
		} );

		frame.setBounds( 20, 20, 600, 400 );
		frame.setVisible( true );
	}

	private class Factory extends EmptyMultipleCDockableFactory<MultipleCDockable> {
		public MultipleCDockable createDockable(){
			return (MultipleCDockable) new Editor( "New Editor", SOUTH, this ).getDockable();
		}
	}

	private class View {
		private int position;
		private CDockable dockable;

		public View( int position, CDockable dockable ){
			this.position = position;
			this.dockable = dockable;
		}

		public View( String title, int position ){
			this( position, new DefaultSingleCDockable( title, title ) );
		}

		public String getId(){
			return ((DefaultSingleCDockable) dockable).getUniqueId();
		}

		public int getPosition(){
			return position;
		}

		public CDockable getDockable(){
			return dockable;
		}
	}

	private class Editor extends View {
		public Editor( String title, int position, MultipleCDockableFactory<MultipleCDockable, MultipleCDockableLayout> factory ){
			super( position, new DefaultMultipleCDockable( factory, title ) );
		}
	}

	private void create(){
		CControlPerspective perspectives = control.getPerspectives();
		CPerspective perspective = null;
		
		if( perspective == null ) {
			perspective = perspectives.createEmptyPerspective();

			CGridPerspective center = perspective.getContentArea().getCenter();

			for( View view : views ) {
				
				control.addDockable( (SingleCDockable)view.getDockable() );
				int pos = view.getPosition();

				switch( pos ){
				case WEST:
					center.gridAdd( 0, 0, 20, 100, new SingleCDockablePerspective( view.getId() ) );
					break;
				case SOUTH:
					center.gridAdd( 20, 80, 80, 20, new SingleCDockablePerspective( view.getId() ) );
					break;
				case EAST:
					center.gridAdd( 70, 40, 30, 40, new SingleCDockablePerspective( view.getId() ) );
					break;
				}
			}
			
			CWorkingPerspective wp = (CWorkingPerspective) perspective.getRoot( "working_area" );
			center.gridAdd( 20, 0, 50, 80, wp );
			
			perspectives.setPerspective( perspective, false );

			// Editors are MultipleCDockables
			for( Editor editor : editors ) {
				DefaultCDockable dockable = (DefaultCDockable) editor.getDockable();
				dockable.setLocation( CLocation.working( workingArea ) );
				control.addDockable( (MultipleCDockable)dockable );
				dockable.setVisible( true );
			}
		}
	}

	private void insert( View view ){
		final DefaultCDockable d = (DefaultCDockable) view.getDockable();

		if( view instanceof Editor ) {
			d.setLocation( CLocation.working( workingArea ) );
			d.setVisible( true );
			EventQueue.invokeLater( new Runnable(){

				@Override
				public void run(){
					d.getContentPane().validate();
					d.getContentPane().repaint();
				}
			} );

		}
		else {
			CPerspective perspective = control.getPerspectives().getPerspective( false );
			
			control.addDockable( (SingleCDockable)view.getDockable() );
			CGridPerspective center = perspective.getContentArea().getCenter();
			
			int pos = view.getPosition();

			switch( pos ){
			case WEST:
				center.gridAdd( 0, 0, 20, 100, new SingleCDockablePerspective( view.getId() ) );
				break;
			case SOUTH:
				center.gridAdd( 20, 80, 80, 20, new SingleCDockablePerspective( view.getId() ) );
				break;
			case EAST:
				center.gridAdd( 70, 0, 30, 40, new SingleCDockablePerspective( view.getId() ) );
				break;
			}

			CControlPerspective perspectives = control.getPerspectives();

			// and this line will remove all MultipleCDockables from the workingArea (independent of true/false)
			perspectives.setPerspective( perspective, false );
		}
	}
}

Hi Beni,

thanks again! Now it works great!

Andy