Home > Swing > Multiple floating toolbars with Swing

Multiple floating toolbars with Swing

These are my holidays, I should be sitting at the beach, not coding. Unfortunately I can’t; and since when I am bored I do things I like, coding is still one good way to kill some time. So I was restarting an old pet project of mine, some kind of editor and I pretty early ran into a problem: The Java gods have brought us floating toolbars that can be docked to the sides of a BorderLayout. Unfortunately the BorderLayout is the only layout they work with und unfortunately a BorderLayout can just hold one component in each of its border regions, so you can’t have more than one JToolbar on each side, which unfortunately renders the floating functionality pretty useless by default. They might have thought “YAGNI”, when the idea of multiple toolbars next to each other crossed their minds, I don’t know. Unfortunately “I am gonna need it”, but fortunately there is remedy.

The basic concept

I came across a solution by Stanislav Lapitsky, which didn’t quite work the way I wanted (it laid out the toolbars in the north under each other, not next to each other), but it demonstrated the basic principle. This is where I took over his code, refactored and modified it to my needs. What I will give you here is still just a prototype, it’s by no means complete, it still lacks shrinking components down to their minimum size if necessary, lacks avoiding overlaps and so on, but it too demonstrates the principle. The basic concept of Stanislav was to extend the BorderLayout class, forget everything it is doing, and implement an own solution that can hold multiple components per region, and it works great.
Here is how it looks like:

Multiple floating toolbars with Swing

Implementation

The implementation looks quite straightforward, just copy and paste the following code. The Java compiler settings should comply with Java 1.7 (for Java 1.6 you just need to add some type arguments to the diamond operators, that’s all). I’ve stripped out some comments that are generated by Javadoc and your IDE automatically by document inheritance.

import static java.util.Arrays.asList;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

/**
 * Extends BorderLayout with multiple components in the northList,
 * southList, eastList, westList and centerList. Layout is used for
 * correct working multiple
 * toolbars.
 * 
 * @author Stanislav Lapitsky (basic idea)
 * @author Wanja Gayk (major rework, layout change)
 * @version 2.0
 */
public class MultiBorderLayout extends BorderLayout{
 
 private static final long serialVersionUID=2L;
 
 private interface DimensionGetter{
  Dimension get(final Component in);
 }
 
 private static final DimensionGetter getMinimumSize=new DimensionGetter(){
  @Override
  public Dimension get(final Component in){
   return in.getMinimumSize();
  }
 };
 
 private static final DimensionGetter getPreferredSize=new DimensionGetter(){
  @Override
  public Dimension get(final Component in){
   return in.getPreferredSize();
  }
 };
 
 private static final DimensionGetter getMaximumSize=new DimensionGetter(){
  @Override
  public Dimension get(final Component in){
   return in.getMaximumSize();
  }
 };
 
 /** List of the north region components. */
 private final List<Component> northList=new ArrayList<>();
 
 /** List of the south List region components. */
 private final List<Component> southList=new ArrayList<>();
 
 /** List of the west region components. */
 private final List<Component> westList=new ArrayList<>();
 
 /** List of the east region components. */
 private final List<Component> eastList=new ArrayList<>();
 
 /** List of the center region components. */
 private final List<Component> centerList=new ArrayList<>();
 
 /** Constructs default layout instance. */
 public MultiBorderLayout(){ }
 
 /**
  * Constructs new layout instance with defined parameters.
  * @param hgap
  *        the horizontal gap.
  * @param vgap
  *        the vertical gap.
  */
 public MultiBorderLayout(final int hgap,final int vgap){
  super(hgap,vgap);
 }
 
 /* The method is deprecated but it's necessary to override it
  * because the current class extends BorderLayout to provide multiple
  * components (e.g. toolbars) */
 @SuppressWarnings("deprecation")
 @Override
 public void addLayoutComponent(final String name,final Component comp){
  synchronized(comp.getTreeLock()){
   // Assign the component to one of the regions.
   // Special case: treat null the same as "Center".
   if(name == null || CENTER.equals(name)){ 
    centerList.add(comp);
   }else if(NORTH.equals(name)){
    northList.add(comp);
   }else if(SOUTH.equals(name)){
    southList.add(comp);
   }else if(WEST.equals(name)){
    westList.add(comp);
   }else if(EAST.equals(name)){
    eastList.add(comp);
   }else{
    throw new IllegalArgumentException(
     "cannot add to layout: unknown constraint: " + name);
   }
  }
 }
 
 @Override
 public void removeLayoutComponent(final Component comp){
  synchronized(comp.getTreeLock()){
   northList.remove(comp);
   southList.remove(comp);
   westList.remove(comp);
   eastList.remove(comp);
   centerList.remove(comp);
  }
 }
 
 @Override
 public Dimension minimumLayoutSize(final Container target){
  return layoutSize(target,getMinimumSize);
 }
 
 @Override
 public Dimension preferredLayoutSize(final Container target){
  return layoutSize(target,getPreferredSize);
 }
 
 @Override
 public Dimension maximumLayoutSize(Container target){
  return layoutSize(target,getMaximumSize);
 }
 
 private Dimension layoutSize(final Container target,
                              final DimensionGetter getter){
  synchronized(target.getTreeLock()){
   final Dimension northSize=sumHorizontal(northList,getter);
   final Dimension southSize=sumHorizontal(southList,getter);
   final Dimension westSize=sumVertical(westList,getter);
   final Dimension eastSize=sumVertical(eastList,getter);
   final Dimension centerSize=sumCenter(centerList,getter);
   
   final Dimension dim=new Dimension(//
     max(northSize.width,
         southSize.width,
         westSize.width + centerSize.width + eastSize.width
     ) //
    , northSize.height 
     + max(westSize.height,centerSize.height,eastSize.height)
     + southSize.height
   );
   
   final Insets insets=target.getInsets();
   dim.width+=insets.left + insets.right;
   dim.height+=insets.top + insets.bottom;
   return dim;
  }
 }
 
