1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 1996-2010, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.dev.demo.impl; 10 import java.awt.AWTEventMulticaster; 11 import java.awt.Canvas; 12 import java.awt.Color; 13 import java.awt.Cursor; 14 import java.awt.Dimension; 15 import java.awt.Font; 16 import java.awt.FontMetrics; 17 import java.awt.Graphics; 18 import java.awt.Image; 19 import java.awt.Point; 20 import java.awt.datatransfer.Clipboard; 21 import java.awt.datatransfer.DataFlavor; 22 import java.awt.datatransfer.StringSelection; 23 import java.awt.datatransfer.Transferable; 24 import java.awt.event.ActionEvent; 25 import java.awt.event.ActionListener; 26 import java.awt.event.FocusEvent; 27 import java.awt.event.FocusListener; 28 import java.awt.event.InputEvent; 29 import java.awt.event.KeyEvent; 30 import java.awt.event.KeyListener; 31 import java.awt.event.MouseEvent; 32 import java.awt.event.MouseListener; 33 import java.awt.event.MouseMotionListener; 34 import java.awt.event.TextEvent; 35 import java.awt.event.TextListener; 36 import java.text.BreakIterator; 37 38 // LIU: Changed from final to non-final 39 public class DumbTextComponent extends Canvas 40 implements KeyListener, MouseListener, MouseMotionListener, FocusListener 41 { 42 43 /** 44 * For serialization 45 */ 46 private static final long serialVersionUID = 8265547730738652151L; 47 48 // private transient static final String copyright = 49 // "Copyright \u00A9 1998, Mark Davis. All Rights Reserved."; 50 private transient static boolean DEBUG = false; 51 52 private String contents = ""; 53 private Selection selection = new Selection(); 54 private int activeStart = -1; 55 private boolean editable = true; 56 57 private transient Selection tempSelection = new Selection(); 58 private transient boolean focus; 59 private transient BreakIterator lineBreaker = BreakIterator.getLineInstance(); 60 private transient BreakIterator wordBreaker = BreakIterator.getWordInstance(); 61 private transient BreakIterator charBreaker = BreakIterator.getCharacterInstance(); 62 private transient int lineAscent; 63 private transient int lineHeight; 64 private transient int lineLeading; 65 private transient int lastHeight = 10; 66 private transient int lastWidth = 50; 67 private static final int MAX_LINES = 200; // LIU: Use symbolic name 68 private transient int[] lineStarts = new int[MAX_LINES]; // LIU 69 private transient int lineCount = 1; 70 71 private transient boolean valid = false; 72 private transient FontMetrics fm; 73 private transient boolean redoLines = true; 74 private transient boolean doubleClick = false; 75 private transient TextListener textListener; 76 private transient ActionListener selectionListener; 77 private transient Image cacheImage; 78 private transient Dimension mySize; 79 private transient int xInset = 5; 80 private transient int yInset = 5; 81 private transient Point startPoint = new Point(); 82 private transient Point endPoint = new Point(); 83 private transient Point caretPoint = new Point(); 84 private transient Point activePoint = new Point(); 85 86 //private transient static String clipBoard; 87 88 private static final char CR = '\015'; // LIU 89 90 // ============================================ 91 DumbTextComponent()92 public DumbTextComponent() { 93 addMouseListener(this); 94 addMouseMotionListener(this); 95 addKeyListener(this); 96 addFocusListener(this); 97 setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); 98 99 } 100 101 // ================ Events ==================== 102 103 // public boolean isFocusTraversable() { return true; } 104 addActionListener(ActionListener l)105 public void addActionListener(ActionListener l) { 106 selectionListener = AWTEventMulticaster.add(selectionListener, l); 107 } 108 removeActionListener(ActionListener l)109 public void removeActionListener(ActionListener l) { 110 selectionListener = AWTEventMulticaster.remove(selectionListener, l); 111 } 112 addTextListener(TextListener l)113 public void addTextListener(TextListener l) { 114 textListener = AWTEventMulticaster.add(textListener, l); 115 } 116 removeTextListener(TextListener l)117 public void removeTextListener(TextListener l) { 118 textListener = AWTEventMulticaster.remove(textListener, l); 119 } 120 121 private transient boolean pressed; 122 mousePressed(MouseEvent e)123 public void mousePressed(MouseEvent e) { 124 if (DEBUG) System.out.println("mousePressed"); 125 if (pressed) { 126 select(e,false); 127 } else { 128 doubleClick = e.getClickCount() > 1; 129 requestFocus(); 130 select(e, true); 131 pressed = true; 132 } 133 } 134 mouseDragged(MouseEvent e)135 public void mouseDragged(MouseEvent e) { 136 if (DEBUG) System.out.println("mouseDragged"); 137 select(e, false); 138 } 139 mouseReleased(MouseEvent e)140 public void mouseReleased(MouseEvent e) { 141 if (DEBUG) System.out.println("mouseReleased"); 142 pressed = false; 143 } 144 mouseEntered(MouseEvent e)145 public void mouseEntered(MouseEvent e) { 146 //if (pressed) select(e, false); 147 } 148 mouseExited(MouseEvent e)149 public void mouseExited(MouseEvent e){ 150 //if (pressed) select(e, false); 151 } 152 mouseClicked(MouseEvent e)153 public void mouseClicked(MouseEvent e) {} mouseMoved(MouseEvent e)154 public void mouseMoved(MouseEvent e) {} 155 156 focusGained(FocusEvent e)157 public void focusGained(FocusEvent e) { 158 if (DEBUG) System.out.println("focusGained"); 159 focus = true; 160 valid = false; 161 repaint(16); 162 } focusLost(FocusEvent e)163 public void focusLost(FocusEvent e) { 164 if (DEBUG) System.out.println("focusLost"); 165 focus = false; 166 valid = false; 167 repaint(16); 168 } 169 select(MouseEvent e, boolean first)170 public void select(MouseEvent e, boolean first) { 171 setKeyStart(-1); 172 point2Offset(e.getPoint(), tempSelection); 173 if (first) { 174 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) { 175 tempSelection.anchor = tempSelection.caret; 176 } 177 } 178 // fix words 179 if (doubleClick) { 180 tempSelection.expand(wordBreaker); 181 } 182 select(tempSelection); 183 } 184 keyPressed(KeyEvent e)185 public void keyPressed(KeyEvent e) { 186 int code = e.getKeyCode(); 187 if (DEBUG) System.out.println("keyPressed " 188 + hex((char)code) + ", " + hex((char)e.getModifiers())); 189 int start = selection.getStart(); 190 int end = selection.getEnd(); 191 boolean shift = (e.getModifiers() & InputEvent.SHIFT_MASK) != 0; 192 boolean ctrl = (e.getModifiers() & InputEvent.CTRL_MASK) != 0; 193 194 switch (code) { 195 case KeyEvent.VK_Q: 196 if (!ctrl || !editable) break; 197 setKeyStart(-1); 198 fixHex(); 199 break; 200 case KeyEvent.VK_V: 201 if (!ctrl) break; 202 if (!editable) { 203 this.getToolkit().beep(); 204 } else { 205 paste(); 206 } 207 break; 208 case KeyEvent.VK_C: 209 if (!ctrl) break; 210 copy(); 211 break; 212 case KeyEvent.VK_X: 213 if (!ctrl) break; 214 if (!editable) { 215 this.getToolkit().beep(); 216 } else { 217 copy(); 218 insertText(""); 219 } 220 break; 221 case KeyEvent.VK_A: 222 if (!ctrl) break; 223 setKeyStart(-1); 224 select(Integer.MAX_VALUE, 0, false); 225 break; 226 case KeyEvent.VK_RIGHT: 227 setKeyStart(-1); 228 tempSelection.set(selection); 229 tempSelection.nextBound(ctrl ? wordBreaker : charBreaker, +1, shift); 230 select(tempSelection); 231 break; 232 case KeyEvent.VK_LEFT: 233 setKeyStart(-1); 234 tempSelection.set(selection); 235 tempSelection.nextBound(ctrl ? wordBreaker : charBreaker, -1, shift); 236 select(tempSelection); 237 break; 238 case KeyEvent.VK_UP: // LIU: Add support for up arrow 239 setKeyStart(-1); 240 tempSelection.set(selection); 241 tempSelection.caret = lineDelta(tempSelection.caret, -1); 242 if (!shift) { 243 tempSelection.anchor = tempSelection.caret; 244 } 245 select(tempSelection); 246 break; 247 case KeyEvent.VK_DOWN: // LIU: Add support for down arrow 248 setKeyStart(-1); 249 tempSelection.set(selection); 250 tempSelection.caret = lineDelta(tempSelection.caret, +1); 251 if (!shift) { 252 tempSelection.anchor = tempSelection.caret; 253 } 254 select(tempSelection); 255 break; 256 case KeyEvent.VK_DELETE: // LIU: Add delete key support 257 if (!editable) break; 258 setKeyStart(-1); 259 if (contents.length() == 0) break; 260 start = selection.getStart(); 261 end = selection.getEnd(); 262 if (start == end) { 263 ++end; 264 if (end > contents.length()) { 265 getToolkit().beep(); 266 return; 267 } 268 } 269 replaceRange("", start, end); 270 break; 271 } 272 } 273 copy()274 void copy() { 275 Clipboard cb = this.getToolkit().getSystemClipboard(); 276 StringSelection ss = new StringSelection( 277 contents.substring(selection.getStart(), selection.getEnd())); 278 cb.setContents(ss, ss); 279 } 280 paste()281 void paste () { 282 Clipboard cb = this.getToolkit().getSystemClipboard(); 283 Transferable t = cb.getContents(this); 284 if (t == null) { 285 this.getToolkit().beep(); 286 return; 287 } 288 try { 289 String temp = (String) t.getTransferData(DataFlavor.stringFlavor); 290 insertText(temp); 291 } catch (Exception e) { 292 this.getToolkit().beep(); 293 } 294 } 295 296 /** 297 * LIU: Given an offset into contents, moves up or down by lines, 298 * according to lineStarts[]. 299 * @param off the offset into contents 300 * @param delta how many lines to move up (< 0) or down (> 0) 301 * @return the new offset into contents 302 */ lineDelta(int off, int delta)303 private int lineDelta(int off, int delta) { 304 int line = findLine(off, false); 305 int posInLine = off - lineStarts[line]; 306 // System.out.println("off=" + off + " at " + line + ":" + posInLine); 307 line += delta; 308 if (line < 0) { 309 line = posInLine = 0; 310 } else if (line >= lineCount) { 311 return contents.length(); 312 } 313 off = lineStarts[line] + posInLine; 314 if (off >= lineStarts[line+1]) { 315 off = lineStarts[line+1] - 1; 316 } 317 return off; 318 } 319 keyReleased(KeyEvent e)320 public void keyReleased(KeyEvent e) { 321 int code = e.getKeyCode(); 322 if (DEBUG) System.out.println("keyReleased " 323 + hex((char)code) + ", " + hex((char)e.getModifiers())); 324 } 325 keyTyped(KeyEvent e)326 public void keyTyped(KeyEvent e) { 327 char ch = e.getKeyChar(); 328 if (DEBUG) System.out.println("keyTyped " 329 + hex((char)ch) + ", " + hex((char)e.getModifiers())); 330 if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) return; 331 int start, end; 332 switch (ch) { 333 case KeyEvent.CHAR_UNDEFINED: 334 break; 335 case KeyEvent.VK_BACK_SPACE: 336 //setKeyStart(-1); 337 if (!editable) break; 338 if (contents.length() == 0) break; 339 start = selection.getStart(); 340 end = selection.getEnd(); 341 if (start == end) { 342 --start; 343 if (start < 0) { 344 getToolkit().beep(); // LIU: Add audio feedback of NOP 345 return; 346 } 347 } 348 replaceRange("", start, end); 349 break; 350 case KeyEvent.VK_DELETE: 351 //setKeyStart(-1); 352 if (!editable) break; 353 if (contents.length() == 0) break; 354 start = selection.getStart(); 355 end = selection.getEnd(); 356 if (start == end) { 357 ++end; 358 if (end > contents.length()) { 359 getToolkit().beep(); // LIU: Add audio feedback of NOP 360 return; 361 } 362 } 363 replaceRange("", start, end); 364 break; 365 default: 366 if (!editable) break; 367 // LIU: Dispatch to subclass API 368 handleKeyTyped(e); 369 break; 370 } 371 } 372 373 // LIU: Subclass API for handling of key typing handleKeyTyped(KeyEvent e)374 protected void handleKeyTyped(KeyEvent e) { 375 insertText(String.valueOf(e.getKeyChar())); 376 } 377 setKeyStart(int keyStart)378 protected void setKeyStart(int keyStart) { 379 if (activeStart != keyStart) { 380 activeStart = keyStart; 381 repaint(10); 382 } 383 } 384 validateKeyStart()385 protected void validateKeyStart() { 386 if (activeStart > selection.getStart()) { 387 activeStart = selection.getStart(); 388 repaint(10); 389 } 390 } 391 getKeyStart()392 protected int getKeyStart() { 393 return activeStart; 394 } 395 396 // ===================== Control ====================== 397 setEditable(boolean b)398 public synchronized void setEditable(boolean b) { 399 editable = b; 400 } 401 isEditable()402 public boolean isEditable() { 403 return editable; 404 } 405 select(Selection newSelection)406 public void select(Selection newSelection) { 407 newSelection.pin(contents); 408 if (!selection.equals(newSelection)) { 409 selection.set(newSelection); 410 if (selectionListener != null) { 411 selectionListener.actionPerformed( 412 new ActionEvent(this, ActionEvent.ACTION_PERFORMED, 413 "Selection Changed", 0)); 414 } 415 repaint(10); 416 valid = false; 417 } 418 } 419 select(int start, int end)420 public void select(int start, int end) { 421 select(start, end, false); 422 } 423 select(int start, int end, boolean clickAfter)424 public void select(int start, int end, boolean clickAfter) { 425 tempSelection.set(start, end, clickAfter); 426 select(tempSelection); 427 } 428 getSelectionStart()429 public int getSelectionStart() { 430 return selection.getStart(); 431 } 432 getSelectionEnd()433 public int getSelectionEnd() { 434 return selection.getEnd(); 435 } 436 setBounds(int x, int y, int w, int h)437 public void setBounds(int x, int y, int w, int h) { 438 super.setBounds(x,y,w,h); 439 redoLines = true; 440 } 441 getPreferredSize()442 public Dimension getPreferredSize() { 443 return new Dimension(lastWidth,lastHeight); 444 } 445 getMaximumSize()446 public Dimension getMaximumSize() { 447 return new Dimension(lastWidth,lastHeight); 448 } 449 getMinimumSize()450 public Dimension getMinimumSize() { 451 return new Dimension(lastHeight,lastHeight); 452 } 453 setText(String text)454 public void setText(String text) { 455 setText2(text); 456 select(tempSelection.set(selection).pin(contents)); 457 } 458 setText2(String text)459 public void setText2(String text) { 460 contents = text; 461 charBreaker.setText(text); 462 wordBreaker.setText(text); 463 lineBreaker.setText(text); 464 redoLines = true; 465 if (textListener != null) 466 textListener.textValueChanged( 467 new TextEvent(this, TextEvent.TEXT_VALUE_CHANGED)); 468 repaint(16); 469 } 470 insertText(String text)471 public void insertText(String text) { 472 if (activeStart == -1) activeStart = selection.getStart(); 473 replaceRange(text, selection.getStart(), selection.getEnd()); 474 } 475 replaceRange(String s, int start, int end)476 public void replaceRange(String s, int start, int end) { 477 setText2(contents.substring(0,start) + s 478 + contents.substring(end)); 479 select(tempSelection.set(selection). 480 fixAfterReplace(start, end, s.length())); 481 validateKeyStart(); 482 } 483 getText()484 public String getText() { 485 return contents; 486 } 487 setFont(Font font)488 public void setFont(Font font) { 489 super.setFont(font); 490 redoLines = true; 491 repaint(16); 492 } 493 494 // ================== Graphics ====================== 495 update(Graphics g)496 public void update(Graphics g) { 497 if (DEBUG) System.out.println("update"); 498 paint(g); 499 } 500 paint(Graphics g)501 public void paint(Graphics g) { 502 mySize = getSize(); 503 if (cacheImage == null 504 || cacheImage.getHeight(this) != mySize.height 505 || cacheImage.getWidth(this) != mySize.width) { 506 cacheImage = createImage(mySize.width, mySize.height); 507 valid = false; 508 } 509 if (!valid || redoLines) { 510 if (DEBUG) System.out.println("painting"); 511 paint2(cacheImage.getGraphics()); 512 valid = true; 513 } 514 //getToolkit().sync(); 515 if (DEBUG) System.out.println("copying"); 516 g.drawImage(cacheImage, 517 0, 0, mySize.width, mySize.height, 518 0, 0, mySize.width, mySize.height, 519 this); 520 } 521 paint2(Graphics g)522 public void paint2(Graphics g) { 523 g.clearRect(0, 0, mySize.width, mySize.height); 524 if (DEBUG) System.out.println("print"); 525 if (focus) g.setColor(Color.black); 526 else g.setColor(Color.gray); 527 g.drawRect(0,0,mySize.width-1,mySize.height-1); 528 g.setClip(1,1, 529 mySize.width-2,mySize.height-2); 530 g.setColor(Color.black); 531 g.setFont(getFont()); 532 fm = g.getFontMetrics(); 533 lineAscent = fm.getAscent(); 534 lineLeading = fm.getLeading(); 535 lineHeight = lineAscent + fm.getDescent() + lineLeading; 536 int y = yInset + lineAscent; 537 String lastSubstring = ""; 538 if (redoLines) fixLineStarts(mySize.width-xInset-xInset); 539 for (int i = 0; i < lineCount; y += lineHeight, ++i) { 540 // LIU: Don't display terminating ^M characters 541 int lim = lineStarts[i+1]; 542 if (lim > 0 && contents.length() > 0 && 543 contents.charAt(lim-1) == CR) --lim; 544 lastSubstring = contents.substring(lineStarts[i],lim); 545 g.drawString(lastSubstring, xInset, y); 546 } 547 drawSelection(g, lastSubstring); 548 lastHeight = y + yInset - lineHeight + yInset; 549 lastWidth = mySize.width-xInset-xInset; 550 } 551 paintRect(Graphics g, int x, int y, int w, int h)552 void paintRect(Graphics g, int x, int y, int w, int h) { 553 if (focus) { 554 g.fillRect(x, y, w, h); 555 } else { 556 g.drawRect(x, y, w-1, h-1); 557 } 558 } 559 drawSelection(Graphics g, String lastSubstring)560 public void drawSelection(Graphics g, String lastSubstring) { 561 g.setXORMode(Color.black); 562 if (activeStart != -1) { 563 offset2Point(activeStart, false, activePoint); 564 g.setColor(Color.magenta); 565 int line = activePoint.x - 1; 566 g.fillRect(line, activePoint.y, 1, lineHeight); 567 } 568 if (selection.isCaret()) { 569 offset2Point(selection.caret, selection.clickAfter, caretPoint); 570 } else { 571 if (focus) g.setColor(Color.blue); 572 else g.setColor(Color.yellow); 573 offset2Point(selection.getStart(), true, startPoint); 574 offset2Point(selection.getEnd(), false, endPoint); 575 if (selection.getStart() == selection.caret) 576 caretPoint.setLocation(startPoint); 577 else caretPoint.setLocation(endPoint); 578 if (startPoint.y == endPoint.y) { 579 paintRect(g, startPoint.x, startPoint.y, 580 Math.max(1,endPoint.x-startPoint.x), lineHeight); 581 } else { 582 paintRect(g, startPoint.x, startPoint.y, 583 (mySize.width-xInset)-startPoint.x, lineHeight); 584 if (startPoint.y + lineHeight < endPoint.y) 585 paintRect(g, xInset, startPoint.y + lineHeight, 586 (mySize.width-xInset)-xInset, endPoint.y - startPoint.y - lineHeight); 587 paintRect(g, xInset, endPoint.y, endPoint.x-xInset, lineHeight); 588 } 589 } 590 if (focus || selection.isCaret()) { 591 if (focus) g.setColor(Color.green); 592 else g.setColor(Color.red); 593 int line = caretPoint.x - (selection.clickAfter ? 0 : 1); 594 g.fillRect(line, caretPoint.y, 1, lineHeight); 595 int w = lineHeight/12 + 1; 596 int braces = line - (selection.clickAfter ? -1 : w); 597 g.fillRect(braces, caretPoint.y, w, 1); 598 g.fillRect(braces, caretPoint.y + lineHeight - 1, w, 1); 599 } 600 } 601 offset2Point(int off, boolean start, Point p)602 public Point offset2Point(int off, boolean start, Point p) { 603 int line = findLine(off, start); 604 int width = 0; 605 try { 606 width = fm.stringWidth( 607 contents.substring(lineStarts[line], off)); 608 } catch (Exception e) { 609 System.out.println(e); 610 } 611 p.x = width + xInset; 612 if (p.x > mySize.width - xInset) 613 p.x = mySize.width - xInset; 614 p.y = lineHeight * line + yInset; 615 return p; 616 } 617 findLine(int off, boolean start)618 private int findLine(int off, boolean start) { 619 // if it is start, then go to the next line! 620 if (start) ++off; 621 for (int i = 1; i < lineCount; ++i) { 622 // LIU: This was <= ; changed to < to make caret after 623 // final CR in line appear at START of next line. 624 if (off < lineStarts[i]) return i-1; 625 } 626 // LIU: Check for special case; after CR at end of the last line 627 if (off == lineStarts[lineCount] && 628 off > 0 && contents.length() > 0 && contents.charAt(off-1) == CR) { 629 return lineCount; 630 } 631 return lineCount-1; 632 } 633 634 // offsets on any line will go from start,true to end,false 635 // excluding start,false and end,true point2Offset(Point p, Selection o)636 public Selection point2Offset(Point p, Selection o) { 637 if (p.y < yInset) { 638 o.caret = 0; 639 o.clickAfter = true; 640 return o; 641 } 642 int line = (p.y - yInset)/lineHeight; 643 if (line >= lineCount) { 644 o.caret = contents.length(); 645 o.clickAfter = false; 646 return o; 647 } 648 int target = p.x - xInset; 649 if (target <= 0) { 650 o.caret = lineStarts[line]; 651 o.clickAfter = true; 652 return o; 653 } 654 int lowGuess = lineStarts[line]; 655 int lowWidth = 0; 656 int highGuess = lineStarts[line+1]; 657 int highWidth = fm.stringWidth(contents.substring(lineStarts[line],highGuess)); 658 if (target >= highWidth) { 659 o.caret = lineStarts[line+1]; 660 o.clickAfter = false; 661 return o; 662 } 663 while (lowGuess < highGuess - 1) { 664 int guess = (lowGuess + highGuess)/2; 665 int width = fm.stringWidth(contents.substring(lineStarts[line],guess)); 666 if (width <= target) { 667 lowGuess = guess; 668 lowWidth = width; 669 if (width == target) break; 670 } else { 671 highGuess = guess; 672 highWidth = width; 673 } 674 } 675 // at end, either lowWidth < target < width(low+1), or lowWidth = target 676 int highBound = charBreaker.following(lowGuess); 677 int lowBound = charBreaker.previous(); 678 // we are now at character boundaries 679 if (lowBound != lowGuess) 680 lowWidth = fm.stringWidth(contents.substring(lineStarts[line],lowBound)); 681 if (highBound != highGuess) 682 highWidth = fm.stringWidth(contents.substring(lineStarts[line],highBound)); 683 // we now have the right widths 684 if (target - lowWidth < highWidth - target) { 685 o.caret = lowBound; 686 o.clickAfter = true; 687 } else { 688 o.caret = highBound; 689 o.clickAfter = false; 690 } 691 // we now have the closest! 692 return o; 693 } 694 fixLineStarts(int width)695 private void fixLineStarts(int width) { 696 lineCount = 1; 697 lineStarts[0] = 0; 698 if (contents.length() == 0) { 699 lineStarts[1] = 0; 700 return; 701 } 702 int end = 0; 703 // LIU: Add check for MAX_LINES 704 for (int start = 0; start < contents.length() && lineCount < MAX_LINES; 705 start = end) { 706 end = nextLine(fm, start, width); 707 lineStarts[lineCount++] = end; 708 if (end == start) { // LIU: Assertion 709 throw new RuntimeException("nextLine broken"); 710 } 711 } 712 --lineCount; 713 redoLines = false; 714 } 715 716 // LIU: Enhanced to wrap long lines. Bug with return of start fixed. nextLine(FontMetrics fMtr, int start, int width)717 public int nextLine(FontMetrics fMtr, int start, int width) { 718 int len = contents.length(); 719 for (int i = start; i < len; ++i) { 720 // check for line separator 721 char ch = (contents.charAt(i)); 722 if (ch >= 0x000A && ch <= 0x000D || ch == 0x2028 || ch == 0x2029) { 723 len = i + 1; 724 if (ch == 0x000D && i+1 < len && contents.charAt(i+1) == 0x000A) // crlf 725 ++len; // grab extra char 726 break; 727 } 728 } 729 String subject = contents.substring(start,len); 730 if (visibleWidth(fMtr, subject) <= width) 731 return len; 732 733 // LIU: Remainder of this method rewritten to accomodate lines 734 // longer than the component width by first trying to break 735 // into lines; then words; finally chars. 736 int n = findFittingBreak(fMtr, subject, width, lineBreaker); 737 if (n == 0) { 738 n = findFittingBreak(fMtr, subject, width, wordBreaker); 739 } 740 if (n == 0) { 741 n = findFittingBreak(fMtr, subject, width, charBreaker); 742 } 743 return n > 0 ? start + n : len; 744 } 745 746 /** 747 * LIU: Finds the longest substring that fits a given width 748 * composed of subunits returned by a BreakIterator. If the smallest 749 * subunit is too long, returns 0. 750 * @param fMtr metrics to use 751 * @param line the string to be fix into width 752 * @param width line.substring(0, result) must be <= width 753 * @param breaker the BreakIterator that will be used to find subunits 754 * @return maximum characters, at boundaries returned by breaker, 755 * that fit into width, or zero on failure 756 */ findFittingBreak(FontMetrics fMtr, String line, int width, BreakIterator breaker)757 private int findFittingBreak(FontMetrics fMtr, String line, int width, 758 BreakIterator breaker) { 759 breaker.setText(line); 760 int last = breaker.first(); 761 int end = breaker.next(); 762 while (end != BreakIterator.DONE && 763 visibleWidth(fMtr, line.substring(0, end)) <= width) { 764 last = end; 765 end = breaker.next(); 766 } 767 return last; 768 } 769 visibleWidth(FontMetrics fMtr, String s)770 public int visibleWidth(FontMetrics fMtr, String s) { 771 int i; 772 for (i = s.length()-1; i >= 0; --i) { 773 char ch = s.charAt(i); 774 if (!(ch == ' ' || ch >= 0x000A && ch <= 0x000D || ch == 0x2028 || ch == 0x2029)) 775 return fMtr.stringWidth(s.substring(0,i+1)); 776 } 777 return 0; 778 } 779 780 // =============== Utility ==================== 781 fixHex()782 private void fixHex() { 783 if (selection.getEnd() == 0) return; 784 int store = 0; 785 int places = 1; 786 int count = 0; 787 int min = Math.min(8,selection.getEnd()); 788 for (int i = 0; i < min; ++i) { 789 char ch = contents.charAt(selection.getEnd()-1-i); 790 int value = Character.getNumericValue(ch); 791 if (value < 0 || value > 15) break; 792 store += places * value; 793 ++count; 794 places *= 16; 795 } 796 String add = ""; 797 int bottom = store & 0xFFFF; 798 if (store >= 0xD8000000 && store < 0xDC000000 799 && bottom >= 0xDC00 && bottom < 0xE000) { // surrogates 800 add = "" + (char)(store >> 16) + (char)bottom; 801 } else if (store > 0xFFFF && store <= 0x10FFFF) { 802 store -= 0x10000; 803 add = "" + (char)(((store >> 10) & 0x3FF) + 0xD800) 804 + (char)((store & 0x3FF) + 0xDC00); 805 806 } else if (count >= 4) { 807 count = 4; 808 add = ""+(char)(store & 0xFFFF); 809 } else { 810 count = 1; 811 char ch = contents.charAt(selection.getEnd()-1); 812 add = hex(ch); 813 if (ch >= 0xDC00 && ch <= 0xDFFF && selection.getEnd() > 1) { 814 ch = contents.charAt(selection.getEnd()-2); 815 if (ch >= 0xD800 && ch <= 0xDBFF) { 816 count = 2; 817 add = hex(ch) + add; 818 } 819 } 820 } 821 replaceRange(add, selection.getEnd()-count, selection.getEnd()); 822 } 823 hex(char ch)824 public static String hex(char ch) { 825 String result = Integer.toString(ch,16).toUpperCase(); 826 result = "0000".substring(result.length(),4) + result; 827 return result; 828 } 829 } 830