package articles.multicolumn;

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

public class MultiColumnView extends BoxView {
    //default width for columns
    int columnWidth=100;
    //default height (could be changed to be JEditorPane's height
    int columnHeight=100;

    //children (paragraphs) offsets and spans and horizontal/vertical sizes
    int majorTargetSpan=0;
    int[] majorOffsets=null;
    int[] majorSpans=null;

    int minorTargetSpan=0;
    int[] minorOffsets=null;
    int[] minorSpans=null;

    //starting positions of paragraph views
    Point[] starts;

    //cont of columns
    int columnCount=1;

    public MultiColumnView(Element elem, int axis) {
        super(elem, axis);
    }

    protected void layout(int width, int height) {
        columnHeight=height;
        super.layout(columnWidth,height);
    }

    protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
        super.layoutMajorAxis(targetSpan,axis,offsets,spans);
        majorTargetSpan=targetSpan;
        majorOffsets=offsets;
        majorSpans=spans;
        performMultiColumnLayout();
        for (int i=0; i<offsets.length; i++) {
            spans[i]=columnHeight;
            offsets[i]=0;
        }
    }

    protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
        super.layoutMinorAxis(targetSpan,axis,offsets,spans);
        minorTargetSpan=targetSpan;
        minorOffsets=offsets;
        minorSpans=spans;
        performMultiColumnLayout();
        for (int i=0; i<offsets.length; i++) {
            View v=getView(i);
            if (v instanceof MultiColumnParagraphView) {
                MultiColumnParagraphView par=(MultiColumnParagraphView)v;
                spans[i] = par.columnCount*columnWidth;
                offsets[i]=par.columnNumber*columnWidth;
            }
        }
    }

    protected void performMultiColumnLayout() {
        if (majorOffsets==null || minorOffsets==null || minorOffsets.length!=majorOffsets.length) {
            return;
        }
        int childCount=majorOffsets.length;
        int verticalStartOffset=0;
        int columnNumber=0;
        starts=new Point[childCount];
        for (int i=0; i<childCount; i++) {
            View v=getView(i);
            starts[i]=new Point();
            if (v instanceof MultiColumnParagraphView) {
                MultiColumnParagraphView par=(MultiColumnParagraphView)v;
                par.verticalStartOffset=verticalStartOffset;
                par.columnWidth=columnWidth;
                par.columnHeight=columnHeight;
                par.columnNumber=columnNumber;
                par.performMultiColumnLayout();
                starts[i].y=verticalStartOffset;
                starts[i].x=columnNumber*columnWidth;
                verticalStartOffset=columnHeight-par.restHeight;
                columnNumber+=par.columnCount-1;
            }
        }
        columnCount = columnNumber + 1;
    }
    public float getPreferredSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }
    public float getMinimumSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }
    public float getMaximumSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }

    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
        //define child container
        if (starts!=null) {
            for (int i=starts.length-1; i>0; i--) {
                if ((starts[i].x<x && starts[i].y<y)
                    || (starts[i].x+columnWidth<x)
                    ){
                    return getView(i).viewToModel(x,y,a,bias);
                }
            }
        }
        return getView(0).viewToModel(x,y,a,bias);
    }
    public void paint(Graphics g, Shape a) {
        super.paint(g,a);
        Rectangle r=a.getBounds();
        int shift=columnWidth;
        g.setColor(new Color(240,240,240));
        while (shift<r.width) {
            g.drawLine(r.x+shift,r.y,r.x+shift,r.y+r.height);
            shift+=columnWidth;
        }
    }
}
package articles.multicolumn;

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

public class MultiColumnParagraphView extends ParagraphView {

    protected int verticalStartOffset=0;
    protected int columnCount=1;
    protected int columnNumber=0;
    protected int restHeight=0;

    int[] majorOffsets=null;
    int[] majorSpans=null;

    int[] minorOffsets=null;
    int[] minorSpans=null;

    protected int columnWidth=100;
    protected int columnHeight=100;
    Point[] starts;

    public MultiColumnParagraphView(Element elem) {
        super(elem);
    }

    protected void layout(int width, int height) {
        super.layout(columnWidth,0);
    }

    protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
        super.layoutMajorAxis(targetSpan,axis,offsets,spans);
        majorOffsets=offsets;
        majorSpans=spans;
        performMultiColumnLayout();
        offsets=majorOffsets;
    }

    protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
        super.layoutMinorAxis(targetSpan,axis,offsets,spans);
        minorOffsets=offsets;
        minorSpans=spans;
        performMultiColumnLayout();
        offsets=minorOffsets;
    }

    protected void performMultiColumnLayout() {
        if (majorOffsets==null || minorOffsets==null || minorOffsets.length!=majorOffsets.length) {
            return;
        }
        int childCount=majorOffsets.length;
        int vo=verticalStartOffset;
        int cc=1;
        starts=new Point[childCount];
        for (int i=0; i<childCount; i++) {
            starts[i]=new Point();
            if (vo+majorSpans[i]>columnHeight) {
                cc++;
                vo=0;
            }
            starts[i].y=vo;
            starts[i].x=(columnNumber+cc-1)*columnWidth;
            majorOffsets[i]=vo;
            vo+=majorSpans[i];
            restHeight=columnHeight-vo;
            minorOffsets[i]=(cc-1)*columnWidth;
        }
        if (columnCount!=cc) {
            columnCount = cc;
            preferenceChanged(getView(0),true,true);
        }
    }

    public float getPreferredSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }
    public float getMinimumSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }
    public float getMaximumSpan(int axis) {
        if (axis==View.Y_AXIS) {
            return columnHeight;
        }
        else {
            return columnWidth*columnCount;
        }
    }
    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
        int ind=getViewIndexAtPoint((int)x,(int)y,a.getBounds());
        View v=getViewAtPoint((int)x,(int)y,a.getBounds());
        Shape childAlloc=getChildAllocation(ind,a);
        return v.viewToModel(x,y,childAlloc,bias);
    }

    protected int getViewIndexAtPoint(int x, int y, Rectangle alloc) {
        if (starts!=null) {
            for (int i=starts.length-1; i>0; i--) {
                if ((starts[i].x<x && starts[i].y<y)
                    || (starts[i].x+columnWidth<x)){
                    return i;
                }
            }
        }
        return 0;
    }
    protected View getViewAtPoint(int x, int y, Rectangle alloc) {
        if (starts!=null) {
            for (int i=starts.length-1; i>0; i--) {
                if ((starts[i].x<x && starts[i].y<y)
                    || (starts[i].x+columnWidth<x)){
                    return getView(i);
                }
            }
        }
        return getView(0);
    }
    public Shape getChildAllocation(int index, Shape a) {
        Rectangle r=super.getChildAllocation(index,a).getBounds();
        r.x=starts[index].x+3;
        r.y=starts[index].y+3;
        return r;
    }
}
package articles.multicolumn;

import javax.swing.*;
import javax.swing.text.*;


public class Application extends JFrame {
    JEditorPane edit=new JEditorPane();
    public Application() {
        super("Multicolumn text example");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        edit.setEditorKit(new MultiColumnEditorKit());

        this.getContentPane().add(new JScrollPane(edit));
        this.setSize(200,200);
    }

    public static void main(String[] args) {
        Application m = new Application();
        m.setVisible(true);
    }

}

class MultiColumnEditorKit extends StyledEditorKit {
    ViewFactory defaultFactory=new MultiColumnFactory();
    public ViewFactory getViewFactory() {
        return defaultFactory;
    }
}

class MultiColumnFactory implements ViewFactory {
    public View create(Element elem) {
        String kind = elem.getName();
        if (kind != null) {
            if (kind.equals(AbstractDocument.ContentElementName)) {
                return new LabelView(elem);
            } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                return new MultiColumnParagraphView(elem);
            } else if (kind.equals(AbstractDocument.SectionElementName)) {
                return new MultiColumnView(elem, View.Y_AXIS);
            } else if (kind.equals(StyleConstants.ComponentElementName)) {
                return new ComponentView(elem);
            } else if (kind.equals(StyleConstants.IconElementName)) {
                return new IconView(elem);
            }
        }

        // default to text display
        return new LabelView(elem);
    }
}