Change/extend the framework behavior

Hi,

I’m currently evaluating DockingFrames for a project and after playing around with it a bit, I’d like to change/extend some of the default behavior of the framework. But before I try to do so, I hoped someone here could perhaps provide some hints where to start and tell me how difficult it would be to implement these features (or if it is not easily possible at all). Note that I do not expect to get complete solutions or source code - a few directions like classes/methods that would need to be implemented/overwritten, properties that would need to be set etc. are fair enough.

(1) A user should be able to cancel a drag/drop gesture by pressing the ESCAPE key (similar to what happens atm when the right mouse button is clicked during dragging). I guess this could easily be implemented by extending DefaultDockRelocator and listening to KeyEvents while dragging is done. Or is there already some easy way to do this e.g. by setting a property (actually I was quite surprised to see, that this is not the default behavior since it is certainly expected by most users to be able to cancel a drag in this way)?

(2) TabPanes/StackStations: At the moment it is (with the default theme including insert animation previews) sometimes very hard to move tabs to a different index of the TabPane if the StackStation is a child of a SplitStation because the parent SplitStation can be accidentially selected to do a split instead (and because the direct preview of the split moves the tab area away from the cursor). I’d like to change this so the StackStation **always **gets precedence if the mouse is above the tab area (with the exception of floating windows of course). I guess this could be done by somehow changing the LayerPriority of the tab area? Also ideally there should be no drop possible at all, as long as the mouse is directly above the currently selected tab…

(3) ToolbarItems: I really like the flexibility to be able to move buttons etc. on toolbars around. But at the moment it is just too easy to accidentially trigger this feature when clicking a button or when trying to drag a tool bar. Would it be possible to extend the framework with any of the following: (a) only allow moving buttons if an additional key like ALT is pressed during the drag or (b) a timer, so moving a button gets only activated after say 1 second of continuously pressing the mouse?

(4) ToolbarContainerDockStation: It would be great if „normal“ Dockables (other than toolbars) could also be docked to toolbar containers in their minimized form (similar to how this is handled in Eclipse 4) but it seems this isn’t easily possible at the moment? I’d guess that adding such a feature would be hard to do since it will most likely require implementing a new DockStation that combines the behavior of ToolbarContainerDockStation and FlapDockStation. But I’ll ask anyway - would it perhaps be possible to do something similar by implementing a custom ToolbarStrategy that overrides ToolbarStrategy#isToolbarPart() to accept any dockables and then add the minimized views of these Dockables to the ToolbarContainerDockStation? On a side note: The Javadoc of ToolbarContainerDockStation says „it accepts only {@link ToolbarElementInterface}s“ but no „ToolbarElementInterface“ calss exists - is this documentation outdated or did I miss something?

Thank you very much for your time and many thanks to the developer(s) for the great work on this amazing framework - ich bin entzückt! :stuck_out_tongue_winking_eye:

Cheers,
Dieter

(1) That one is easy, just call the “cancel” method like this

			@Override
			public boolean keyTyped( CDockable source, KeyEvent event ) {
				return false;
			}

			@Override
			public boolean keyReleased( CDockable source, KeyEvent event ) {
				return false;
			}

			@Override
			public boolean keyPressed( CDockable source, KeyEvent event ) {
				if( event.getKeyCode() == KeyEvent.VK_ESCAPE ) {
					control.getController().getRelocator().cancel();
					return true;
				}else{
					return false;
				}
			}
		} );```

You are right, this could be a nice little enhancement. I did put it on my todo-list.

*** Edit ***

(2) Yes, the "DockStationDropLayer" is responsible for deciding which station gets the Dockable. I can add a hook that allows clients to override the default behavior, currently you would need to replace the entire StackDockStation which is possible but a lot of work.

(3) You can implement the interface "DockRelactorMode" and the "VetoableDockRelocatorListener" to achieve such an effect. Like in the code below.
The "DockRelocatorMode" is invoked whenever the mouse is moved in a way that looks like a "drag&drop" operation. It is "activated" while a drag&drop operation actually is in progress or about to start, but only if the "shouldBeActive" method returned true. The "VetoableDockRelocatorListener" may be used to cancel any work that is done during or before a drag&drop operation.

```		StopEverything stop = new StopEverything();
		control.getController().getRelocator().addMode( stop );
		control.getController().getRelocator().addVetoableDockRelocatorListener( stop );

	public class StopEverything extends VetoableDockRelocatorAdapter implements DockRelocatorMode{
		private ModifierMask mask = new ModifierMask( KeyEvent.ALT_DOWN_MASK );
		private boolean active;
		
		@Override
		public boolean shouldBeActive( DockController controller, int modifiers ) {
			return !mask.matches( modifiers );
		}
		
		@Override
		public void setActive( DockController controller, boolean active ) {
			this.active = active;
		}
		
		@Override
		public void searched( DockRelocatorEvent event ) {
			if( active ){
				event.forbid();
				System.out.println("not allowed, alt not pressed");
			}
		}
	}

