]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_8_1_1/demos/src/com/ibm/icu/dev/demo/impl/DumbTextComponent.java
Added flags.
[Dictionary.git] / jars / icu4j-4_8_1_1 / demos / src / com / ibm / icu / dev / demo / impl / DumbTextComponent.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 1996-2010, International Business Machines Corporation and    *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package com.ibm.icu.dev.demo.impl;
8 import java.awt.AWTEventMulticaster;
9 import java.awt.Canvas;
10 import java.awt.Color;
11 import java.awt.Cursor;
12 import java.awt.Dimension;
13 import java.awt.Font;
14 import java.awt.FontMetrics;
15 import java.awt.Graphics;
16 import java.awt.Image;
17 import java.awt.Point;
18 import java.awt.datatransfer.Clipboard;
19 import java.awt.datatransfer.DataFlavor;
20 import java.awt.datatransfer.StringSelection;
21 import java.awt.datatransfer.Transferable;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.awt.event.FocusEvent;
25 import java.awt.event.FocusListener;
26 import java.awt.event.InputEvent;
27 import java.awt.event.KeyEvent;
28 import java.awt.event.KeyListener;
29 import java.awt.event.MouseEvent;
30 import java.awt.event.MouseListener;
31 import java.awt.event.MouseMotionListener;
32 import java.awt.event.TextEvent;
33 import java.awt.event.TextListener;
34 import java.text.BreakIterator;
35
36 // LIU: Changed from final to non-final
37 public 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 }