CWorkingArea in a minimized closes the tab upon receiving focus

Hi,

I have come across an problem that may be a bug, or may be the way that I am using the framework. Here is the layout:

I have a tab (“tab”) that contains a panel (“panel”). This panel contains a button (which in the code example below is entitled “NotInCWorkingArea”) and a CWorkingArea (“workArea”), with the CWorkingArea containing two tabs with some text.

When I minimize the tab containing everything, open the flap, and click inside the working area, it immediately reminimizes after processing the click. If I open the flap and click outside of the working area (such as on the “NotInCWorkingArea” button) the flap remains open.

What I desire is that the flap remain open when I click inside it, but still close when I click outside of it.

Here is an example:

package docktest;

import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CWorkingArea;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.mode.ExtendedMode;

public class DockTest
{
	static class MyPanel extends JPanel
	{
		public MyPanel( CControl control )
		{
			this.setLayout( new BorderLayout() );
			this.add( new JButton( "Not in CWorkingArea" ), BorderLayout.NORTH );

			CWorkingArea workArea = control.createWorkingArea( "internalWorkArea" );

			DefaultSingleCDockable itab1 = new DefaultSingleCDockable( "itab1",
					"itab2" );
			itab1.add( new JLabel( "text1" ) );

			DefaultSingleCDockable itab2 = new DefaultSingleCDockable( "itab2",
					"itab2" );
			itab2.add( new JLabel( "text2" ) );

			CGrid internalGrid = new CGrid( control );
			internalGrid.add( 0, 0, 1, 1, itab1 );
			internalGrid.add( 0, 1, 1, 1, itab2 );
			workArea.deploy( internalGrid );

			itab1.setLocation( CLocation.base().minimalWest() );
			itab1.setExtendedMode( ExtendedMode.NORMALIZED );
			itab1.setDefaultLocation( ExtendedMode.NORMALIZED,
					itab1.getBaseLocation() );
			itab1.setVisible( true );

			itab2.setLocation( CLocation.base().minimalWest() );
			itab2.setExtendedMode( ExtendedMode.NORMALIZED );
			itab2.setDefaultLocation( ExtendedMode.NORMALIZED,
					itab1.getBaseLocation() );
			itab2.setVisible( true );

			this.add( workArea.getComponent(), BorderLayout.CENTER );
		}
	}

	public static void main( String[] args )
	{
		SwingUtilities.invokeLater( new Runnable()
		{
			@Override
			public void run()
			{
				JFrame frame = new JFrame();
				frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
				frame.setSize( 800, 600 );

				CControl control = new CControl( frame );
				frame.getContentPane().add( control.getContentArea() );

				MyPanel panel = new MyPanel( control );

				DefaultSingleCDockable tab = new DefaultSingleCDockable( "tab",
						"tab", panel );

				control.addDockable( tab );
				tab.setVisible( true );

				frame.setVisible( true );
			}
		} );
	}
}

Help is appreciated. Thanks.

It’s quite easy: the framework tracks the Component which has the focus, and searches the Dockable to which the focused Component belongs. If that Dockable is a child of the FlapDockStation (the station showing minimized Dockables), then the window stays open - if not, the window closes.

The issue in your application is, that the CWorkingArea is not actually a child of the FlapDockStation. Just because the Component of the CWorkingArea is put on a JPanel that belongs to a minimized Dockable, does not mean the CWorkingArea is registered as “minimized”. For the framework it looks like the CWorkingArea is “normalized”.

What you can do, is make a connection between CWorkingArea and FlapDockStation. For that you need to get rid of the DefaultSingleCDockable you created - you simply don’t need it. Instead you treat the CWorkingArea like any other SingleCDockable, you can do that because CWorkingArea implements the interface.

You can tell the CWorkingArea to show a title (it is not pretty), and you can tell it to allow minimization. You may wonder why you need to override a method for that… that’s because the framework does not officially support minimized CWorkingAreas. And you will notice some small issues, it really depends on your application whether these issues can be tolerated or not.

In the example below I’ve added some code to show the button outside the CWorkingArea, and to set up some nicer title than the default title. Feel free to delete everything you don’t need…


import java.awt.BorderLayout;
import java.awt.Component;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CWorkingArea;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.intern.EfficientControlFactory;
import bibliothek.gui.dock.common.intern.station.CSplitDockStation;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.station.CommonStationDelegate;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.station.DockableDisplayer.Location;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.themes.basic.BasicDisplayerFactory;
import bibliothek.gui.dock.themes.basic.BasicDockTitle;
import bibliothek.gui.dock.title.DockTitleFactory;
import bibliothek.gui.dock.title.DockTitleManager;
import bibliothek.gui.dock.title.DockTitleRequest;

