1/*
2 * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @key headful
27 * @bug 8133864 8158209
28 * @summary  Wrong display, when the document I18n properties is true.
29 */
30import javax.swing.*;
31import javax.swing.text.*;
32import java.awt.*;
33import java.awt.event.KeyEvent;
34import java.util.ArrayList;
35import javax.swing.event.CaretEvent;
36import javax.swing.event.CaretListener;
37
38public class TableViewLayoutTest extends JFrame {
39
40    private static double yCaret;
41    private static double xCaret;
42
43    // Number of iteration to verify the stability of the test with different robot delays :
44    // Work well with robot.delay(50) in hitKey method.
45    // But if the robot delay is too low, the test is not stable.
46    // Put this to 100, and reduce robot delay sometimes answers may be different.
47    private static int tn = 2;
48
49    // The four caret positions to test.
50    private static double yCarFLTab;
51    private static double yCarLLTab;
52    private static double xCarBTab;
53    private static double xCarETab;
54
55    // The caret coordonate differences along axis after the insertion and the removing cycle.
56    // 0 if the table layout is right.
57    private static double dyCarFLTab;
58    private static double dyCarLLTab;
59    private static double dxCarBTab;
60    private static double dxCarETab;
61
62    private static JEditorPane edit = new JEditorPane();
63    private static TableViewLayoutTest frame;
64
65    private static String Prop = "\n";
66    private static boolean isTabWrong = Boolean.FALSE;
67
68    private static Boolean isI18n = false;
69
70    public TableViewLayoutTest() {
71
72        super("Code example for a TableView bug");
73        setUndecorated(true);
74        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
75        edit.setEditorKit(new CodeBugEditorKit());
76        initCodeBug();
77        this.getContentPane().add(new JScrollPane(edit));
78        this.pack();
79        this.setLocationRelativeTo(null);
80
81        edit.addCaretListener(new CaretListener() {
82            public void caretUpdate(CaretEvent e) {
83                JTextComponent textComp = (JTextComponent) e.getSource();
84                try {
85                    Rectangle rect = textComp.getUI().modelToView(textComp, e.getDot());
86                    yCaret = rect.getY();
87                    xCaret = rect.getX();
88                } catch (BadLocationException ex) {
89                    throw new RuntimeException("Failed to get pixel position of caret", ex);
90                }
91            }
92        });
93    }
94
95    private void initCodeBug() {
96        CodeBugDocument doc = (CodeBugDocument) edit.getDocument();
97        try {
98            doc.insertString(0, "TextB  TextE", null);
99        } catch (BadLocationException ex) {
100        }
101        doc.insertTable(6, 4, 3);
102        try {
103            doc.insertString(7, "Cell11", null);
104            doc.insertString(14, "Cell12", null);
105            doc.insertString(21, "Cell13", null);
106            doc.insertString(28, "Cell21", null);
107            doc.insertString(35, "Cell22", null);
108            doc.insertString(42, "Cell23", null);
109            doc.insertString(49, "Cell31", null);
110            doc.insertString(56, "Cell32", null);
111            doc.insertString(63, "Cell33", null);
112            doc.insertString(70, "Cell41", null);
113            doc.insertString(77, "Cell42", null);
114            doc.insertString(84, "Cell43", null);
115        } catch (BadLocationException ex) {
116        }
117    }
118
119    public static void main(String[] args) throws Exception {
120
121        for (int i = 0; i < tn; i++) {
122            Robot rob = new Robot();
123
124            SwingUtilities.invokeAndWait(new Runnable() {
125                @Override
126                public void run() {
127                    frame = new TableViewLayoutTest();
128                    frame.setVisible(true);
129                }
130            });
131
132            SwingUtilities.invokeAndWait(new Runnable() {
133                @Override
134                public void run() {
135                    //Enable or disable i18n.
136                    isI18n = !isI18n;
137                    edit.getDocument().putProperty("i18n", isI18n);
138
139                    //Made a change to update table layout.
140                    //Without any change the table i18n property change is not take in account.
141                    edit.select(11, 12);
142                    edit.replaceSelection("1");
143
144                    //Catch the four caret positions to test before insertions.
145                    edit.setCaretPosition(6);
146                    xCarBTab = xCaret;
147                    edit.setCaretPosition(91);
148                    xCarETab = xCaret;
149
150                    edit.setCaretPosition(74);
151                    yCarLLTab = yCaret;
152                    edit.setCaretPosition(11);
153                    yCarFLTab = yCaret;
154                }
155            });
156
157            hitKey(rob, KeyEvent.VK_T);
158            hitKey(rob, KeyEvent.VK_E);
159            hitKey(rob, KeyEvent.VK_S);
160            hitKey(rob, KeyEvent.VK_T);
161            hitKey(rob, KeyEvent.VK_BACK_SPACE);
162            hitKey(rob, KeyEvent.VK_BACK_SPACE);
163            hitKey(rob, KeyEvent.VK_BACK_SPACE);
164            hitKey(rob, KeyEvent.VK_BACK_SPACE);
165
166            rob.waitForIdle();
167
168            SwingUtilities.invokeAndWait(new Runnable() {
169                @Override
170                public void run() {
171                    //Calculate caret coordinate differences and catch caret positions after insertions.
172                    edit.setCaretPosition(6);
173                    dxCarBTab = Math.abs(xCarBTab - xCaret);
174                    edit.setCaretPosition(91);
175                    dxCarETab = Math.abs(xCarETab - xCaret);
176
177                    edit.setCaretPosition(74);
178                    dyCarLLTab = Math.abs(yCarLLTab - yCaret);
179                    edit.setCaretPosition(11);
180                    dyCarFLTab = Math.abs(yCarFLTab - yCaret);
181
182                    edit.setCaretPosition(74);
183                    yCarLLTab = yCaret;
184                    edit.setCaretPosition(11);
185                    yCarFLTab = yCaret;
186                }
187            });
188
189            Object dp = edit.getDocument().getProperty("i18n");
190            Boolean isI18n = dp instanceof Boolean ? (Boolean) dp : Boolean.FALSE;
191            String i18n = isI18n ? "\nWhen i18n enable, " : "\nWhen i18n disable, ";
192
193            if (Math.abs(yCarFLTab - yCarLLTab) < 10) {
194                isTabWrong = Boolean.TRUE;
195                Prop = Prop + i18n + "test can't be completed : TableView layout wrong, lines overlap, see JDK-8133864.";
196            } else {
197                if (dyCarFLTab != 0 || dyCarLLTab != 0) {
198                    isTabWrong = Boolean.TRUE;
199                    Prop = Prop + i18n + "TableView layout wrong : Table high change when inserts and removes caracters, bug never reported yet. First Line dy=" + dyCarFLTab + " Last Line dy=" + dyCarLLTab;
200                }
201                if (dxCarBTab != 0 || dxCarETab != 0) {
202                    isTabWrong = Boolean.TRUE;
203                    Prop = Prop + i18n + "TableView layout wrong : Table width change when inserts and removes caracters, see JDK-8158209 and JDK-7169915. Before Table dx=" + dxCarBTab + " After Table dx=" + dxCarETab;
204                }
205            }
206            rob.waitForIdle();
207
208            SwingUtilities.invokeAndWait(new Runnable() {
209                @Override
210                public void run() {
211                    frame.dispose();
212                }
213            });
214        }
215        if (isTabWrong) {
216            throw new RuntimeException(Prop);
217        }
218
219        System.out.println("ok");
220    }
221
222    private static void hitKey(Robot robot, int k) throws Exception {
223        robot.delay(50);
224        robot.keyPress(k);
225        robot.keyRelease(k);
226        robot.delay(50);
227    }
228}
229
230//------------------------------------------------------------------------------
231class CodeBugEditorKit extends StyledEditorKit {
232
233    ViewFactory defaultFactory = new TableFactory();
234
235    @Override
236    public ViewFactory getViewFactory() {
237        return defaultFactory;
238    }
239
240    @Override
241    public Document createDefaultDocument() {
242        return new CodeBugDocument();
243    }
244}
245//------------------------------------------------------------------------------
246
247class TableFactory implements ViewFactory {
248
249    @Override
250    public View create(Element elem) {
251        String kind = elem.getName();
252        if (kind != null) {
253            if (kind.equals(AbstractDocument.ContentElementName)) {
254                return new LabelView(elem);
255            } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
256                return new ParagraphView(elem);
257            } else if (kind.equals(AbstractDocument.SectionElementName)) {
258                return new BoxView(elem, View.Y_AXIS);
259            } else if (kind.equals(StyleConstants.ComponentElementName)) {
260                return new ComponentView(elem);
261            } else if (kind.equals(CodeBugDocument.ELEMENT_TABLE)) {
262                return new tableView(elem);
263            } else if (kind.equals(StyleConstants.IconElementName)) {
264                return new IconView(elem);
265            }
266        }
267        // default to text display
268        return new LabelView(elem);
269
270    }
271}
272//------------------------------------------------------------------------------
273
274//------------------------------------------------------------------------------
275class tableView extends TableView implements ViewFactory {
276
277    public tableView(Element elem) {
278        super(elem);
279    }
280
281    @Override
282    public void setParent(View parent) {
283        super.setParent(parent);
284    }
285
286    @Override
287    public void setSize(float width, float height) {
288        super.setSize(width, height);
289    }
290
291    @Override
292    public ViewFactory getViewFactory() {
293        return this;
294    }
295
296    @Override
297    public float getMinimumSpan(int axis) {
298        return getPreferredSpan(axis);
299    }
300
301    @Override
302    public float getMaximumSpan(int axis) {
303        return getPreferredSpan(axis);
304    }
305
306    @Override
307    public float getAlignment(int axis) {
308        return 0.5f;
309    }
310
311    @Override
312    public float getPreferredSpan(int axis) {
313        if (axis == 0) {
314            return super.getPreferredSpan(0);
315        }
316        float preferredSpan = super.getPreferredSpan(axis);
317        return preferredSpan;
318    }
319
320    @Override
321    public void paint(Graphics g, Shape allocation) {
322        super.paint(g, allocation);
323        Rectangle alloc = allocation.getBounds();
324        int lastY = alloc.y + alloc.height - 1;
325        g.drawLine(alloc.x, lastY, alloc.x + alloc.width, lastY);
326    }
327
328    @Override
329    protected void paintChild(Graphics g, Rectangle alloc, int index) {
330        super.paintChild(g, alloc, index);
331        int lastX = alloc.x + alloc.width;
332        g.drawLine(alloc.x, alloc.y, lastX, alloc.y);
333    }
334
335    @Override
336    public View create(Element elem) {
337        String kind = elem.getName();
338        if (kind != null) {
339            if (kind.equals(CodeBugDocument.ELEMENT_TR)) {
340                return new trView(elem);
341            } else if (kind.equals(CodeBugDocument.ELEMENT_TD)) {
342                return new BoxView(elem, View.Y_AXIS);
343
344            }
345        }
346
347        // default is to delegate to the normal factory
348        View p = getParent();
349        if (p != null) {
350            ViewFactory f = p.getViewFactory();
351            if (f != null) {
352                return f.create(elem);
353            }
354        }
355
356        return null;
357    }
358
359    public class trView extends TableRow {
360
361        @Override
362        public void setParent(View parent) {
363            super.setParent(parent);
364        }
365
366        public trView(Element elem) {
367            super(elem);
368        }
369
370        public float getMinimumSpan(int axis) {
371            return getPreferredSpan(axis);
372        }
373
374        public float getMaximumSpan(int axis) {
375            return getPreferredSpan(axis);
376        }
377
378        public float getAlignment(int axis) {
379            return 0f;
380        }
381
382        @Override
383        protected void paintChild(Graphics g, Rectangle alloc, int index) {
384            super.paintChild(g, alloc, index);
385            int lastY = alloc.y + alloc.height - 1;
386            g.drawLine(alloc.x, alloc.y, alloc.x, lastY);
387            int lastX = alloc.x + alloc.width;
388            g.drawLine(lastX, alloc.y, lastX, lastY);
389        }
390    };
391}
392
393//------------------------------------------------------------------------------
394class CodeBugDocument extends DefaultStyledDocument {
395
396    public static final String ELEMENT_TABLE = "table";
397    public static final String ELEMENT_TR = "table cells row";
398    public static final String ELEMENT_TD = "table data cell";
399
400    public CodeBugDocument() {
401        //putProperty("i18n", Boolean.TRUE);
402    }
403
404    protected void insertTable(int offset, int rowCount, int colCount) {
405        try {
406            ArrayList Specs = new ArrayList();
407            ElementSpec gapTag = new ElementSpec(new SimpleAttributeSet(),
408                    ElementSpec.ContentType, "\n".toCharArray(), 0, 1);
409            Specs.add(gapTag);
410
411            SimpleAttributeSet tableAttrs = new SimpleAttributeSet();
412            tableAttrs.addAttribute(ElementNameAttribute, ELEMENT_TABLE);
413            ElementSpec tableStart
414                    = new ElementSpec(tableAttrs, ElementSpec.StartTagType);
415            Specs.add(tableStart); //start table tag
416
417            fillRowSpecs(Specs, rowCount, colCount);
418
419            ElementSpec[] spec = new ElementSpec[Specs.size()];
420            Specs.toArray(spec);
421
422            this.insert(offset, spec);
423        } catch (BadLocationException ex) {
424        }
425    }
426
427    protected void fillRowSpecs(ArrayList Specs, int rowCount, int colCount) {
428        SimpleAttributeSet rowAttrs = new SimpleAttributeSet();
429        rowAttrs.addAttribute(ElementNameAttribute, ELEMENT_TR);
430        for (int i = 0; i < rowCount; i++) {
431            ElementSpec rowStart
432                    = new ElementSpec(rowAttrs, ElementSpec.StartTagType);
433            Specs.add(rowStart);
434
435            fillCellSpecs(Specs, colCount);
436
437            ElementSpec rowEnd
438                    = new ElementSpec(rowAttrs, ElementSpec.EndTagType);
439            Specs.add(rowEnd);
440        }
441
442    }
443
444    protected void fillCellSpecs(ArrayList Specs, int colCount) {
445        for (int i = 0; i < colCount; i++) {
446            SimpleAttributeSet cellAttrs = new SimpleAttributeSet();
447            cellAttrs.addAttribute(ElementNameAttribute, ELEMENT_TD);
448
449            ElementSpec cellStart
450                    = new ElementSpec(cellAttrs, ElementSpec.StartTagType);
451            Specs.add(cellStart);
452
453            ElementSpec parStart = new ElementSpec(new SimpleAttributeSet(),
454                    ElementSpec.StartTagType);
455            Specs.add(parStart);
456            ElementSpec parContent = new ElementSpec(new SimpleAttributeSet(),
457                    ElementSpec.ContentType, "\n".toCharArray(), 0, 1);
458            Specs.add(parContent);
459            ElementSpec parEnd = new ElementSpec(new SimpleAttributeSet(),
460                    ElementSpec.EndTagType);
461            Specs.add(parEnd);
462            ElementSpec cellEnd
463                    = new ElementSpec(cellAttrs, ElementSpec.EndTagType);
464            Specs.add(cellEnd);
465        }
466    }
467}
468