Customizing dockable border thickness


#1

Hi,

Could you please advice, how can I customize a dockable’s border thickness?

Thank you


#2

Depends what exactly you mean with “border”. If you mean “java.awt.Border”: let me answer this question with one of the examples that can be found in the tutorial that is delivered with the framework :smiley:

For you the most important code probably is the piece below. This how you install a “BorderModifier”. The BorderModifier is asked by the framework what border to use, and then just returns whatever is fitting. All available keys for the method “setBorderModifier” are listed in the API documentation. The keys containing “displayer” are the ones for borders that are painted around a dockable.

DockController controller = control.getController();
controller.getThemeManager().setBorderModifier( MiniButton.BORDER_KEY_MOUSE_OVER, new BorderModifier(){
    public Border modify( Border border ){
        return BorderFactory.createEtchedBorder( new Color( 150, 255, 150 ), new Color( 0, 150, 0 ) );
    }
});```

The entire example:
```package tutorial.core.basics;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.Timer;
import javax.swing.border.Border;

import tutorial.support.ColorDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;
import bibliothek.gui.DockController;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.displayer.DisplayerDockBorder;
import bibliothek.gui.dock.station.split.SplitDockGrid;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.themes.basic.action.buttons.MiniButton;
import bibliothek.gui.dock.themes.border.BorderModifier;
import bibliothek.gui.dock.util.Priority;
import bibliothek.gui.dock.util.UIBridge;
import bibliothek.gui.dock.util.UIValue;

@Tutorial( id="BorderModifier", title="Border" )
public class BorderModifierExample {
    /* Clients can install "BorderModifiers" to modify any Border used by the framework. Basically a BorderModifier
     * gets the original border, and can decide what to do with that border. The border may be replaced, it may
     * be modified, or it may be just used as it is. */
    public static void main( String[] args ) throws IOException{
        /* setting up frame and controller as usual */
        JTutorialFrame frame = new JTutorialFrame( BorderModifierExample.class );
        
        DockController controller = new DockController();
        controller.setRootWindow( frame );
        frame.destroyOnClose( controller );
        

        /* We now install our modifier. In this example we create an UIBridge of type "AlteringBridge" to apply the
         * border to only a subset of components. */
        final AlteringBridge bridge = new AlteringBridge();
        /* The priority "CLIENT" means that we override any other setting. The "kind" tells us that we will only
         * receive listeners of type "DisplayerDockBorder". The "BORDER_MODIFIER_TYPE" ensure type safety, and
         * "bridge" is the object we install. */
        controller.getThemeManager().publish( Priority.CLIENT, DisplayerDockBorder.KIND, ThemeManager.BORDER_MODIFIER_TYPE, bridge );
        
        /* We may also set a modifier directly using a key. The modifier is then applied to all places which use the key. */
        controller.getThemeManager().setBorderModifier( MiniButton.BORDER_KEY_MOUSE_OVER, new BorderModifier(){
            public Border modify( Border border ){
                return BorderFactory.createEtchedBorder( new Color( 150, 255, 150 ), new Color( 0, 150, 0 ) );
            }
        });
        
        /* As this application runs together with other applications, we have to make sure it
         * is cleaned up if closed. */
        frame.runOnClose( new Runnable(){
            public void run(){
                bridge.destroy();
            }
        });
        
        /* And now we set up different DockStations and Dockables */
        SplitDockStation splitDockStation = new SplitDockStation();
        controller.add( splitDockStation );
        frame.add( splitDockStation );
        
        SplitDockGrid grid = new SplitDockGrid();
        
        grid.addDockable(  0,  0, 100, 20, new ColorDockable( "Red", Color.RED ));
        grid.addDockable(  0, 20,  30, 50, new ColorDockable( "Blue", Color.BLUE ));
        grid.addDockable(  0, 70,  30, 30, new ColorDockable( "Yellow", Color.YELLOW ));
        grid.addDockable( 30, 20,  80, 80, new ColorDockable( "White", Color.WHITE ));
        grid.addDockable( 30, 20,  80, 80, new ColorDockable( "Black", Color.BLACK ));
        
        splitDockStation.dropTree( grid.toTree() );
        
        FlapDockStation flapDockStation = new FlapDockStation();
        controller.add( flapDockStation );
        flapDockStation.add( new ColorDockable( "Green", Color.GREEN ));
        frame.add( flapDockStation.getComponent(), BorderLayout.NORTH );
        
        ScreenDockStation screenDockStation = new ScreenDockStation( controller.getRootWindowProvider() );
        controller.add( screenDockStation );
        
        /* Now we make all frames and windows visible. */
        frame.setVisible( true );
        screenDockStation.setShowing( true );
    }
    