 @Override
 public void layoutContainer(final Container target){
  synchronized(target.getTreeLock()){
   final Insets insets=target.getInsets();
   
   final Rectangle availableBounds = new Rectangle(
      insets.left
    , insets.top 
    , target.getWidth()- insets.left - insets.right
    , target.getHeight() - insets.top - insets.bottom
   );
   
   // TODO: this is only using the preferred size, shrink components
   // to their minimum size, if preferred Size doesn't fit!
   
   final Dimension northSize=sumHorizontal(northList,getPreferredSize);
   final Dimension southSize=sumHorizontal(southList,getPreferredSize);
   final Dimension westSize=sumVertical(westList,getPreferredSize);
   final Dimension eastSize=sumVertical(eastList,getPreferredSize);
   
   int left,right,top,bottom;
   
   left=availableBounds.x;
   top=availableBounds.y;
   for(final Component c:northList){
    if(c.isVisible()){
     final Dimension d=sumHorizontal(asList(c),getPreferredSize);
     c.setBounds(left,top,d.width,d.height);
     left+=d.width;
    }
   }
   
   left=availableBounds.x;
   bottom=availableBounds.y + availableBounds.height;
   for(final Component c:southList){
    if(c.isVisible()){
     final Dimension d=sumHorizontal(asList(c),getPreferredSize);
     c.setBounds(left,bottom - d.height,d.width,d.height);
     left+=d.width;
    }
   }
   
   left=availableBounds.x;
   top=availableBounds.y + northSize.height;
   for(final Component c:westList){
    if(c.isVisible()){
     final Dimension d=sumVertical(asList(c),getPreferredSize);
     c.setBounds(left,top,d.width,d.height);
     top+=d.height;
    }
   }
   
   right=availableBounds.x + availableBounds.width;
   top=availableBounds.y + northSize.height;
   for(final Component c:eastList){
    if(c.isVisible()){
     final Dimension d=sumVertical(asList(c),getPreferredSize);
     c.setBounds(right - d.width,top,d.width,d.height);
     top+=d.height;
    }
   }
   
   top=availableBounds.x + northSize.height;
   left=availableBounds.y + westSize.width;
   right=availableBounds.x + availableBounds.width - eastSize.width;
   bottom=availableBounds.y + availableBounds.height - southSize.height;
   for(final Component c:centerList){
    if(c.isVisible()){
     c.setBounds(left,top,right - left,bottom - top);
    }
   }
  }
 }
 
 private Dimension sumHorizontal(final Iterable<Component> components,
                                 final DimensionGetter getter){
  final Dimension dim=new Dimension();
  for(final Component c:components){
   if(c.isVisible()){
    final Dimension d=getter.get(c);
    dim.width+=d.width + getHgap();
    dim.height=Math.max(d.height,dim.height);
   }
  }
  return dim;
 }
 
 private Dimension sumVertical(final Iterable<Component> components,
                               final DimensionGetter getter){
  final Dimension dim=new Dimension();
  for(final Component c:components){
   if(c.isVisible()){
    final Dimension d=getter.get(c);
    dim.width=Math.max(d.width,dim.width);
    dim.height+=d.height + getVgap();
   }
  }
  return dim;
 }
 
 @SuppressWarnings("static-method")
 private Dimension sumCenter(final Iterable<Component> components,
                             final DimensionGetter getter){
  final Dimension dim=new Dimension();
  for(final Component c:components){
   if(c.isVisible()){
    final Dimension d=getter.get(c);
    dim.width+=d.width;
    dim.height=Math.max(d.height,dim.height);
   }
  }
  return dim;
 }
 
 private static int max(final int...values){
  int max=0;
  for(int value:values){
   max=Math.max(max,value);
  }
  return max;
 }
}

Testing

The following test code, which is a virtually unchanged version of Stanislav’s orginal, will help you play around with some JToolbars in the new Layout. Just grab some and move them to the sides or outside of the component.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToolBar;
import javax.swing.WindowConstants;

public class MultiFloatableToolbar extends JFrame{
 
 public MultiFloatableToolbar(){
  super("Multiple float toolbars example");
  setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
  getContentPane().setPreferredSize(new Dimension(360,270));
  getContentPane().setLayout(new MultiBorderLayout(0,0));
  getContentPane().add(createToolbar("a","b"),BorderLayout.NORTH);
  getContentPane().add(createToolbar("c","d"),BorderLayout.NORTH);
  getContentPane().add(createToolbar("e","f"),BorderLayout.NORTH);
  getContentPane().add(new JLabel("Center area"),BorderLayout.CENTER);
 }
 
 protected JToolBar createToolbar(final String s1,final String s2){
  final JToolBar bar=new JToolBar();
  bar.add(new JButton(s1));
  bar.add(new JButton(s2));
  return bar;
 }
 
 public static void main(final String args[]){
  EventQueue.invokeLater(new Runnable(){
   @Override
   public void run(){
    final MultiFloatableToolbar frame=new MultiFloatableToolbar();
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
   }
  });
 }
}

Final words

I’d like to thank Stanislav for publishing his code; I very much appreciate how helpful it is sometimes, when you search the internet and you find a simple, working example that you can just copy and paste to your IDE and it works without further ado. In case you’re interested in his original version, have a look at:
http://java-sl.com/tip_multiple_floatable_toolbars.html

Advertisements
Categories: Swing Tags: , ,
  1. clément
    September 19, 2015 at 18:30

    It’s very helpful ! Thank you a lot !

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: