• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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