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