JMU
Custom GUI Layout
in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
A "Flip Chart" Layout Manager
javaexamples/slides/FlipChartLayout.java
        package slides;

import java.awt.*;
import java.util.*;

/**
 * A LayoutManager that treats each component in a container 
 * as if it is on a page of a flip chart.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class FlipChartLayout implements LayoutManager
{
    private LinkedList       components;


    private static final int MINIMUM_SIZE   = 0;
    private static final int PREFERRED_SIZE = 1;


    /**
     * Default Constructor
     */
    public FlipChartLayout() 
    {
       components = new LinkedList();
    }



    /**
     * Adds the specified component to this LayoutManager.
     * (Required by LayoutManager)
     *
     * @param name The name of the component (not used)
     * @param comp The component to be added
     */
    public void addLayoutComponent(String name, Component comp) 
    {
       synchronized (comp.getTreeLock()) 
       {
          if   (components.isEmpty()) comp.setVisible(true);
          else                        comp.setVisible(false);
          components.addLast(comp);
       }
    }




    /**
     * Flips the pages backward by one
     *
     * @param parent   The parent container
     */
    public void backward(Container parent) 
    {
       Component     comp;

       synchronized (parent.getTreeLock()) 
       {
          // Throw an Exception if the parent isn't using this layout
          checkLayout(parent);

          if (!components.isEmpty()) 
          {
             comp = (Component)components.getFirst();
             comp.setVisible(false);
             comp = (Component)components.removeLast();
             comp.setVisible(true);
             components.addFirst(comp);
             parent.validate();
          }
       }
    }





    /**
     * Determines either maximum (of each dimension) 
     * of the components in the container.
     *
     * @param  parent  The name of the parent container
     * @return         The preferred size
     */
    private Dimension calculateLayoutSize(Container parent, int type) 
    {
       Component    comp;
       Dimension    d;
       Insets       insets;
       int          w, h;
       Iterator     iter;

       synchronized (parent.getTreeLock()) 
       {
          // Throw an Exception if the parent isn't using this layout
          checkLayout(parent);

          // Initialize the width and height
          w = 0;
          h = 0;

          // Get the insets (i.e., unused space) for the parent
          insets = parent.getInsets();

          // Calculate the maximum (along each dimension)
          iter = components.iterator();
          while (iter.hasNext()) 
          {
             comp = (Component)iter.next();

             if (type == PREFERRED_SIZE) d = comp.getPreferredSize();
             else                        d = comp.getMinimumSize();

             if (d.width  > w) w = d.width;
             if (d.height > h) h = d.height;
          }

          return new Dimension(insets.left + insets.right + w,
                               insets.top + insets.bottom + h);
       }
    }





    /**
     * Make sure that the parent is actually using this
     * FlipChartLayout
     *
     * @param parent   The parent container
     */
    private void checkLayout(Container parent) 
    {
       if ((parent == null) || (parent.getLayout() != this)) 
       {
          throw new IllegalArgumentException("The parent container "+
                                             "is not using a "+
                                             "FlipChartLayout");
       }
    }




    /**
     * Flips the pages forward by one
     *
     * @param parent   The parent container
     */
    public void forward(Container parent) 
    {
       Component     comp;

       synchronized (parent.getTreeLock()) 
       {
          // Throw an Exception if the parent isn't using this layout
          checkLayout(parent);

          if (!components.isEmpty()) 
          {
             comp = (Component)components.removeFirst();
             comp.setVisible(false);
             components.addLast(comp);
             comp = (Component)components.getFirst();
             comp.setVisible(true);
             parent.validate();
          }
       }
    }



    /**
     * Gets the current front/first page
     *
     * @param parent   The parent container
     */
    public Component getFirst(Container parent) 
    {
       Component     comp;

       synchronized (parent.getTreeLock()) 
       {
          // Throw an Exception if the parent isn't using this layout
          checkLayout(parent);

          comp = null;
          if (!components.isEmpty()) 
          {
             comp = (Component)components.getFirst();
          }

          return comp;
       }
    }



    /**
     * Lays out the specified container.
     * (Required by LayoutManager)
     *
     * Specifically, the first component is reshaped
     * to be the size of the container, minus space for surrounding
     * insets.
     *
     * @param parent  The parent container
     */
    public void layoutContainer(Container parent) 
    {
       Component    first;
       Dimension    d;
       Insets       insets;

       synchronized (parent.getTreeLock()) 
       {
          // Throw an Exception if the parent isn't using this layout
          checkLayout(parent);

          if (!components.isEmpty()) 
          {
             d      = parent.getSize();
             insets = parent.getInsets();

             first  = (Component)components.getFirst();

             first.setBounds(insets.left, insets.top,
                             d.width - (insets.left + insets.right),
                             d.height - (insets.top + insets.bottom));

             first.setVisible(true);
          }
       }
    }



    /**
     * Calculates the minimum size for the specified panel.
     * (Required by LayoutManager)
     *
     * In this case, it is simply the maximum of the minimum sizes
     * of the "pages" of the FlipChart.
     *
     * @param  parent  The name of the parent container
     * @return         The preferred size
     */
    public Dimension minimumLayoutSize(Container parent) 
    {
       return calculateLayoutSize(parent, MINIMUM_SIZE);
    }




    /**
     * Determines the preferred size of the container.
     * (Required by LayoutManager)
     *
     * In this case, it is simply the maximum of the preferred sizes
     * of the "pages" of the FlipChart.
     *
     * @param  parent  The name of the parent container
     * @return         The preferred size
     */
    public Dimension preferredLayoutSize(Container parent) 
    {
       return calculateLayoutSize(parent, PREFERRED_SIZE);
    }



    /**
     * Removes the specified component from the layout.
     * (Required by LayoutManager)
     *
     * @param   comp   The component to be removed
     */
    public void removeLayoutComponent(Component comp) 
    {
       Component   first;
       int         index;

       synchronized (comp.getTreeLock()) 
       {
          if (!components.isEmpty()) 
          {
             first = (Component)components.getFirst();
             if (first == comp) 
             {
                comp.setVisible(false);
                components.removeFirst();

                if (!components.isEmpty()) 
                {
                   first = (Component)components.getFirst();
                   first.setVisible(true);
                }
             } 
             else 
             {
                components.remove(comp);
             }
          }
       }
    }

}

        
A "Slide" Layout Manager
javaexamples/slides/SlideLayout.java
        package slides;