*** Edit ***

(4) uh, never trust documentation.

Toolbars are managed by the “ToolbarStrategy”, which can be replaced by a custom strategy. You could define a strategy like the one below, which will allow any Dockable to be moved into the toolbar:

			public boolean isToolbarPart(Dockable dockable) {
				if( isSpecialDockable( dockable )){
					return true;
				}
				
				return super.isToolbarPart( dockable );
			}
			
			public Dockable ensureToolbarLayer(DockStation station, Dockable dockable) {
				if( isSpecialDockable( dockable )){
					// "dockable" can now act as button, or as toolbar, or as group of toolbars... certainly more code is required to get "nice" behavior
					return dockable;
				}
				return super.ensureToolbarLayer( station, dockable );
			}
			
			private boolean isSpecialDockable( Dockable dockable ){
				// here you can write down any condition you would like to decide which Dockable can
				// be put onto a toolbar.
				return dockable instanceof CommonDockable;
			}
		} );```

If you add a "CDockableStateListener" to your "CDockables" and override the method "extendedModeChanged", then you will even receive an event if a Dockable is put into the toolbar. 

You may actually insert a FlapDockStation between toolbar and Dockable, to get an effect that looks like the Dockable was added to the toolbar. Like in the code below. But when you run the example you will notice one or two issues and exceptions. This is certainly a feature that does not work out of the box, and it would need some work to function properly. Still, at least it is not completely impossible.

```package test;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;

import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.FlapDockStation.Direction;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.event.CDockableStateListener;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.DefaultCommonDockable;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.dockable.DockableStateEvent;
import bibliothek.gui.dock.dockable.DockableStateListener;
import bibliothek.gui.dock.station.toolbar.DefaultToolbarStrategy;
import bibliothek.gui.dock.station.toolbar.ToolbarStrategy;
import bibliothek.gui.dock.toolbar.CToolbarContentArea;
import bibliothek.gui.dock.toolbar.CToolbarItem;

public class ToolbarTest {
	public static void main( String[] args ) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		
		final CControl control = new CControl(frame);
		control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);
		
		control.putProperty( ToolbarStrategy.STRATEGY, new DefaultToolbarStrategy(){
			public boolean isToolbarPart(Dockable dockable) {
				if( isSpecialDockable( dockable )){
					return true;
				}
				
				return super.isToolbarPart( dockable );
			}
			
			public Dockable ensureToolbarLayer(DockStation station, final Dockable dockable) {
				if( isSpecialDockable( dockable )){
					final FlapDockStation parent = new FlapDockStation();
					parent.setAutoDirection( false );
					parent.setDirection( Direction.SOUTH );
					parent.addDockableStateListener( new DockableStateListener() {
						@Override
						public void changed( DockableStateEvent event ) {
							if( parent.getDockParent() != null && parent.getController() != null ){
								if( dockable.getDockParent() != parent ){
									parent.add( dockable );
								}
							}
						}
					} );
					return parent;
				}
				return super.ensureToolbarLayer( station, dockable );
			}
			
			private boolean isSpecialDockable( Dockable dockable ){
				// here you can write down any condition you would like to decide which Dockable can
				// be put onto a toolbar.
				return dockable instanceof DefaultCommonDockable;
			}
		} );

		// some other stuff
		CToolbarContentArea toolbarArea = new CToolbarContentArea(control, "base");
		control.addStationContainer(toolbarArea);
		frame.add(toolbarArea);

		JSpinner timeSpinner = new JSpinner(new SpinnerDateModel());
		JSpinner.DateEditor timeEditor = new JSpinner.DateEditor(timeSpinner, "mm:ss.SSS");
		timeSpinner.setEditor(timeEditor);

		CToolbarItem prefToolbarItem = new CToolbarItem("pref");
		prefToolbarItem.setLocation(toolbarArea.getNorthToolbar().getStationLocation());
		prefToolbarItem.setItem(timeSpinner);

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

		// adding the special Dockable
		DefaultSingleCDockable specialDockable = new DefaultSingleCDockable( "a", "Aaaa" );
		control.addDockable( specialDockable );
		specialDockable.add( new JButton( "ABC" ) );
		specialDockable.setVisible( true );
		specialDockable.addCDockableStateListener( new CDockableStateListener() {
			@Override
			public void visibilityChanged( CDockable dockable ) {
				// ignore
			}
			
			@Override
			public void extendedModeChanged( CDockable dockable, ExtendedMode mode ) {
				System.out.println(mode.getModeIdentifier());	
			}
		} );
		
		frame.setBounds( 20, 20, 600, 600 );
		frame.setVisible( true );
	}
}

Follow up: the “hook” got finally added, it is called DockStationDropLayerFactory.