Groups of CActions

Hello,

I am using CActions on my Dockables. Suppose I have three groups of CActions: red actions, green actions, and blue actions, that should always appear in that order.

I need to dynamically remove some actions, and add some. But if I want to add say a red action, I obviously want it to be appended at the end of the red actions. Hence I cannot use dockable.addAction, and I have to keep track of the number of red actions I have, so that I use dockable.insertAction at some index.

My question is: to avoid keeping track of the number of actions created in each group, etc., is there a way to define some sort of “group of actions”? Something that allows me for instance to tag all red actions as belonging to the same group, and when I want to add a new red action, I add it to the group, without having to worry about where would it appear on my dockable.

Thanks a lot,
Shant

Maybe just disabling the actions would be an alternative?

But there is a way to build groups. It is not necessary a “nice” way, but it works well enough. All you need to know is that actions are always grouped in “DockActionSources” (the sources build a tree), and that we can smuggle a few new sources into our Dockable. The code below will do that, notice that “addAction” or “insertAction” still works, but actions added that way will appear either on the left or the right side of our new group, never inside the group.

[Edit: there are other, more general solutions, like using an ActionGuard. But this solution here requires no set up and only one new class.]


import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;

import javax.swing.Icon;
import javax.swing.JFrame;

import bibliothek.gui.dock.action.DefaultDockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.action.MultiDockActionSource;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.intern.DefaultCommonDockable;

public class Dock26 {
	public static void main( String[] args ){
		/* A frame and control... */
		JFrame frame = new JFrame( "Test" );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 300, 300 );

		CControl control = new CControl( frame );
		frame.add( control.getContentArea() );
		
		/* This is our specialized SingleCDockable that offers groups for actions */
		ActionCDockable dockable = new ActionCDockable( "t", "Test" );
		control.addDockable( dockable );
		dockable.setVisible( true );
		
		/* We create a few actions, and tell them to which group the will belong */
		ColorAction red = new ColorAction( Color.RED, dockable.redActions );
		ColorAction green = new ColorAction( Color.GREEN, dockable.greenActions );
		ColorAction blue = new ColorAction( Color.BLUE, dockable.blueActions );
		
		/* We tell the groups what actions exist. Notice the call to "intern", we need to
		 * use API from Core to work here. */
		dockable.redActions.add( red.intern() );
		dockable.greenActions.add( green.intern() );
		dockable.blueActions.add( blue.intern() );
		
		frame.setVisible( true );
	}
	
	/* This is one of our grouped actions. If the user clicks on it, it adds a copy of
	 * itself into its group. */
	private static class ColorAction extends CButton{
		private Color color;
		private DefaultDockActionSource group;
		
		public ColorAction( Color color, DefaultDockActionSource group ){
			setIcon( new ColorIcon( color ) );
			this.color = color;
			this.group = group;
		}
		
		@Override
		protected void action(){
			super.action();
			group.add( new ColorAction( color, group ).intern() );
		}
	}
	
	private static class ColorIcon implements Icon{
		private Color color;
		
		public ColorIcon( Color color ){
			this.color = color;
		}

		@Override
		public void paintIcon( Component c, Graphics g, int x, int y ){
			g.setColor( color );
			g.fillOval( x, y, 16, 16 );
		}

		@Override
		public int getIconWidth(){
			return 16;
		}

		@Override
		public int getIconHeight(){
			return 16;
		}
	}
	
	/* Our specialed SingleCDockable... */
	public static class ActionCDockable extends DefaultSingleCDockable{
		/* ... has an additional "DockActionSource" which represents *all* groups, a "super-group". 
		 * We could achieve the same effects without this object, but the code is more readable with it. */
		private MultiDockActionSource groupedActions;
		
		/* These are our groups */
		private DefaultDockActionSource redActions;
		private DefaultDockActionSource greenActions;
		private DefaultDockActionSource blueActions;
		
		public ActionCDockable( String id, String title ){
			super( id, title );
			
			/* Each group is just a list of DockActions */
			redActions = new DefaultDockActionSource();
			greenActions = new DefaultDockActionSource();
			blueActions = new DefaultDockActionSource();
			
			/* And we need to tell our super-group which groups exist */
			groupedActions.add( redActions );
			groupedActions.add( greenActions );
			groupedActions.add( blueActions );
		}
		
		@Override
		protected DefaultCommonDockable createCommonDockable(){
			/* As this method is called from the super constructor, we need to initalize and assign
			 * "groupedActions" here */
			if( groupedActions == null ){
				/* The LocationHint tells at which location, in respect to other actions, our groups appear */
				groupedActions = new MultiDockActionSource( new LocationHint( LocationHint.LEFT ) );
			}
			/* Finally we execute the same code as the original "createCommonDockable", but smuggle our
			 * super-group into the call. */
			return new DefaultCommonDockable( this, groupedActions, getClose() );
		}
	}
}

Thank you Beni!

This solution for building groups works well enough indeed.

Sorry Beni, just a small question: in your createCommonDockable implementation above, why do you have the if(groupedActions == null) check?

That gives the impression that the method is called more than once, and hence we want to assign “groupedActions” only the first time. Is that true?

Isn’t it only called from the super constructor, as in your comment?

Hm, yes you are right, that was just a bit lazy from me. The check can be removed, it is completely unnecessary.

Thanks! :smiley: