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