Mike Carpenter
Mike Carpenter

Reputation: 307

Force space between Panels to be 0 using BoxLayout?

Currently as part of a project I'm working on, I am implementing a component which can be used to visualize a permutation of bits (as part of a cryptographic algorithm). I am doing so by creating two rows of "pins" and connecting them by drawing lines between the tips, creating a sort of web between them.

An important part of this is that I am using this visualization both on its own as well as a part of other visualizations (for example, I may want to include S-Boxes) and therefore I need to be able to turn the pins on and off. My solution to this was using JPanels to put the rows of pins into a header and footer panel which can be made invisible.

I am laying them out vertically in a BoxLayout, but I end up with space between them, even if I add glue above the header and below the footer.

My example looks like this when initialized:

Web without resizing.

And when I resize it, they come together a bit, but still only touch on one side:

Web after resizing.

I'm guessing this is some sort of silly mistake converting my user space into device space in terms of component size and layout, but for the life of me I cannot find it. This is my code, although I apologize for the mess:

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Ellipse2D;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class PermutationWeb extends JPanel
{
    private static enum EndPanelType
    {
        HEADER, FOOTER
    }

    private final JPanel header;
    private final JPanel mainPanel;
    private final JPanel footer;

    private double  widthFactor;
    private double  heightFactor;
    private int     widthMax;
    private int     heightMax;
    private int[]   indexMappings;
    private Point2D.Double[] endpoints;
    private Line2D.Double[]  drawingLines;

    public PermutationWeb(int indices, boolean endPanelsOn)
    {
        super();

        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        widthMax  = (indices + 1)*2;
        heightMax = (int)Math.round(widthMax*(3.0/17.0));
        widthFactor  = 1;
        heightFactor = 1;

        endpoints    = new Point2D.Double[indices * 2];
        drawingLines = new Line2D.Double[indices];

        for(int i=0; i<indices; i++)
        {
            endpoints[i]         = new Point2D.Double(i*2+2, 0);
            endpoints[i+indices] = new Point2D.Double(i*2+2, heightMax);
            drawingLines[i] = new Line2D.Double();
        }

        header    = new WebEndPanel(EndPanelType.HEADER);
        mainPanel = new WebMainPanel();
        footer    = new WebEndPanel(EndPanelType.FOOTER);

        add(Box.createVerticalGlue());
        add(header);
        add(mainPanel);
        add(footer);
        add(Box.createVerticalGlue());

        setEndPanelsOn(endPanelsOn);
    }

    public Point2D getEndpoint(int index)
    {
        return endpoints[index];
    }

    public void updateMappings(int[] mappings)
    {
        this.indexMappings = mappings;

        for(int i=0; i<indexMappings.length; i++)
        {
            drawingLines[i].setLine(endpoints[i], endpoints[indexMappings.length + indexMappings[i]]);
        }

        //paint();
    }

    public void setEndPanelsOn(boolean endPanelsOn)
    {
        header.setVisible(endPanelsOn);
        footer.setVisible(endPanelsOn);
    }

    @Override
    public Dimension getMaximumSize()
    {
        int height = mainPanel.getHeight();
        if(header.isVisible())
        {
            height += (header.getHeight() * 2);
        }

        int width = mainPanel.getWidth();

        return new Dimension(width, height);
    }

    @Override
    public Dimension getPreferredSize()
    {
        return getMaximumSize();
    }

    public static void main(String[] args)
    {
        JFrame jf = new JFrame();
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setSize(800, 600);

        int[] mappings = {0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15};

        PermutationWeb webTest = new PermutationWeb(16, true);
        jf.add(webTest);
        jf.setVisible(true);
        webTest.setVisible(true);

        webTest.updateMappings(mappings);

        System.out.printf("Header:    [%s]\nMainPanel: [%s]\nFooter:    [%s]\n",
                webTest.header.getSize().toString(),
                webTest.mainPanel.getSize().toString(),
                webTest.footer.getSize().toString());
    }


    private class WebMainPanel extends WebSubPanel
    {
        private static final double HEIGHT_RATIO = 0.25;

        @Override
        public void paintComponent(Graphics g)
        {
            Graphics2D g2 = (Graphics2D)g;
            super.paintComponent(g2);

            scaleTo(getSize());
            g2.scale(widthFactor, widthFactor);
            g2.setStroke(new BasicStroke((float)(2.0/widthFactor)));

            for(Line2D line: drawingLines)
            {
                g2.draw(line);
            }
        }

        @Override
        public Dimension getMaximumSize()
        {
            return new Dimension(MAX_WIDTH_PX, (int)(MAX_WIDTH_PX*HEIGHT_RATIO));
        }
    }

    private class WebEndPanel extends WebSubPanel
    {
        private static final double HEIGHT_RATIO = 0.125;
        private static final double PIN_RADIUS = 0.5;

        private final EndPanelType endType;
        private Line2D.Double[]    edgeLines;
        private Ellipse2D.Double[] pinHeads;

        public WebEndPanel(EndPanelType endType)
        {
            super();
            this.endType = endType;
            this.edgeLines = new Line2D.Double[endpoints.length/2];
            this.pinHeads  = new Ellipse2D.Double[endpoints.length/2];

            for(int i=0; i<edgeLines.length; i++)
            {
                Point2D pointA;
                Point2D pointB;


                if(EndPanelType.HEADER.equals(this.endType))
                {
                    pointA = new Point2D.Double(i*2+2, 4);
                    pointB = new Point2D.Double(i*2+2, 2);

                    pinHeads[i]  = new Ellipse2D.Double(
                            pointB.getX()-PIN_RADIUS,
                            pointB.getY()-PIN_RADIUS*2,
                            PIN_RADIUS*2,
                            PIN_RADIUS*2);
                }
                else // FOOTER
                {
                    pointA = new Point2D.Double(i*2+2, 0);
                    pointB = new Point2D.Double(i*2+2, 2);

                    pinHeads[i]  = new Ellipse2D.Double(
                            pointB.getX()-PIN_RADIUS,
                            3-PIN_RADIUS*2,
                            PIN_RADIUS*2,
                            PIN_RADIUS*2);
                }

                edgeLines[i] = new Line2D.Double(pointA, pointB);
            }
        }

        @Override
        public Dimension getMaximumSize()
        {
            return new Dimension(MAX_WIDTH_PX, (int)(MAX_WIDTH_PX*HEIGHT_RATIO));
        }

        @Override
        public void paintComponent(Graphics g)
        {
            Graphics2D g2 = (Graphics2D)g;
            super.paintComponent(g2);

            scaleTo(getSize());
            g2.scale(widthFactor, widthFactor);
            g2.setStroke(new BasicStroke((float)(2.0/widthFactor)));

            for(Line2D line: edgeLines)
            {
                g2.draw(line);
            }

            for(Ellipse2D pin: pinHeads)
            {
                g2.draw(pin);
            }
        }
    }

    private abstract class WebSubPanel extends JPanel
    {
        protected static final int MAX_WIDTH_PX = 800;

        public WebSubPanel()
        {
            super();
            setBorder(null);
            setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
        }

        public void scaleTo(Dimension d)
        {
            widthFactor  = d.getWidth()  / (double)widthMax;
            heightFactor = d.getHeight() / (double)heightMax;
        }

        @Override
        public Dimension getPreferredSize()
        {
            return getMaximumSize();
        }        
    }
}

The ultimate goal here is a resizable web where the header and footer WebEndPanels can be invisible, but have 0 space between them and the WebMainPanel when shown (as though they were a single entity).

Upvotes: 2

Views: 740

Answers (1)

camickr
camickr

Reputation: 324128

A BoxLayout will resize a component up to its maximum size if space is available.

So you first need to implement the getPreferredSize() method of the component so it can be packed as displayed at its normal size.

Then if it has the ability to grow (and your custom painting supports this) you override the getMaximumSize() method to return the size.

So the painting needs to be based on the actual size of the panel if you want it to be contiguous with other panels.

Upvotes: 2

Related Questions