    /* This "UIBridge" is responsible for installing our BorderModifier. In this example
     * we create a Timer and alter the BorderModifier each second. This means that the 
     * application blinks in various colors. It also shows that a client can alter the
     * modifier at any time and the framework will react immediately. */
    private static class AlteringBridge implements UIBridge<BorderModifier, UIValue<BorderModifier>>, ActionListener{
        /* our first modifier creates a red border */
        private BorderModifier redModifier = new BorderModifier(){
            public Border modify( Border border ){
                return BorderFactory.createEtchedBorder( new Color( 255, 150, 150 ), new Color( 150, 0, 0 ) );
            }
        };
        /* our second modifier creates a blue border */
        private BorderModifier blueModifier = new BorderModifier(){
            public Border modify( Border border ){
                return BorderFactory.createEtchedBorder( new Color( 150, 150, 255 ), new Color( 0, 0, 150 ) );
            }
        };
        
        /* UIValues are listeners, and we use these listeners to inform the framework
         * about new BorderModifiers to use */
        private Set<UIValue<BorderModifier>> listeners = new HashSet<UIValue<BorderModifier>>();
        
        /* The timer triggering a change of the modifier */
        private Timer timer;
        
        /* Whether to use the red or the blue borders */
        private boolean state = true;
        
        public AlteringBridge(){
            timer = new Timer( 1000, this );
            timer.start();
        }
        
        public void destroy(){
            timer.stop();
            timer.removeActionListener( this );
        }
        
        public void actionPerformed( ActionEvent e ){
            state = !state;
            
            /* For changing the modifier we just iterate over all listeners and install
             * the new modifier with "set". */
            for( UIValue<BorderModifier> border : listeners ){
                if( state ){
                    border.set( redModifier );
                }
                else{
                    border.set( blueModifier );
                }
            }
        }
        
        /* Tells whether we should pay attention to some border. We only pay attention
         * to those borders which are shown directly on a SplitDockStation.
         * Note that we can cast uiValue to DisplayerDockBorder because of the restrictions
         * we applied when "publishing" the bridge on line 50. */
        private boolean shouldManage( UIValue<BorderModifier> uiValue ){
            DisplayerDockBorder displayer = (DisplayerDockBorder)uiValue;
            return displayer.getDisplayer().getStation() instanceof SplitDockStation;
        }
        
        public void add( String id, UIValue<BorderModifier> uiValue ){
            if( shouldManage( uiValue )){
                listeners.add( (DisplayerDockBorder)uiValue );
            }
        }

        public void remove( String id, UIValue<BorderModifier> uiValue ){
            listeners.remove( uiValue );
        }