public class DockTest {
	public static void main( String[] args ){
		// Starting up the application in the EDT, preventing any kind of multi-threading issue
		SwingUtilities.invokeLater( new Runnable(){
			@Override
			public void run(){
				JFrame frame = new JFrame();
				frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
				frame.setSize( 800, 600 );

				CControl control = new CControl( frame, new CustomizedFactory() );
				setupTitle( control );
				setupDisplayer( control );
				frame.getContentPane().add( control.getContentArea() );

				MinimizeableWorkingArea area = setUpArea( control );
				area.setExtendedMode( ExtendedMode.NORMALIZED );
				area.setVisible( true );

				frame.setVisible( true );
			}
		} );
	}

	private static MinimizeableWorkingArea setUpArea( CControl control ){
		// create and register the working-area
		MinimizeableWorkingArea workArea = new MinimizeableWorkingArea( control, "internalArea" );
		control.addStation( workArea, true );
		control.addDockable( workArea );

		// add some content to the area
		DefaultSingleCDockable itab1 = new DefaultSingleCDockable( "itab1", "itab2" );
		itab1.add( new JLabel( "text1" ) );

		DefaultSingleCDockable itab2 = new DefaultSingleCDockable( "itab2", "itab2" );
		itab2.add( new JLabel( "text2" ) );

		CGrid internalGrid = new CGrid( control );
		internalGrid.add( 0, 0, 1, 1, itab1 );
		internalGrid.add( 0, 1, 1, 1, itab2 );
		workArea.deploy( internalGrid );

		itab1.setLocation( CLocation.base().minimalWest() );
		itab1.setExtendedMode( ExtendedMode.NORMALIZED );
		itab1.setDefaultLocation( ExtendedMode.NORMALIZED, itab1.getBaseLocation() );
		itab1.setVisible( true );

		itab2.setLocation( CLocation.base().minimalWest() );
		itab2.setExtendedMode( ExtendedMode.NORMALIZED );
		itab2.setDefaultLocation( ExtendedMode.NORMALIZED, itab1.getBaseLocation() );
		itab2.setVisible( true );

		return workArea;
	}

	/*
	 * CControl uses this factory to build various objects that are used everywhere.
	 * 
	 * Our customized CWorkingArea will ask this factory for a new SplitDockStation, that station
	 * actually does all the heavy work while CWorkingArea only offers a nice API. 
	 */
	private static class CustomizedFactory extends EfficientControlFactory {
		@Override
		public CommonDockStation<SplitDockStation, CSplitDockStation> createSplitDockStation( CommonStationDelegate<CSplitDockStation> delegate ){
			if( delegate.getStation() instanceof MinimizeableWorkingArea ) {
				return new MinimizeableSplitDockStation( delegate );
			}
			else {
				return super.createSplitDockStation( delegate );
			}
		}
	}

	/*
	 * This SplitDockStation wrapps its content into a panel, allowing additional Components
	 * to show up around the "original" station. 
	 */
	public static class MinimizeableSplitDockStation extends CSplitDockStation {
		private JPanel panel;

		public MinimizeableSplitDockStation( CommonStationDelegate<CSplitDockStation> delegate ){
			super( delegate );
			panel = new JPanel( new BorderLayout() );
			panel.add( super.getComponent(), BorderLayout.CENTER );
		}

		public JPanel getPanel(){
			return panel;
		}

		@Override
		public Component getComponent(){
			return panel;
		}
	}

	public static class MinimizeableWorkingArea extends CWorkingArea {
		public MinimizeableWorkingArea( CControl control, String uniqueId ){
			super( control, uniqueId );
			setTitleShown( true );

			// the delegate has been created by our customized factory, we can now add additional
			// components to the station
			MinimizeableSplitDockStation delegate = (MinimizeableSplitDockStation) getStation();
			delegate.getPanel().add( new JButton( "Not in MinimizeableWorkingArea" ), BorderLayout.NORTH );
		}

		@Override
		public boolean isMinimizable(){
			// enable minimization of this station
			return true;
		}
	}
	
	// since we are at it, you may want to replace the default title for DockStations. This is
	// how you do it:
	private static void setupTitle( CControl control ){
		DockTitleFactory factory = new DockTitleFactory(){
			@Override
			public void uninstall( DockTitleRequest request ){
				// ignore	
			}
			
			@Override
			public void request( DockTitleRequest request ){
				request.answer( new BasicDockTitle( request.getTarget(), request.getVersion() ) );
			}
			
			@Override
			public void install( DockTitleRequest request ){
				// ignore
			}
		};
		
		DockTitleManager titles = control.getController().getDockTitleManager();
		titles.registerClient( SplitDockStation.TITLE_ID, factory );
		titles.registerClient( FlapDockStation.WINDOW_TITLE_ID, factory );
	}
	
