Dockable maximum problem in StackDockable layout

Hi Byte,

When I click the maximize button in the stackDockable layout, only the currently selected dockable is maximized. How can I maximize the entire stackDockable?

When I add a CButton to the title, how do I put a portion of the Button’s position to the right of the maximize/minimize button?

Best regards,
Scott.

Are you using the code from this thread: screenDockable - #2 von Beni ?

If so the CGroupBehavior prevents stacks from being maximed. This can be solved by providing a more customized CGroupBehavior.

Try out the example below. Stacks can be maximized, but the user is prevented from making an entire stack floating.

package test;

import javax.swing.JFrame;

import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.group.CGroupMovement;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.LocationModeManager;

public class AdvancedGroupingTest {
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

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

		// custom grouping behavior is set here
		control.setGroupBehavior( new MyGroupBehavior() );

		for( int i = 0; i < 4; i++ ) {
			DefaultSingleCDockable dockable = new DefaultSingleCDockable( "id" + i );
			dockable.setTitleText( "Title " + i );
			control.addDockable( dockable );
			dockable.setLocation( CLocation.base().normal().stack() );
			dockable.setVisible( true );
		}

		frame.setBounds( 50, 50, 1000, 1000 );
		frame.setVisible( true );
	}

	private static class MyGroupBehavior implements CGroupBehavior {
		@Override
		public CGroupMovement prepare( LocationModeManager<? extends LocationMode> manager, Dockable dockable, ExtendedMode target ) {
			return getBehaviorForTarget( target ).prepare( manager, dockable, target );
		}

		@Override
		public Dockable getGroupElement( LocationModeManager<? extends LocationMode> manager, Dockable dockable, ExtendedMode mode ) {
			return getBehaviorForTarget( mode ).getGroupElement( manager, dockable, mode );
		}

		@Override
		public Dockable getReplaceElement( LocationModeManager<? extends LocationMode> manager, Dockable old, Dockable dockable, ExtendedMode mode ) {
			return getBehaviorForTarget( mode ).getReplaceElement( manager, old, dockable, mode );
		}

		@Override
		public boolean shouldForwardActions( LocationModeManager<? extends LocationMode> manager, DockStation station, Dockable dockable, ExtendedMode mode ) {
			return getBehaviorForTarget( mode ).shouldForwardActions( manager, station, dockable, mode );
		}

		private CGroupBehavior getBehaviorForTarget( ExtendedMode target ) {
			if( target == ExtendedMode.EXTERNALIZED ) {
				return CGroupBehavior.TOPMOST;
			} else {
				return CGroupBehavior.STACKED;
			}
		}
	}
}

There is more than one way to change the order of the buttons.

Actions (a button is an Action) are collected in thematically organized DockActionSources. There is a source for the maximize/minimize/etc buttons, there is a source for the close button, there is a source for the clients custom actions. Sources are collected in CommonDockable.

In addition, DockStations and customizable ActionGuards may provide additional DockActionSources.

All these sources are collected, and forwarded to an ActionOffer. The ActionOffer combines all the actions, and creates one combined DockActionSource which is forwarded to the DockTitles and thus shown to the user.

Providing a new ActionGuard is a good way to introduce special actions. Like in the example below. Note the „special“ button showing up on the very right side of the title.

package test;

import javax.swing.JFrame;

import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.ActionGuard;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.action.CActionSource;

public class CustomButtonLocationTest {
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

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

		// register additional source of actions
		control.getController().addActionGuard( new MoreActionsGuard() );

		MoreActionsDockable dockable = new MoreActionsDockable( "id" );
		dockable.setTitleText( "Title" );
		dockable.setCloseable( true );

		dockable.addAction( button( "normal" ) );

		// now there is a special method for buttons that should be on the right side
		dockable.addActionRight( button( "special" ) );

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

