Dynamic ColorBridge and access to DockTitle

Thanks to the nice examples I have successfully managed to get a custom ColorBridge work for tile colors. Cool! :slight_smile:
Now I would like to have dynamic title colors, a boolean switch (i.e. a selectable action) and based on the state the title background color should be red or blue.

If I don’t use the ColorBridge approach but simply
CDockable.getColors().setColor(ColorMap.COLOR_KEY_TITLE_BACKGROUND_FOCUSED, Color.RED );
than it works fine. Color changes are immediately visible.

Unfortunately that doesn’t really work for FlatTheme (which I need for the title gradient), here I have to install a ColorBridge to set my title background gradient colors.

[ul]
[li]How do I set custom dynamic background gradient colors for a specific CDockable so a gradient color change (based on the state of my boolean switch) is immediately visible?
[/li][li]And another question: How can I access the (Basic)DockTitle from a CDockable (in my case a DefaultSingleCDockable)?
[/li](I have search everything and didn’t find any hint.)
[/ul]
BTW: Thanks a lot for DockingFrames!

I’ll write an example later this evening, but the ColorBridge knows (methods add and remove) who is using the colors and thus can inform about changes.

For the second question: really depends on what your goals are. Often you have to replace a title through the DockTitleManager and then your custom title is free to do whatever you want. For more specific task there are more specific answers :wink:

Thanks Benni, here the more specific question:

I have a ©Dockable and would like to get a reference to the belonging DockTitle (if possible without doing all the DockTitleManager magic).

In an ideal world I would like to have: Dockable.getDockTitle() :wink:

There is a method „Dockable.listBoundTitles“ („CDockable.intern().listBoundTitles“) which gives you all the titles that are currently used for a Dockable. And the DockableListener will be informed whenever a title is added or removed. Still, I’m not convinced that you really want to use these methods :slight_smile: .

Aha, here we go! That was a hard one (I guess you are right, I shouldn’t use these methods, but it is what I was looking for!)

Nevertheless last question for today (it’s already late in my timezone):

Why do we need more than one DockTitle per Dockable?
(I guess because the dockable can dock to different stations, so we need various titles to visually represent the dockable correctly per station.)

And how do I determine which DockTitle is currently used?

  • Because of the FlapDockStation (shows minimized Dockables), it shows two titles at the same time: the button which pops up a window with a Dockable, and a title on the window itself.
  • Because during drag and drop operations some themes paint an additional title near the mouse cursor.
    (- And clients can show titles as well)

All the titles that are bound (returned by listBoundTitles) are currently in use. Meaning they are (most likely) painted somewhere on the screen. Titles are created and disposed all the times, e.g. when moving a Dockable the old titles are deleted and new titles are shown.

If you want to know which title is currently dragged by the user… I’d have to think a bit how to implement that. In theory the DockRelocator would know that, but that knowledge is not public. But I could expose it through its listener interface.

Good morning, here is the example for the colors. It’s only using the Core library, but converting it to Common should not be a problem for you. Instead of using a “DefaultDockable” use a “DefaultSingleCDockable” and the method “extract” has to call “((CommonDockable)dockable).getDockable()” first.


import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JFrame;

import bibliothek.extension.gui.dock.theme.FlatTheme;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.action.DefaultDockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.action.SelectableDockAction;
import bibliothek.gui.dock.action.actions.SimpleSelectableAction;
import bibliothek.gui.dock.event.SelectableDockActionListener;
import bibliothek.gui.dock.station.split.SplitDockProperty;
import bibliothek.gui.dock.themes.color.TitleColor;
import bibliothek.gui.dock.util.Priority;
import bibliothek.gui.dock.util.color.ColorBridge;
import bibliothek.gui.dock.util.color.DockColor;

public class Dock48 {
	/* This example shows how a Dockable and a ColorBridge can be combined to instantly change
	 * colors depending on the state of the Dockable */
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		DockController controller = new DockController();
		controller.setRootWindow( frame );
		controller.setTheme( new FlatTheme() );
		
		SplitDockStation center = new SplitDockStation();
		controller.add( center );
		frame.add( center );
		
		controller.getColors().publish( Priority.CLIENT, TitleColor.KIND_TITLE_COLOR, new CustomColorBridge() );
		
		center.drop( new CustomDockable( "A" ) );
		center.drop( new CustomDockable( "B" ), new SplitDockProperty( 0, 0, 1.0, 0.5 ));
		center.drop( new CustomDockable( "C" ), new SplitDockProperty( 0, 0, 0.5, 0.5 ));
		center.drop( new CustomDockable( "D" ), new SplitDockProperty( 0, 0.5, 0.5, 0.5 ));
		
