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