		frame.setBounds( 50, 50, 1000, 1000 );
		frame.setVisible( true );
	}

	private static CButton button( String name ) {
		CButton button = new CButton();
		button.setText( name );
		button.setShowTextOnButtons( true );
		return button;
	}

	// extracts the additional source of actions from MoreActionsDockable
	private static class MoreActionsGuard implements ActionGuard {
		@Override
		public boolean react( Dockable dockable ) {
			return toCDockable( dockable ) instanceof MoreActionsDockable;
		}

		@Override
		public DockActionSource getSource( Dockable dockable ) {
			return ((MoreActionsDockable) toCDockable( dockable )).moreActions;
		}

		private CDockable toCDockable( Dockable dockable ) {
			if( dockable instanceof CommonDockable ) {
				return ((CommonDockable) dockable).getDockable();
			} else {
				return null;
			}
		}
	}

	private static class MoreActionsDockable extends DefaultSingleCDockable {
		// list of actions that should be on the right side of the title.
		private CActionSource moreActions = new CActionSource( new LocationHint( LocationHint.DOCKABLE, LocationHint.RIGHT_OF_ALL ) );

		public MoreActionsDockable( String id ) {
			super( id );
		}

		public void addActionRight( CAction action ) {
			moreActions.add( action );
		}
	}
}

Thanks for your detailed answer.
I will try the solution you said.

Best regards,
Scott.

The solution you gave solves my problem. Thanks again.


Regarding the title of the dockable, I found that only a

dockable.setEnabled(EnableableItem.ALL, false );

method, can I remove the context menu on the Dockable title?

Best regards,
Scott.

There is an interface called ActionPopupSuppressor, which allows clients to analyze and to suppress popup menus.

Try this code:

CControl control = ...
control.getController().setPopupSuppressor( ActionPopupSuppressor.SUPPRESS_ALWAYS )

… this should disable all popup menus.

Or you implement your own ActionPopupSuppressor, with a custom filter.

The maximized and minimized icons are not in the same position, the maximized icon is behind the docking icon, and the minimized icon is actually in front of the docking icon. Can they make their position the same? I tried do this modification and didn’t find the right way.

regards,
Scott.

You may reorder the actions by implementing your custom ActionOffer (an ActionOffer can completely override which actions are shown to the user. It may add, remove or order actions however it likes).

May result in something like this:

This image was created by the code below. (It’s just hastily thrown together code, use it as inspiration).

package test;

import static bibliothek.gui.dock.common.mode.ExtendedMode.EXTERNALIZED;
import static bibliothek.gui.dock.common.mode.ExtendedMode.MAXIMIZED;
import static bibliothek.gui.dock.common.mode.ExtendedMode.MINIMIZED;
import static bibliothek.gui.dock.common.mode.ExtendedMode.NORMALIZED;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.swing.JFrame;

import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.AbstractDockActionSource;
import bibliothek.gui.dock.action.ActionOffer;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.core.CommonDockAction;
import bibliothek.gui.dock.common.action.predefined.CExternalizeAction;
import bibliothek.gui.dock.common.action.predefined.CMaximizeAction;
import bibliothek.gui.dock.common.action.predefined.CMinimizeAction;
import bibliothek.gui.dock.common.intern.action.CExtendedModeAction;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.event.DockActionSourceListener;

public class ReorderActionsTest {
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

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

		// overriding default behavior how to calculate the actions of a Dockable
		control.getController().addActionOffer( new ReorderingActionOffer() );

		DefaultSingleCDockable dockable = new DefaultSingleCDockable( "id", "Title" );
		dockable.setCloseable( true );

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

