JDK-7169915 : Swing Table Layout bad repaint of the row
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 6u26,7
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: linux
  • CPU: x86
  • Submitted: 2012-05-18
  • Updated: 2016-09-08
  • Resolved: 2012-06-25
Related Reports
Duplicate :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Server VM (build 22.0-b10, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Mageia 1

A DESCRIPTION OF THE PROBLEM :
A swing table row is  not correctly repaint if columns have different widths.
Related Bug : 7072926

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Launch the code.
1-Insert a texte ("test" for example) after "Cell11", the table layout is update.
Remove the text, (the table layout is not update : bug 7072926)
Inside border are badly repaint.
2-Move the cursor to the last cell with right arrow, the text desappear.
3-Try to put the cursor between "Cel" and "l23". The cursor go to the end of the cell.

Conclusion : This bug and bug 7072926 are different. When I solved 7072926 the cells are still bad repaint.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Launch the code.
1-Insert a texte ("test" for example) after "Cell11", the table layout is update.
Remove the text, (the table layout is update : bug 7072926)
Inside border are badly repaint.
2-Move the cursor to the last cell with right arrow, the text desappear.
3-Try to put the cursor between "Cel" and "l23". The cursor go to the end of the cell.
ACTUAL -
The cell text desappear.
Inside border are badly repaint.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package CodeBug;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.text.*;
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
public class CodeBug extends JFrame {

    JEditorPane edit = new JEditorPane();

    public CodeBug() {
        super("Code example for a TableView bug");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        edit.setEditorKit(new CodeBugEditorKit());
        initCodeBug();

        this.getContentPane().add(new JScrollPane(edit));
        this.setSize(300, 200);
        this.setLocationRelativeTo(null);

    }

    private void initCodeBug() {
        CodeBugDocument doc = (CodeBugDocument) edit.getDocument();
        try {
            doc.insertString(0, "TextB  TextE", null);
        } catch (BadLocationException ex) {
        }
        doc.insertTable(6, 4, 3);
        try {
            doc.insertString(7, "Cell11", null);
            doc.insertString(14, "Cell12", null);
            doc.insertString(21, "Cell13", null);
            doc.insertString(28, "Cell21", null);
            doc.insertString(35, "Cell22", null);
            doc.insertString(42, "Cell23", null);
            doc.insertString(49, "Cell31", null);
            doc.insertString(56, "Cell32", null);
            doc.insertString(63, "Cell33", null);
            doc.insertString(70, "Cell41", null);
            doc.insertString(77, "Cell42", null);
            doc.insertString(84, "Cell43", null);
        } catch (BadLocationException ex) {
        }
    }

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

//-------------------------------------------------------------------------------
class CodeBugEditorKit extends StyledEditorKit {

    ViewFactory defaultFactory = new TableFactory();

    @Override
    public ViewFactory getViewFactory() {
        return defaultFactory;
    }

    @Override
    public Document createDefaultDocument() {
        return new CodeBugDocument();
    }
}
//-------------------------------------------------------------------------------

class TableFactory implements ViewFactory {

    @Override
    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 ParagraphView(elem);
            } else if (kind.equals(AbstractDocument.SectionElementName)) {
                return new BoxView(elem, View.Y_AXIS);
            } else if (kind.equals(StyleConstants.ComponentElementName)) {
                return new ComponentView(elem);
            } else if (kind.equals(CodeBugDocument.ELEMENT_TABLE)) {
                return new tableView(elem);
            } else if (kind.equals(StyleConstants.IconElementName)) {
                return new IconView(elem);
            }
        }
        // default to text display
        return new LabelView(elem);

    }
}
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
class tableView extends TableView implements ViewFactory {

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

    @Override
    public ViewFactory getViewFactory() {
        return this;
    }

    @Override
    public float getMinimumSpan(int axis) {
        return getPreferredSpan(axis);
    }

    @Override
    public float getMaximumSpan(int axis) {
        return getPreferredSpan(axis);
    }

    @Override
    public float getAlignment(int axis) {
        return 0.5f;
    }

    @Override
    public void paint(Graphics g, Shape allocation) {
        super.paint(g, allocation);
        Rectangle alloc = allocation.getBounds();
        int lastY = alloc.y + alloc.height - 1;
        g.drawLine(alloc.x, lastY, alloc.x + alloc.width, lastY);
    }

    @Override
    protected void paintChild(Graphics g, Rectangle alloc, int index) {
        super.paintChild(g, alloc, index);
        int lastX = alloc.x + alloc.width;
        g.drawLine(alloc.x, alloc.y, lastX, alloc.y);
    }