import java.awt.*;
import java.util.*;

/**
 * A LayoutManager that can be used for slide
 * presentations and outlines
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class SlideLayout implements LayoutManager
{
    private Font[]        fonts;
    private Hashtable     constraints;
    private int           numLevels;
    private int[]         indents, vSpaces;

    private static final int indentWidthInPixels = 40;
    private static final int lineHeightInPixels  = 50;


    public static final String CENTER = "CENTER";
    public static final String LEVEL0 = "LEVEL0";
    public static final String LEVEL1 = "LEVEL1";
    public static final String LEVEL2 = "LEVEL2";


    /**
     * Default constructor
     */
    public SlideLayout()
    {
        int             i, iSize, fSize;

        constraints = new Hashtable();

        numLevels = 5;

        indents = new int[numLevels];
        vSpaces = new int[numLevels];
        fonts   = new Font[numLevels];
        

        fSize = 24;
        iSize = 0;
        for (i=0; i < numLevels; i++) 
        {
            indents[i] = iSize;
            fonts[i]   = new Font("Sans Serif", Font.PLAIN, fSize);
            vSpaces[i] = lineHeightInPixels / 5;
            fSize -= 3;
            iSize += indentWidthInPixels;
        }
        vSpaces[0] = lineHeightInPixels;
    }




    /**
     * Adds the specified component with the specified "constraint"
     * to the layout (Required by LayoutManager)
     *
     * @param constraint The constraint (e.g., "LEVEL0" or "CENTER")
     * @param comp       The component
     */
    public void addLayoutComponent(String constraint, Component comp)
    {
        synchronized (comp.getTreeLock()) 
        {
            constraints.put(comp, constraint);
        }
    }






    /**
     * Lays out the container in the specified panel
     * (Required by LayoutManager)
     *
     * @param The Container to layout
     */
    public void layoutContainer(Container parent)
    {
        Component[]       e;
        Component         comp;
        Dimension         dComp, dParent;
        int               i, level, x, y;
        String            constraint;


        synchronized (parent.getTreeLock()) 
        {
            dParent = parent.getSize();
            y = 0;

            e = parent.getComponents();
            for (i=0; i < e.length; i++) 
            {
                comp   = e[i];
                constraint = (String)constraints.get(comp);

                if (constraint.equals(CENTER)) 
                {
                    y += vSpaces[2];
                    dComp = comp.getSize();
                    x = dParent.width/2 - dComp.width/2;
                } 
                else 
                {
                    if      (constraint.equals(LEVEL0)) level = 0;
                    else if (constraint.equals(LEVEL1)) level = 1;
                    else if (constraint.equals(LEVEL2)) level = 2;
                    else                                level = 2;
                    
                
                    x = indents[level];
                    y += vSpaces[level];
                    comp.setFont(fonts[level]);
                }

                locateComponent(comp, x, y);
                
                // Update the vertical position
                dComp = comp.getSize();
                y     += dComp.height;
                
            }
        }
    }


    /**
     * Set the location of a component
     *
     * @param comp   The component
     * @param x      The horizontal location
     * @param y      The vertical location
     */
    protected void locateComponent(Component comp, int x, int y)
    {
        Dimension         dComp;

        
        // Set the width of the Component
        dComp = comp.getPreferredSize();
        comp.setSize(dComp);
        
        // Invalidate any cached information used to layout 
        // the Component
        comp.invalidate();
        
        
        // Set the location of the Component
        comp.setLocation(x, y);
        
        // Validate the component (i.e., layout the component)
        comp.validate();
    }




    /**
     * Calculates the minimum size dimensions for the specified 
     * panel given the components in the specified parent container
     * (Required by LayoutManager)
     *
     * @param parent  The Container
     * @return        The minimum size
     */
    public Dimension minimumLayoutSize(Container parent)
    {
        return preferredLayoutSize(parent);
    }




    /**
     * Calculates the preferred size dimensions for the specified 
     * panel given the components in the specified parent container
     * (Required by LayoutManager)
     *
     * @param parent  The Container
     * @return        The preferred size
     */
    public Dimension preferredLayoutSize(Container parent)
    {
        Component[]      components;
        Dimension        d, parentD;


        synchronized (parent.getTreeLock()) 
        {
            // Note: Should worry about insets
            d = new Dimension(0,0);
            
            components = parent.getComponents();
            if ((components != null) && (components.length != 0)) 
            {
                parentD  = parent.getSize();
                d.width  = parentD.width;
                d.height = components.length * lineHeightInPixels;
                if (d.height < parentD.height) d.height = parentD.height;
            }
            
            return d;
        }
    }




    /**
     * Removes the specified component from the layout
     * (Required by LayoutManager)
     *
     * @param comp    The Component to remove
     */
    public void removeLayoutComponent(Component comp)
    {
        constraints.remove(comp);
    }



}
        