		frame.setBounds( 20, 20, 400, 400 );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setVisible( true );
	}

	/* To be notified about changes of the state of our Dockables we create this listener interface */
	private static interface CustomListener{
		public void colorChanged( CustomDockable dockable );
	}
	
	/* And this is the action that will change the state of our Dockables */
	private static class CustomAction extends SimpleSelectableAction.Check implements SelectableDockActionListener{
		private CustomDockable dockable;
		
		public CustomAction( CustomDockable dockable ){
			this.dockable = dockable;
			setText( "switch" );
			addSelectableListener( this );
		}
		
		@Override
		public void selectedChanged( SelectableDockAction action, Set<Dockable> dockables ){
			dockable.setState( isSelected() );
		}
	}
	
	/* And this is our Dockable with a "state", if the state changes the Dockable fires an event. */
	private static class CustomDockable extends DefaultDockable{
		private List<CustomListener> listeners = new ArrayList<CustomListener>();
		private boolean state = false;
		
		public CustomDockable( String title ){
			setTitleText( title );
			DefaultDockActionSource source = new DefaultDockActionSource( new LocationHint( LocationHint.DOCKABLE, LocationHint.LEFT ) );
			source.add( new CustomAction( this ) );
			setActionOffers( source );
		}
		
		public void addCustomListener( CustomListener listener ){
			listeners.add( listener );
		}
		
		public void removeCustomListener( CustomListener listener ){
			listeners.remove( listener );
		}
		
		public boolean isState(){
			return state;
		}
		
		public void setState( boolean state ){
			if( this.state != state ){
				this.state = state;
				for( CustomListener listener : listeners ){
					listener.colorChanged( this );
				}
			}
		}
	}
	
	/* Now the interesting part: this ColorBridge changes the colors of DockTitles according to the current state of 
	 * a CustomDockable. */
	private static class CustomColorBridge implements ColorBridge, CustomListener{
		/* These are all the DockColors that are currently using a color, we only store the interesting ones - any
		 * color not associated with a CustomDockable is ignored. Please note that we always carefully ensure that
		 * no entry overrides another entry in these maps. */
		private Map<CustomDockable, Map<String, List<DockColor>>> observers = new HashMap<CustomDockable, Map<String,List<DockColor>>>();
		
		@Override
		public void colorChanged( CustomDockable dockable ){
			/* If the state of one of our Dockables changed, we search its DockColors and update them */
			Map<String, List<DockColor>> map = observers.get( dockable );
			if( map != null ){
				for( Map.Entry<String, List<DockColor>> entry : map.entrySet() ){
					for( DockColor uiValue : entry.getValue() ){
						maybeSet( entry.getKey(), uiValue );
					}
				}
			}
		}
		
		/* This methods stores "color" in a map, it returns "true" if this is the first time
		 * that "dockable" is using a color. */
		private boolean add( CustomDockable dockable, String id, DockColor color ){
			boolean result = false;
			Map<String, List<DockColor>> map = observers.get( dockable );
			if( map == null ){
				result = true;
				map = new HashMap<String, List<DockColor>>();
				observers.put( dockable, map );
			}
			
			List<DockColor> list = map.get( id );
			if( list == null ){
				list = new ArrayList<DockColor>();
				map.put( id, list );
			}
			list.add( color );
			
			return result;
		}
		
		/* This method removes "color" from the care of this ColorBridge. The method returns
		 * "true" if "color" was the last DockColor "dockable" was using. */
		private boolean remove( CustomDockable dockable, String id, DockColor color ){
			boolean result = false;
			Map<String,List<DockColor>> map = observers.get( dockable );
			List<DockColor> list = map.get( id );
			list.remove( color );
			if( list.isEmpty() ){
				map.remove( id );
				if( map.isEmpty() ){
					observers.remove( dockable );
					result = true;
				}
			}
			return result; 
		}
		
		/* Given a DockColor tells which CustomDockable is associated with that DockColor */
		private CustomDockable extract( DockColor uiValue ){
			TitleColor color = (TitleColor)uiValue;
			Dockable dockable = color.getTitle().getDockable();
			if( dockable instanceof CustomDockable ){
				return (CustomDockable)dockable;
			}
			return null;
		}
		
		/* Called when a DockColor starts requesting values. */
		@Override
		public void add( String id, DockColor uiValue ){
			CustomDockable dockable = extract( uiValue );
			if( dockable != null ){
				if( add( dockable, id, uiValue )){
					dockable.addCustomListener( this );
				}
			}
		}
		
		/* Called when a DockColor no longer requests values */
		@Override
		public void remove( String id, DockColor uiValue ){
			CustomDockable dockable = extract( uiValue );
			if( dockable != null ){
				if( remove( dockable, id, uiValue )){
					dockable.removeCustomListener( this );
				}
			}
		}
		
		/* Updates the current value of "uiValue" */
		@Override
		public void set( String id, Color value, DockColor uiValue ){
			if( !maybeSet( id, uiValue )){
				uiValue.set( value );
			}
		}
		
		/* Replaces some colors of our choosing by other colors that depend on the 
		 * current state of the CustomDockable that is associated with "uiValue" */
		private boolean maybeSet( String id, DockColor uiValue ){
			CustomDockable dockable = extract( uiValue );
			if( dockable == null ){
				return false;
			}
			if( dockable.isState() ){
				if( id.equals( "title.active.left" )){
					uiValue.set( Color.RED.brighter() );
					return true;
				}
				else if( id.equals(  "title.inactive.left" )){
					uiValue.set( Color.ORANGE.brighter() );
					return true;
				}
				else if( id.equals(  "title.active.right" )){
					uiValue.set( Color.RED.darker() );
					return true;
				}
				else if( id.equals(  "title.inactive.right" )){	
					uiValue.set( Color.ORANGE.darker() );
					return true;
				}
				else{
					return false;
				}
			}
			else{
				if( id.equals( "title.active.left" )){
					uiValue.set( Color.BLUE.brighter() );
					return true;
				}
				else if( id.equals(  "title.inactive.left" )){
					uiValue.set( Color.CYAN.brighter() );
					return true;
				}
				else if( id.equals(  "title.active.right" )){
					uiValue.set( Color.BLUE.darker() );
					return true;
				}
				else if( id.equals(  "title.inactive.right" )){	
					uiValue.set( Color.CYAN.darker() );
					return true;
				}
				else{
					return false;
				}				
			}
		}
	}
}