		frame.setBounds( 50, 50, 1000, 1000 );
		frame.setVisible( true );
	}

	// replace default DockActionSource by custom ReorderingDockActionSource
	private static class ReorderingActionOffer implements ActionOffer {
		private boolean recursion = false;

		@Override
		public boolean interested( Dockable dockable ) {
			// guard against unwanted recursion and subsequent StackOverflowException
			return !recursion;
		}

		@Override
		public DockActionSource getSource( Dockable dockable, DockActionSource source, DockActionSource[] guards, DockActionSource parent, DockActionSource[] parents ) {
			try {
				recursion = true;
				DockActionSource original = dockable.getController().getActionOffer( dockable ).getSource( dockable, source, guards, parent, parents );
				return new ReorderingDockActionSource( original );
			} finally {
				recursion = false;
			}
		}
	}

	// custom ordering scheme of actions. Any ordering scheme may be used, as long as it is stable (e.g. don't order the
	// actions by their text, as the text may change).
	private static class ReorderingComparator implements Comparator<DockAction> {
		private final List<ExtendedMode> order = Arrays.asList( null, MAXIMIZED, MINIMIZED, NORMALIZED, EXTERNALIZED );

		@Override
		public int compare( DockAction o1, DockAction o2 ) {
			ExtendedMode m1 = getMode( o1 );
			ExtendedMode m2 = getMode( o2 );

			return Integer.compare( order.indexOf( m1 ), order.indexOf( m2 ) );
		}

		private ExtendedMode getMode( DockAction action ) {
			if( action instanceof CommonDockAction ) {
				CAction caction = ((CommonDockAction) action).getAction();
				if( caction instanceof CExternalizeAction ) {
					return EXTERNALIZED;
				} else if( caction instanceof CMaximizeAction ) {
					return MAXIMIZED;
				} else if( caction instanceof CMinimizeAction ) {
					return MINIMIZED;
				} else if( caction instanceof CExtendedModeAction ) {
					return NORMALIZED;
				}
			}
			return null;
		}
	}

	// Wrapper around a list of actions, orders them by ReorderingComparator
	private static class ReorderingDockActionSource extends AbstractDockActionSource implements DockActionSourceListener {
		private final DockActionSource original;

		private final List<DockAction> copy = new ArrayList<>();
		private final List<DockAction> reorderedActions = new ArrayList<>();

		public ReorderingDockActionSource( DockActionSource original ) {
			this.original = original;
		}

		// sort original list of actions by custom ordering scheme
		private List<DockAction> sort( List<DockAction> original ) {
			return original.stream().sorted( new ReorderingComparator() ).collect( Collectors.toList() );
		}

		// translate original position of an action to its position after reordering
		private int toReordered( int index ) {
			List<DockAction> copy;
			if( hasListeners() ) {
				copy = this.copy;
			} else {
				copy = StreamSupport.stream( original.spliterator(), false ).collect( Collectors.toList() );
			}

			List<DockAction> sorted = sort( copy );
			return sorted.indexOf( copy.get( index ) );
		}

		@Override
		public void addDockActionSourceListener( DockActionSourceListener listener ) {
			if( !hasListeners() ) {
				// keep a copy of the original actions as long as listeners are registered.
				// we want to keep a copy to apply one event after the other to our reordered list.
				// This makes sure we never mix up positions when forwarding events.
				for( DockAction action : original ) {
					copy.add( action );
				}
				reorderedActions.addAll( sort( copy ) );
				original.addDockActionSourceListener( this );
			}
			super.addDockActionSourceListener( listener );
		}

		@Override
		public void removeDockActionSourceListener( DockActionSourceListener listener ) {
			super.removeDockActionSourceListener( listener );
			if( !hasListeners() ) {
				copy.clear();
				reorderedActions.clear();
				original.removeDockActionSourceListener( this );
			}
		}

		@Override
		public LocationHint getLocationHint() {
			return original.getLocationHint();
		}

		@Override
		public int getDockActionCount() {
			if( hasListeners() ) {
				return reorderedActions.size();
			} else {
				return original.getDockActionCount();
			}
		}

		@Override
		public DockAction getDockAction( int index ) {
			if( hasListeners() ) {
				return reorderedActions.get( index );
			} else {
				int reordered = toReordered( index );
				return original.getDockAction( reordered );
			}
		}

		@Override
		public Iterator<DockAction> iterator() {
			return new Iterator<DockAction>() {
				private int index = 0;

				@Override
				public boolean hasNext() {
					return index < getDockActionCount();
				}

				@Override
				public DockAction next() {
					return getDockAction( index++ );
				}
			};
		}

		@Override
		public void actionsAdded( DockActionSource source, int firstIndex, int lastIndex ) {
			for( int index = firstIndex; index <= lastIndex; index++ ) {
				DockAction action = source.getDockAction( index );
				copy.add( index, action );

				int insertAt = toReordered( index );
				reorderedActions.add( insertAt, action );

				fireAdded( insertAt, insertAt );
			}
		}

		@Override
		public void actionsRemoved( DockActionSource source, int firstIndex, int lastIndex ) {
			for( int index = lastIndex; index >= firstIndex; index-- ) {
				int removeAt = toReordered( index );

				copy.remove( index );
				reorderedActions.remove( removeAt );

				fireRemoved( removeAt, removeAt );
			}
		}
	}
}