A Presentation System
javaexamples/slides/Slide.java
        package slides;

import java.applet.*;
import java.awt.*;
import javax.swing.*;

/**
 * A slide (for use in a  slide presentation)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class  Slide extends    JPanel
{
    private SlideLayout          layout;



    /**
     * Default Constructor
     *
     * Note: This is used for a slide that does not
     * have a title
     */
    public Slide()
    {
        this(null);
    }


    /**
     * Explicit Value Constructor
     *
     * @param title   The title of the slide
     */
    public Slide(String title)
    {
        super();

        setBackground(SlidePresentation.BACKGROUND);
        setForeground(SlidePresentation.FOREGROUND);
        setName(title);
        layout = new SlideLayout();
        setLayout(layout);
    }



    /**
     * Add a "generic" bullet
     *
     * @param comp   The component to use
     * @param level  The indentation level
     */
    public void addBullet(Component comp, String level)
    {
        comp.setForeground(getForeground());
        comp.setBackground(getBackground());
        add(level, comp);
    }



    /**
     * Add a text bullet
     *
     * @param text   The text to use
     * @param level  The indentation level
     */
    public Component addBullet(String text, String level)
    {
        Label               b;

        b = new Label(text);
        b.setForeground(getForeground());
        b.setBackground(getBackground());
        add(level, b);

        return b;
    }





    /**
     * Set the visibility of this component
     *
     * @param ok   The visibility (true for visible)
     */
    public void setVisible(boolean ok)
    {
        Component[]   c;
        int i;


        c = getComponents();
        for (i=0; i < c.length; i++) c[i].setVisible(ok);
    }


}
        