    @Override
    public View create(Element elem) {
        String kind = elem.getName();
        if (kind != null) {
            if (kind.equals(CodeBugDocument.ELEMENT_TR)) {
                return new trView(elem);
            } else if (kind.equals(CodeBugDocument.ELEMENT_TD)) {
                return new BoxView(elem, View.Y_AXIS);
            }
        }

        // default is to delegate to the normal factory
        View p = getParent();
        if (p != null) {
            ViewFactory f = p.getViewFactory();
            if (f != null) {
                return f.create(elem);
            }
        }

        return null;
    }

    public class trView extends TableRow {

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

        public float getMinimumSpan(int axis) {
            return getPreferredSpan(axis);
        }

        public float getMaximumSpan(int axis) {
            return getPreferredSpan(axis);
        }

        public float getAlignment(int axis) {
            return 0f;
        }

        @Override
        protected void paintChild(Graphics g, Rectangle alloc, int index) {
            super.paintChild(g, alloc, index);
            int lastY = alloc.y + alloc.height - 1;
            g.drawLine(alloc.x, alloc.y, alloc.x, lastY);
            int lastX = alloc.x + alloc.width;
            g.drawLine(lastX, alloc.y, lastX, lastY);
        }
    };
}

//-------------------------------------------------------------------------------
class CodeBugDocument extends DefaultStyledDocument {

    public static final String ELEMENT_TABLE = "table";
    public static final String ELEMENT_TR = "table cells row";
    public static final String ELEMENT_TD = "table data cell";

    public CodeBugDocument() {
    }

    protected void insertTable(int offset, int rowCount, int colCount) {
        try {
            ArrayList Specs = new ArrayList();
            ElementSpec gapTag = new ElementSpec(new SimpleAttributeSet(), ElementSpec.ContentType, "\n".toCharArray(), 0, 1);
            Specs.add(gapTag);

            SimpleAttributeSet tableAttrs = new SimpleAttributeSet();
            tableAttrs.addAttribute(ElementNameAttribute, ELEMENT_TABLE);
            ElementSpec tableStart = new ElementSpec(tableAttrs, ElementSpec.StartTagType);
            Specs.add(tableStart); //start table tag


            fillRowSpecs(Specs, rowCount, colCount);

            ElementSpec[] spec = new ElementSpec[Specs.size()];
            Specs.toArray(spec);

            this.insert(offset, spec);
        } catch (BadLocationException ex) {
        }
    }

    protected void fillRowSpecs(ArrayList Specs, int rowCount, int colCount) {
        SimpleAttributeSet rowAttrs = new SimpleAttributeSet();
        rowAttrs.addAttribute(ElementNameAttribute, ELEMENT_TR);
        for (int i = 0; i < rowCount; i++) {
            ElementSpec rowStart = new ElementSpec(rowAttrs, ElementSpec.StartTagType);
            Specs.add(rowStart);

            fillCellSpecs(Specs, colCount);

            ElementSpec rowEnd = new ElementSpec(rowAttrs, ElementSpec.EndTagType);
            Specs.add(rowEnd);
        }

    }