	// And maybe you want to control the position of the title as well, you can use this
	// piece of code for that
	private static void setupDisplayer( CControl control ){
		BasicDisplayerFactory factory = new BasicDisplayerFactory();
		factory.setStationLocation( Location.TOP );
		
		ThemeManager manager = control.getController().getThemeManager();
		manager.setDisplayerFactory( ThemeManager.DISPLAYER_FACTORY + ".flap", factory );
		manager.setDisplayerFactory( ThemeManager.DISPLAYER_FACTORY + ".split", factory );
	}
}```

Thanks for the fast reply. Unfortunately there is a reason for having the DefaultSingleCDockable - namely in the real code it is a class that contains a couple custom features. Hence directly using a CWorkingArea isn’t an option. I’ve tried nesting it inside of the original tab but without any success.

I did come up with an idea of using listeners to automatically pin/unpin the tab when appropriate so as to simulate the desired behavior. If the suggestion above doesn’t pan out, then perhaps that would work?

I’m not certain pining will work, I fear the event for closing the window will be too fast.

Would a solution be to create an additional CControl just for managing this CWorkingArea? It’s children would not interact with any other station, including the “minimize area”.

Otherwise I would have to modify the code of the framework directly (won’t happen before next weekend). I mean this would certainly result in a solution, but not necessarily in a nice one.

Sorry for the long delay. Unfortunately, restricting the child tabs to a CControl in the internal working area is not an option.

I have played around a little with the pinning idea, but I keep running to an exception. Here is how to reproduce manually:

  1. Drag the main tab to be minimized on the east
  2. Open the tab and pin it
  3. Click on an internal tab
  4. Click the pin button. An exception is raised:
	at java.util.ArrayList.RangeCheck(ArrayList.java:547)
	at java.util.ArrayList.remove(ArrayList.java:387)
	at java.awt.Container.remove(Container.java:1153)
	at java.awt.Container.remove(Container.java:1198)
	at bibliothek.gui.dock.station.flap.DefaultFlapWindow.setDockable(DefaultFlapWindow.java:342)
	at bibliothek.gui.dock.FlapDockStation.updateWindow(FlapDockStation.java:771)
	at bibliothek.gui.dock.FlapDockStation.setFrontDockable(FlapDockStation.java:730)
	at bibliothek.gui.dock.FlapDockStation.updateHold(FlapDockStation.java:860)
	at bibliothek.gui.dock.common.intern.station.CFlapLayoutManager$2.minimizedHoldChanged(CFlapLayoutManager.java:83)
	at bibliothek.gui.dock.common.intern.CListenerCollection$2.minimizedHoldChanged(CListenerCollection.java:104)
	at bibliothek.gui.dock.common.intern.AbstractCDockable.setMinimizedHold(AbstractCDockable.java:500)
	at bibliothek.gui.dock.common.intern.station.CFlapLayoutManager.setHold(CFlapLayoutManager.java:173)
	at bibliothek.gui.dock.FlapDockStation.setHold(FlapDockStation.java:840)
	at bibliothek.gui.dock.station.flap.FlapDockHoldToggle.setSelected(FlapDockHoldToggle.java:115)
	at bibliothek.gui.dock.themes.basic.action.BasicSelectableHandler$Check.triggered(BasicSelectableHandler.java:61)
	at bibliothek.gui.dock.themes.basic.action.BasicButtonModel.trigger(BasicButtonModel.java:631)
	at bibliothek.gui.dock.themes.basic.action.BasicButtonModel$Listener.mouseReleased(BasicButtonModel.java:669)
	at java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:272)
	at java.awt.Component.processMouseEvent(Component.java:6288)
	at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
	at java.awt.Component.processEvent(Component.java:6053)
	at java.awt.Container.processEvent(Container.java:2041)
	at java.awt.Component.dispatchEventImpl(Component.java:4651)
	at java.awt.Container.dispatchEventImpl(Container.java:2099)
	at java.awt.Component.dispatchEvent(Component.java:4481)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4577)
	at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
	at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
	at java.awt.Container.dispatchEventImpl(Container.java:2085)
	at java.awt.Window.dispatchEventImpl(Window.java:2478)
	at java.awt.Component.dispatchEvent(Component.java:4481)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:643)
	at java.awt.EventQueue.access$000(EventQueue.java:84)
	at java.awt.EventQueue$1.run(EventQueue.java:602)
	at java.awt.EventQueue$1.run(EventQueue.java:600)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
	at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:98)
	at java.awt.EventQueue$2.run(EventQueue.java:616)
	at java.awt.EventQueue$2.run(EventQueue.java:614)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:613)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)```

Sorry for the long delay as well, I completely forgot your question until I read some other question…

Unfortunately I could not produce the exception :frowning: Could you please give the code you are using now, because the exception does not tell me that much.

My experimental code is messy, but you can reproduce it using the original code I posted by adding (after setVisible)
tab.setMinimizedHold( true );

Looking at the line numbers in the StackTrace I can deduce, you are using a version that is quite old. If my guess is correct: could you please download the newest version from the website and try again? Because a few months ago I fixed an issue that could be related to this exception.

You are correct. My playground workspace was using dockingframes 1.1.0 whereas the actual application workspace is using 1.1.1. I will not get a chance to play around with this until next week.

After looking further I don’t think the sticky idea will be viable - not that it isn’t possible, but that there are many cases to handle which makes it susceptible to having issues and idiosyncrasies.