A Presentation System (cont.)
javaexamples/slides/ButtonPanel.java
        package slides;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * The button panel for a slide presentation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class ButtonPanel extends JPanel
{
    private JButton         backwardB, forwardB;

    private static final Insets  margin = new Insets(0,0,0,0);


    /**
     * Default constructor
     */
    public ButtonPanel()
    {
        backwardB = createButton("backward");
        forwardB  = createButton("forward");

        performLayout();

        backwardB.setEnabled(true);
        forwardB.setEnabled(true);
    }
    
    
    /**
     * Add an ActionListener
     *
     * @param al   The listener
     */
    public void addActionListener(ActionListener al)
    {
        backwardB.addActionListener(al);
        forwardB.addActionListener(al);
    }




    /**
     * Create a button
     *
     * @param prefix   The button's identifier
     * @return         The button
     */
    private JButton createButton(String prefix)
    {
        JButton       b;
        
        b = new JButton();
        setupButton(b, prefix);
        return b;
    }




    /**
     * Set-up a button
     *
     * @param b        The button
     * @param prefix   The button's identifier
     */
    private void setupButton(AbstractButton b, String prefix)
    {
        ImageIcon     icon;
        
        icon = new ImageIcon(prefix+"out.gif");
        b.setIcon(icon);
        icon = new ImageIcon(prefix+"disabled.gif");
        b.setDisabledIcon(icon);
        b.setMargin(margin);
        b.setBorderPainted(false);
        b.setFocusPainted(false);
        b.setRolloverEnabled(false);
        b.setActionCommand(prefix);

        b.setBackground(null);
    }



    /**
     * Layout this component
     */
    private void performLayout()
    {
        setBackground(null);

        setLayout(new FlowLayout());
        add(backwardB);
        add(forwardB);
    }

}
        
A Presentation System (cont.)
javaexamples/slides/TitlePanel.java
        package slides;

import java.awt.*;
import javax.swing.*;

/**
 * A panel containing slide titles
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class TitlePanel extends JPanel
{
    private Color              highlight;
    private String             text;


    /**
     * Default constructor
     */
    public TitlePanel()
    {
        this(null);
    }


    /**
     * Explicit value constructor
     *
     * @param text   The text in the title
     */
    public TitlePanel(String text)
    {
        this.text = text;
        setBackground(null);
        setForeground(null);
        setHighlight(Color.white);
    }




    /**
     * Get the highlight color
     *
     * @return   The highlight color
     */
    public Color getHighlight()
    {
        return highlight;
    }





    /**
     * Get the minimum size of this component
     *
     * @return  The minimum size
     */
    public Dimension getMinimumSize()
    {
        return getPreferredSize();
    }




    /**
     * Get the preferred size of this component
     *
     * @return  The preferred size
     */
    public Dimension getPreferredSize()
    {
        FontMetrics        fm;
        int                h, w;

        fm = getFontMetrics(getFont());
        h = fm.getHeight()+fm.getMaxDescent()+20;
        w = fm.stringWidth(text) * 2;

        return new Dimension(w, h);
    }


    /**
     * Paint this component
     *
     * @param g   The Graphics context to use
     */
    public void paint(Graphics g)
    {
        Dimension           d;
        FontMetrics         fm;
        int                 bars, barHeight, barSpace;
        int                 barTop, barThickness, barWidth;
        int                 descent, h, i, middle, w, x;


        super.paint(g);
        barSpace = 6;
        barThickness = 3;
        bars = 5;

        if (text != null) 
        {
            g.setFont(getFont());
            fm = g.getFontMetrics();
            
            d      = getSize();
            h      = fm.getHeight();
            middle = d.height/2;

            descent   = fm.getMaxDescent();
            barWidth  = d.width*7/10;
            barHeight = descent+h;
            barTop    = middle-h/2-descent/2;
            
            
            g.setColor(getHighlight());
            g.fillRect(0, barTop, barWidth, barHeight);
            
            x = barWidth;
            for (i=0; i < bars; i++) 
            {
                x += barSpace;
                g.fillRect(x, barTop, barThickness, barHeight);
                x += barThickness;
            }

            x = 5;
            g.setColor(getForeground());
            g.drawString(text, x, middle+h/2-descent/2);
        }
    }


    /**
     * Set the highlight color
     *
     * @param highlight   The highlight color
     */
    public void setHighlight(Color highlight)
    {
        this.highlight = highlight;
    }


    /**
     * Set the title text
     */
    public void setText(String text)
    {
        this.text = text;
        repaint();
    }
}
        
