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