    protected void fillCellSpecs(ArrayList Specs, int colCount) {
        for (int i = 0; i < colCount; i++) {
            SimpleAttributeSet cellAttrs = new SimpleAttributeSet();
            cellAttrs.addAttribute(ElementNameAttribute, ELEMENT_TD);

            ElementSpec cellStart = new ElementSpec(cellAttrs, ElementSpec.StartTagType);
            Specs.add(cellStart);

            ElementSpec parStart = new ElementSpec(new SimpleAttributeSet(), ElementSpec.StartTagType);
            Specs.add(parStart);
            ElementSpec parContent = new ElementSpec(new SimpleAttributeSet(), ElementSpec.ContentType, "\n".toCharArray(), 0, 1);
            Specs.add(parContent);
            ElementSpec parEnd = new ElementSpec(new SimpleAttributeSet(), ElementSpec.EndTagType);
            Specs.add(parEnd);
            ElementSpec cellEnd = new ElementSpec(cellAttrs, ElementSpec.EndTagType);
            Specs.add(cellEnd);
        }

    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The swing text package is powerful but It's Difficult  to use it without resolving this kind of bug.
Il could read on the WWW some messages of desappointed coder about table in JTextPane or JEditorPane.
I'm not sure these are bugs. I suppose that somebody remove some code in tableview. I haven't got previous releases to compare.

I can't comment bug 7072926 since many weeks so I send the solution for the two bug in this message.
  Bug 7072926 : columnRequirements are not clear in TableView.calculateColumnRequirements(int axis).
In another way you can add invalidateGrid() in TableView.preferenceChanged(View child, boolean width, boolean height)

This Bug : The major axis requirements of the row is baldy compute. It had to return the same width : the total columns widths. Overwrite the BoxView.calculateMajorAxisRequirements(int axis, SizeRequirements r)

Rewrite TableView.
The "// Rajout" tag in tableview show the differents with TableView. The rest of the code is unchanged.

-------------------------------------------------------------------------------------------------------------------------------------
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package CodeBug;

import java.awt.*;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Vector;

import javax.swing.SizeRequirements;
import javax.swing.event.DocumentEvent;

import javax.swing.text.AttributeSet;
import javax.swing.text.BoxView;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;

/**
 * <p> Implements View interface for a table, that is composed of an element
 * structure where the child elements of the element this view is responsible
 * for represent rows and the child elements of the row elements are cells. The
 * cell elements can have an arbitrary element structure under them, which will
 * be built with the ViewFactory returned by the getViewFactory method.
 * <pre>
 *
 * &nbsp;  TABLE
 * &nbsp;    ROW
 * &nbsp;      CELL
 * &nbsp;      CELL
 * &nbsp;    ROW
 * &nbsp;      CELL
 * &nbsp;      CELL
 *
 * </pre> <p> This is implemented as a hierarchy of boxes, the table itself is a
 * vertical box, the rows are horizontal boxes, and the cells are vertical
 * boxes. The cells are allowed to span multiple columns and rows. By default,
 * the table can be thought of as being formed over a grid (i.e. somewhat like
 * one would find in gridbag layout), where table cells can request to span more
 * than one grid cell. The default horizontal span of table cells will be based
 * upon this grid, but can be changed by reimplementing the requested span of
 * the cell (i.e. table cells can have independant spans if desired).
 *
 * @author Timothy Prinzing
 * @see View
 */
public abstract class tableview extends BoxView {

    /**
     * Constructs a TableView for the given element.
     *
     * @param elem the element that this view is responsible for
     */
    public tableview(Element elem) {
        super(elem, View.Y_AXIS);
        rows = new Vector<TableRow>();
        gridValid = false;
        // Rajout
        totalColumnRequirements = new SizeRequirements();
    }

    /**
     * Creates a new table row.
     *
     * @param elem an element
     * @return the row
     */
    protected TableRow createTableRow(Element elem) {
        return new TableRow(elem);
    }

    /**
     * @deprecated Table cells can now be any arbitrary View implementation and
     * should be produced by the ViewFactory rather than the table.
     *
     * @param elem an element
     * @return the cell
     */
    @Deprecated
    protected TableCell createTableCell(Element elem) {
        return new TableCell(elem);
    }

    /**
     * The number of columns in the table.
     */
    int getColumnCount() {
        return columnSpans.length;
    }

    /**
     * Fetches the span (width) of the given column. This is used by the nested
     * cells to query the sizes of grid locations outside of themselves.
     */
    int getColumnSpan(int col) {
        return columnSpans[col];
    }

    /**
     * The number of rows in the table.
     */
    int getRowCount() {
        return rows.size();
    }

    /**
     * Fetches the span (height) of the given row.
     */
    int getRowSpan(int row) {
        View rv = getRow(row);
        if (rv != null) {
            return (int) rv.getPreferredSpan(Y_AXIS);
        }
        return 0;
    }

    TableRow getRow(int row) {
        if (row < rows.size()) {
            return rows.elementAt(row);
        }
        return null;
    }

    /**
     * Determines the number of columns occupied by the table cell represented
     * by given element.
     */
    /*
     * protected
     */ int getColumnsOccupied(View v) {
        // PENDING(prinz) this code should be in the html
        // paragraph, but we can't add api to enable it.
        AttributeSet a = v.getElement().getAttributes();
        String s = (String) a.getAttribute(HTML.Attribute.COLSPAN);
        if (s != null) {
            try {
                return Integer.parseInt(s);
            } catch (NumberFormatException nfe) {
                // fall through to one column
            }
        }

        return 1;
    }

    /**
     * Determines the number of rows occupied by the table cell represented by
     * given element.
     */
    /*
     * protected
     */ int getRowsOccupied(View v) {
        // PENDING(prinz) this code should be in the html
        // paragraph, but we can't add api to enable it.
        AttributeSet a = v.getElement().getAttributes();
        String s = (String) a.getAttribute(HTML.Attribute.ROWSPAN);
        if (s != null) {
            try {
                return Integer.parseInt(s);
            } catch (NumberFormatException nfe) {
                // fall through to one row
            }
        }

        return 1;
    }


( This report has more than 16,000 characters and has been truncated. )

Comments
EVALUATION The problem is reproducible, but the test case is very tricky. It's not clear if the problem in Swing code or in the provided test. I wrote a simple example (see attach) and it works as expected. Could you please minimize the test in such way that it shows the problem in Swing? *** (#2 of 2): 2012-05-23 12:18:00 MSK ###@###.### the bug is closed as Incomplete over one month, please re-open if you have more details
25-06-2012