A Presentation System (cont.)
javaexamples/slides/SlidePresentation.java
        package slides;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 * A container for a slide presentation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class SlidePresentation extends    JPanel
                               implements ActionListener
{
    protected ButtonPanel       buttonP;
    protected FlipChartLayout   layout;
    protected Panel             slidePanel;
    protected TitlePanel        titleP;


    public static final Color         BACKGROUND = Color.black;
    public static final Color         FOREGROUND = Color.white;
    public static final Color         TITLE      = Color.yellow;



    /**
     * Explicit value constructor
     *
     * @param slides   The array of slides
     */
    public SlidePresentation(Component[]  slides)
    {
        super();

        slidePanel = new Panel();
        buttonP = new ButtonPanel();
        layout = new FlipChartLayout();
        titleP = new TitlePanel("         ");

        performLayout(slides);

        buttonP.addActionListener(this);
        showTitle();
    }



    /**
     * Handle actionPerformed events (required by ActionListener)
     *
     * @param evt    The event
     */
    public void actionPerformed(ActionEvent evt)
    {
        Object      source;
        String      ac;



        ac     = evt.getActionCommand();

        if      (ac.equals("forward"))  forward();
        else if (ac.equals("backward")) backward();

        showTitle();
    }



    /**
     * Move one slide backward
     */
    protected void backward()
    {
        layout.backward(slidePanel);
        showTitle();
    }


    /**
     * Move one slide forward
     */
    protected void forward()
    {
        layout.forward(slidePanel);
        showTitle();
    }


    /**
     * Layout this component
     */
    protected void performLayout(Component[] slides)
    {
        ImageIcon   icon;
        int         i;
        JPanel      bottomP, centerP, tmpP;
        JLabel      keyboardP;

        setBackground(BACKGROUND);
        setForeground(FOREGROUND);

        titleP.setHighlight(TITLE);
        titleP.setForeground(BACKGROUND);
        titleP.setBackground(BACKGROUND);




        setLayout(new BorderLayout());

        slidePanel.setLayout(layout);

             for (i=0; i < slides.length; i++) 
             {
                 slidePanel.add(slides[i], "Slide"+i);
             }

        add(slidePanel, BorderLayout.CENTER);

        bottomP = new JPanel();
            bottomP.setLayout(new BorderLayout());

                tmpP = new JPanel();
                tmpP.setBackground(BACKGROUND);
                tmpP.setLayout(new GridLayout(2,1));
                tmpP.add(new JLabel(" "));
                tmpP.add(buttonP);

            bottomP.add(tmpP, BorderLayout.WEST);

            icon = new ImageIcon("keyboard.gif");
            keyboardP = new JLabel(icon);
            bottomP.add(keyboardP, BorderLayout.EAST);

            bottomP.add(new JLabel(" "), BorderLayout.CENTER);

            bottomP.setBackground(Color.black);

        add(bottomP, BorderLayout.SOUTH);

            titleP.setFont(new Font("Sans Serif", Font.BOLD, 24));

        add(titleP, BorderLayout.NORTH);
    }



    /**
     * Show the title associated with the first/front
     * slide
     */
    protected void showTitle()
    {
        Slide         first;

        first = (Slide)layout.getFirst(slidePanel);
        if (first != null) 
        {
            titleP.setText(first.getName());
        }
    }
}
        
A Presentation System (cont.)
javaexamples/slides/Driver.java
        import java.awt.*;
import javax.swing.*;

import gui.*;
import slides.Slide;
import slides.SlideLayout;
import slides.SlidePresentation;


/**
 * The driver for a slide presentation
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Driver
{
    /**
     * The entry point for the application
     *
     * @param args    The command-line arguments
     */
    public static void main(String[] args)
    {
        CloseableFrame     f;
        Container          contentPane;
        ImageIcon          icon;
        JLabel             image;
        JPanel             p;
        Slide              temp;
        Slide[]            slides;

        f = new CloseableFrame();
        contentPane = f.getContentPane();
        contentPane.setLayout(new BorderLayout());

        slides = new Slide[3];


        // Slide 0
        temp = new Slide("About the CS Department at JMU");
            icon = new ImageIcon("chip.gif");
            image = new JLabel(icon);
            image.setBackground(Color.black);
            temp.addBullet("Computing is at the core of many "+
                           "advances, including:", 
                           SlideLayout.LEVEL0);
            temp.addBullet("The Internet", SlideLayout.LEVEL1);
            temp.addBullet("Mobile Telecommunications", SlideLayout.LEVEL1);
            temp.addBullet("Interactive TV", SlideLayout.LEVEL1);
            temp.addBullet("Intelligent Devices", SlideLayout.LEVEL1);
            temp.addBullet(image, SlideLayout.CENTER);
            temp.addBullet("The CS Department at JMU teaches students to:", 
                            SlideLayout.LEVEL0);
            temp.addBullet("Improve existing uses of computers", 
                            SlideLayout.LEVEL1);
            temp.addBullet("Develop new uses of computers", 
                            SlideLayout.LEVEL1);
        slides[0] = temp;


        // Slide 1
        temp = new Slide("Special Programs");
            icon = new ImageIcon("acm.gif");
            image = new JLabel(icon);
            image.setBackground(Color.black);
            temp.addBullet("Honors Program", SlideLayout.LEVEL0);
            temp.addBullet("Internship Program", SlideLayout.LEVEL0);
            temp.addBullet("Interdisciplinary Minors", SlideLayout.LEVEL0);
            temp.addBullet("Health Information Systems", SlideLayout.LEVEL1);
            temp.addBullet("Telecommunications", SlideLayout.LEVEL1);
            temp.addBullet("Various Clubs and Organizations", 
                            SlideLayout.LEVEL0);
            temp.addBullet(image, SlideLayout.LEVEL1);
            temp.addBullet("Independent Studies and Directed Research", 
                            SlideLayout.LEVEL0);
        slides[1] = temp;




        // Slide 2
        temp = new Slide("Career Opportunities");
            temp.addBullet("Graduates typically become:",
                                SlideLayout.LEVEL0);
            temp.addBullet("Programmers", SlideLayout.LEVEL1);
            temp.addBullet("Software Engineers", SlideLayout.LEVEL1);
            temp.addBullet("Recent graduates have worked for:", 
                            SlideLayout.LEVEL0);
            temp.addBullet("AT&T", SlideLayout.LEVEL1);
            temp.addBullet("Capital One", SlideLayout.LEVEL1);
            temp.addBullet("GTE", SlideLayout.LEVEL1);
            temp.addBullet("IBM", SlideLayout.LEVEL1);
        slides[2] = temp;




        contentPane.add(new SlidePresentation(slides)); 
        //contentPane.add(new SlideShow(slides));
        f.setSize(600,800);
        f.show();
        f.setBackground(Color.black);
        f.setForeground(Color.white);

    }

}