        /* This method may be called any time for installed listeners. */
        public void set( String id, BorderModifier value, UIValue<BorderModifier> uiValue ){
            if( shouldManage( uiValue )){
                if( state ){
                    uiValue.set( redModifier );
                }
                else{
                    uiValue.set( blueModifier );
                }
            }
            else{
                uiValue.set( value );
            }
        }
    }
}```

#3

I checked in the common project only, oupsy

Thank you Beni


#4

Hi Beni,

I find use

control.getController().getThemeManager().setBorderModifier(ThemeManager.BORDER_MODIFIER+".title.tab", BorderModifier()) and control.putProperty(EclipseTheme.TAB_PAINTER,new TabPainer(); could change title border, but It will change all the dockable title borders under the controller. What method can I use to change only the title_border of the selected Dockable?

thanks,
Scott.


#5

Sorry, did not see your question for a while.

I’ll have to search a bit (that code is really old). But the answer will be something along the line of "you have to call setBorderModifierBridge instead of setBorderModifier.

How exactly should the UI look like after your modifications are applied? Can you provide an image, some description, etc?


#6

sure.
333
Just like the picture display, It’s StackDockStation. as you can see, I should personlized dockable border(no border, selected border and not selected border). Is there any easy way to change the title insets ? Is it possible to add a JLable in the top_right of the title?

Best regards,
Scott.


#7

It is possible to add Components to the tabs, because the tabs are Components too.

In the example below I just took the existing ArchGradientPainter, made a subclass, and add a JLabel.

The new label, and the original Component are both put onto a new parent Container which will later be added to the StackDockStation.


The UI of the example does not look that great. If you want a look like in the image you provided, then the fastest solution would certainly be to write a new implementation of TabComponent (in the example CustomTab does implement TabComponent indirectly)

TabComponent also is informed about the state of its Dockable - nothing, selected, focused. So it would be a good place to paint the blue underline you have in your picture.

If your new TabComponent is a subclass of BaseTabComponent you already gain a “title-JLabel” and a “buttons-JPanel”. There are methods like setLabelInsets and setButtonInsets which adds empty space around these predefined Components.

package test;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.Border;

import bibliothek.extension.gui.dock.theme.EclipseTheme;
import bibliothek.extension.gui.dock.theme.eclipse.OwnedEclipseBorder;
import bibliothek.extension.gui.dock.theme.eclipse.stack.EclipseTabPane;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.ArchGradientPainter;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.BorderedComponent;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.DefaultInvisibleTab;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.InvisibleTab;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.InvisibleTabPane;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.LinePainter;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabComponent;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabPainter;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabPanePainter;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.theme.ThemeMap;

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

		CControl control = new CControl( frame );
		control.setTheme( ThemeMap.KEY_ECLIPSE_THEME );

		control.putProperty( EclipseTheme.TAB_PAINTER, new CustomTabPainter() );

		frame.add( control.getContentArea() );

		// setting up some dockables
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 1, dockable( "Aaaa" ) );
		grid.add( 0, 0, 1, 1, dockable( "Bbbb" ) );
		grid.add( 1, 0, 1, 1, dockable( "Cccc" ) );
		control.getContentArea().deploy( grid );

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

	private static DefaultSingleCDockable dockable( String title ) {
		return new DefaultSingleCDockable( title, title );
	}

	private static class CustomTab extends ArchGradientPainter {
		private JLabel label;
		private JComponent combination;

		public CustomTab( EclipseTabPane pane, Dockable dockable ) {
			super( pane, dockable );

			// define the label popping up at the top right corner
			this.label = new JLabel( "15" );
			this.label.setBackground( Color.RED );
			this.label.setOpaque( true );

			combination = new JPanel( null ) {
				@Override
				public void doLayout() {
					Dimension add = label.getPreferredSize();

					int width = getWidth();
					int height = getHeight();

					originalComponent().setBounds( 0, add.height / 2, width - add.width / 2, height - add.height / 2 );
					label.setBounds( width - add.width, 0, add.width, add.height );
				}

				@Override
				public Dimension getPreferredSize() {
					return CustomTab.this.getPreferredSize();
				}

				@Override
				public Dimension getMinimumSize() {
					return CustomTab.this.getMinimumSize();
				}
			};

			combination.add( label );
			combination.add( originalComponent() );
		}

		private Component originalComponent() {
			return super.getComponent();
		}

		@Override
		public Component getComponent() {
			if( combination == null ) {
				return originalComponent();
			}
			return combination;
		}

		@Override
		public Dimension getPreferredSize() {
			Dimension size = super.getPreferredSize();
			Dimension add = label.getPreferredSize();

			return new Dimension( size.width + add.width / 2, size.height + add.height / 2 );
		}

		@Override
		public Dimension getMinimumSize() {
			Dimension size = super.getPreferredSize();
			Dimension add = label.getPreferredSize();

			return new Dimension( size.width + add.width / 2, size.height + add.height / 2 );
		}
	}

	private static class CustomTabPainter implements TabPainter {
		public TabComponent createTabComponent( EclipseTabPane pane, Dockable dockable ) {
			return new CustomTab( pane, dockable );
		}

		public TabPanePainter createDecorationPainter( EclipseTabPane pane ) {
			return new LinePainter( pane );
		}

		public InvisibleTab createInvisibleTab( InvisibleTabPane pane, Dockable dockable ) {
			return new DefaultInvisibleTab( pane, dockable );
		}

		public Border getFullBorder( BorderedComponent owner, DockController controller, Dockable dockable ) {
			return new OwnedEclipseBorder( owner, controller, true );
		}
	}
}

#8

Second example, this time just the blue line under a new kind of label.

package test;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Insets;

import javax.swing.JFrame;
import javax.swing.border.Border;

import bibliothek.extension.gui.dock.theme.EclipseTheme;
import bibliothek.extension.gui.dock.theme.eclipse.OwnedEclipseBorder;
import bibliothek.extension.gui.dock.theme.eclipse.stack.EclipseTabPane;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.BaseTabComponent;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.BorderedComponent;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.DefaultInvisibleTab;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.InvisibleTab;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.InvisibleTabPane;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.LinePainter;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabComponent;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabPainter;
import bibliothek.extension.gui.dock.theme.eclipse.stack.tab.TabPanePainter;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.theme.ThemeMap;

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

		CControl control = new CControl( frame );
		control.setTheme( ThemeMap.KEY_ECLIPSE_THEME );

		control.putProperty( EclipseTheme.TAB_PAINTER, new CustomTabPainter() );

		frame.add( control.getContentArea() );

		// setting up some dockables
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 1, dockable( "Aaaa" ) );
		grid.add( 0, 0, 1, 1, dockable( "Bbbb" ) );
		grid.add( 1, 0, 1, 1, dockable( "Cccc" ) );
		control.getContentArea().deploy( grid );

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

	private static DefaultSingleCDockable dockable( String title ) {
		return new DefaultSingleCDockable( title, title );
	}

	private static class CustomTab extends BaseTabComponent {
		public CustomTab( EclipseTabPane pane, Dockable dockable ) {
			super( pane, dockable );
		}

		@Override
		public Insets getOverlap( TabComponent other ) {
			return new Insets( 0, 0, 0, 0 );
		}

		@Override
		public void updateBorder() {
			// ignore
		}

		@Override
		public void updateFocus() {
			update();
		}

		@Override
		protected void updateSelected() {
			update();
		}

		@Override
		protected void updateColors() {
			// ignore
		}

		@Override
		protected void updateEnabled() {
			// ignore
		}

		@Override
		protected void updateOrientation() {
			// ignore
		}

		private void update() {
			if( isSelected() ) {
				setLabelInsets( new Insets( 0, 0, 2, 0 ) );
				setButtonInsets( new Insets( 0, 0, 2, 0 ) );
			} else {
				setLabelInsets( new Insets( 0, 0, 0, 0 ) );
				setButtonInsets( new Insets( 0, 0, 0, 0 ) );
			}
		}

		@Override
		public void paintBackground( Graphics g ) {
			super.paintBackground( g );

			if( isSelected() ) {
				int width = getWidth();
				int height = getHeight();

				g.setColor( new Color( 100, 100, 255 ) );
				g.fillRect( 0, height - 2, width, 2 );
			}
		}
	}

	private static class CustomTabPainter implements TabPainter {
		public TabComponent createTabComponent( EclipseTabPane pane, Dockable dockable ) {
			return new CustomTab( pane, dockable );
		}

		public TabPanePainter createDecorationPainter( EclipseTabPane pane ) {
			return new LinePainter( pane );
		}

		public InvisibleTab createInvisibleTab( InvisibleTabPane pane, Dockable dockable ) {
			return new DefaultInvisibleTab( pane, dockable );
		}

		public Border getFullBorder( BorderedComponent owner, DockController controller, Dockable dockable ) {
			return new OwnedEclipseBorder( owner, controller, true );
		}
	}
}

Are these answers helping you in any way? Or if not, what do you need?


#9

Thanks for your patience answer, it’s really help me a lot. I would like try your way and let you know the result.
At present, we’re working on progress, maybe the GUI will changed.

Thanks again.
Scott.


#10

Hi Beni,

I meet two problems use your ways. In our console, we used SynthLookAndFeel.

_@Override
public void paintBackground( Graphics g ) {
super.paintBackground( g );
if( isSelected() ) {
int width = getWidth();
int height = getHeight();

g.setColor( new Color( 0, 150, 214 ) );
g.fillRect( 0, height - 3, width, 3 );
}
g.setColor(new Color(51,51,51));
}_

  1. As the code display, I add the bold line. if I don’t add, the default title_text background is white. If I add, the title text background get white when they are lose focus(as the above picture).
  2. I don’t know why the title inset is invalid setting.

thanks,
Scott.


#11

Hehe, I would label it as a bug of the SyncLookAndFeel. It’s really easy to fix, in your paintBackground method first just call Graphics.create. This creates a copy of the original Graphics object, and you can play around with it without modifying the original.

		@Override
		public void paintBackground( Graphics g ) {
			g = g.create();

			super.paintBackground( g );

			if( isSelected() ) {
				int width = getWidth();
				int height = getHeight();

				g.setColor( new Color( 100, 100, 255 ) );
				g.fillRect( 0, height - 2, width, 2 );
			}
		}

I don’t understand you mean with that. You get a compiler error? Please post the entire code.

P.S. To format code, write it in triple grave accent: ```code```


#12

image

About the title_text color I rewrite paintForeground method, it’s worked.

The spacing between text and border I set as followed:

        public void update() {
            if( isSelected() ) {
                setLabelInsets( new Insets( 0, 0, 0, 0 ) );
                setButtonInsets( new Insets( 0, 0, 0, 0 ) );
            } else {
                setLabelInsets( new Insets( 0, 0, 0, 0  ) );
                setButtonInsets( new Insets( 0, 0, 0, 0 ) );
            }
        }

But the actual effect is as shown in the above picture. I tried it in a clean environment and it worked.
I have repeatedly compared it several times and did not find a place that might cause different things.
I would like to ask you is: what else may affect the title inset.
There is too much code involved, I will show you the relevant code for docking.

        DefaultScreenDockWindowFactory factory = new DefaultScreenDockWindowFactory();
	    factory.setKind(DefaultScreenDockWindowFactory.Kind.FRAME);

        controlRef.addFocusListener(new CDockableFocusListener());
        controlRef.putProperty(EclipseTheme.TAB_PAINTER, new DMTabPainter());
        controlRef.putProperty(EclipseTheme.ECLIPSE_COLOR_SCHEME, new DMColorScheme());
        controlRef.putProperty(ScreenDockStation.EXPAND_ON_DOUBLE_CLICK, true);
        controlRef.setTheme(ThemeMap.KEY_ECLIPSE_THEME);
        controlRef.putProperty(ScreenDockStation.WINDOW_FACTORY, factory);
        controlRef.getIcons().setIconClient("locationmanager.externalize", IconMngr.getImageIcon(UNDOCKING));
		controlRef.getIcons().setIconClient("locationmanager.unexternalize",IconMngr.getImageIcon(UNDOCKING));
		controlRef.putProperty( DockTheme.DOCKABLE_MOVING_IMAGE_FACTORY, new BasicMovingImageFactory());

        dockable.setMaximizable(false);
		dockable.setMinimizable(false);
		dockable.setCloseable(false);
		grid.add(0,0,1,1,dockable);

Thanks very much,
Scott.


#13

I guess you know that all your numbers are 0 in the code you posted, and thus nothing happens :slight_smile:

The insets are modified by the existing implementations for tabs, like ArchGradientPainter, RectGradientPainter and CGlassEclipseTabPainter. All of them set the insets in their update method.

There is no other code that changes the insets.


It works if you just use your DMTabPainter and nothing else? The other things you posted do not look suspicious to me.


The LabelInsets include both the icon and the title text, the red parts in the image
The ButtonInsets include all the buttons (e.g. the x for closing the Dockable), the green parts in the image.

If you want the tabs to slightly overlap (the red label with the number 15 hovering over the next tab as well), then you need to override Insets getOverlap( TabComponent other ).

tab


#15

Thanks very much. Just like you said, I ignored the Icon size. I would like use your solution.
I found I cant change the label size. if the Label set icon, the size get different with no icon label.

image

Best regards,
Scott.


#16

image
Can I remove the area marked by the red line?

Regards,
Scott.


#17

Probably yes. I don’t know which code generates the empty space.

I would try

  • switching the border (try returning something else in getFullBorder)
  • make sure the insets of the tabs are not too big, especially not Insets.top
  • pfff… starting the debugger and trying to find the Component that is painted at the empty white spot

#18

I Fixed it, used StrechIcon solved this problem( link: http://www.camick.com/java/source/StretchIcon.java).

I have another question bother you again:stuck_out_tongue_winking_eye:
I used the method :controller.getContentArea().getInsets(), print result is : inset(2,2,2,2) and I haven’t find any settings about this. could I remove this inset?

thanks,
Scott.