| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2010, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.dev.demo.impl; |
| import java.awt.AWTEventMulticaster; |
| import java.awt.Canvas; |
| import java.awt.Color; |
| import java.awt.Cursor; |
| import java.awt.Dimension; |
| import java.awt.Font; |
| import java.awt.FontMetrics; |
| import java.awt.Graphics; |
| import java.awt.Image; |
| import java.awt.Point; |
| import java.awt.datatransfer.Clipboard; |
| import java.awt.datatransfer.DataFlavor; |
| import java.awt.datatransfer.StringSelection; |
| import java.awt.datatransfer.Transferable; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.awt.event.FocusEvent; |
| import java.awt.event.FocusListener; |
| import java.awt.event.InputEvent; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.KeyListener; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.MouseListener; |
| import java.awt.event.MouseMotionListener; |
| import java.awt.event.TextEvent; |
| import java.awt.event.TextListener; |
| import java.text.BreakIterator; |
| |
| // LIU: Changed from final to non-final |
| public class DumbTextComponent extends Canvas |
| implements KeyListener, MouseListener, MouseMotionListener, FocusListener |
| { |
| |
| /** |
| * For serialization |
| */ |
| private static final long serialVersionUID = 8265547730738652151L; |
| |
| // private transient static final String copyright = |
| // "Copyright \u00A9 1998, Mark Davis. All Rights Reserved."; |
| private transient static boolean DEBUG = false; |
| |
| private String contents = ""; |
| private Selection selection = new Selection(); |
| private int activeStart = -1; |
| private boolean editable = true; |
| |
| private transient Selection tempSelection = new Selection(); |
| private transient boolean focus; |
| private transient BreakIterator lineBreaker = BreakIterator.getLineInstance(); |
| private transient BreakIterator wordBreaker = BreakIterator.getWordInstance(); |
| private transient BreakIterator charBreaker = BreakIterator.getCharacterInstance(); |
| private transient int lineAscent; |
| private transient int lineHeight; |
| private transient int lineLeading; |
| private transient int lastHeight = 10; |
| private transient int lastWidth = 50; |
| private static final int MAX_LINES = 200; // LIU: Use symbolic name |
| private transient int[] lineStarts = new int[MAX_LINES]; // LIU |
| private transient int lineCount = 1; |
| |
| private transient boolean valid = false; |
| private transient FontMetrics fm; |
| private transient boolean redoLines = true; |
| private transient boolean doubleClick = false; |
| private transient TextListener textListener; |
| private transient ActionListener selectionListener; |
| private transient Image cacheImage; |
| private transient Dimension mySize; |
| private transient int xInset = 5; |
| private transient int yInset = 5; |
| private transient Point startPoint = new Point(); |
| private transient Point endPoint = new Point(); |
| private transient Point caretPoint = new Point(); |
| private transient Point activePoint = new Point(); |
| |
| //private transient static String clipBoard; |
| |
| private static final char CR = '\015'; // LIU |
| |
| // ============================================ |
| |
| public DumbTextComponent() { |
| addMouseListener(this); |
| addMouseMotionListener(this); |
| addKeyListener(this); |
| addFocusListener(this); |
| setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); |
| |
| } |
| |
| // ================ Events ==================== |
| |
| // public boolean isFocusTraversable() { return true; } |
| |
| public void addActionListener(ActionListener l) { |
| selectionListener = AWTEventMulticaster.add(selectionListener, l); |
| } |
| |
| public void removeActionListener(ActionListener l) { |
| selectionListener = AWTEventMulticaster.remove(selectionListener, l); |
| } |
| |
| public void addTextListener(TextListener l) { |
| textListener = AWTEventMulticaster.add(textListener, l); |
| } |
| |
| public void removeTextListener(TextListener l) { |
| textListener = AWTEventMulticaster.remove(textListener, l); |
| } |
| |
| private transient boolean pressed; |
| |
| public void mousePressed(MouseEvent e) { |
| if (DEBUG) System.out.println("mousePressed"); |
| if (pressed) { |
| select(e,false); |
| } else { |
| doubleClick = e.getClickCount() > 1; |
| requestFocus(); |
| select(e, true); |
| pressed = true; |
| } |
| } |
| |
| public void mouseDragged(MouseEvent e) { |
| if (DEBUG) System.out.println("mouseDragged"); |
| select(e, false); |
| } |
| |
| public void mouseReleased(MouseEvent e) { |
| if (DEBUG) System.out.println("mouseReleased"); |
| pressed = false; |
| } |
| |
| public void mouseEntered(MouseEvent e) { |
| //if (pressed) select(e, false); |
| } |
| |
| public void mouseExited(MouseEvent e){ |
| //if (pressed) select(e, false); |
| } |
| |
| public void mouseClicked(MouseEvent e) {} |
| public void mouseMoved(MouseEvent e) {} |
| |
| |
| public void focusGained(FocusEvent e) { |
| if (DEBUG) System.out.println("focusGained"); |
| focus = true; |
| valid = false; |
| repaint(16); |
| } |
| public void focusLost(FocusEvent e) { |
| if (DEBUG) System.out.println("focusLost"); |
| focus = false; |
| valid = false; |
| repaint(16); |
| } |
| |
| public void select(MouseEvent e, boolean first) { |
| setKeyStart(-1); |
| point2Offset(e.getPoint(), tempSelection); |
| if (first) { |
| if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) { |
| tempSelection.anchor = tempSelection.caret; |
| } |
| } |
| // fix words |
| if (doubleClick) { |
| tempSelection.expand(wordBreaker); |
| } |
| select(tempSelection); |
| } |
| |
| public void keyPressed(KeyEvent e) { |
| int code = e.getKeyCode(); |
| if (DEBUG) System.out.println("keyPressed " |
| + hex((char)code) + ", " + hex((char)e.getModifiers())); |
| int start = selection.getStart(); |
| int end = selection.getEnd(); |
| boolean shift = (e.getModifiers() & InputEvent.SHIFT_MASK) != 0; |
| boolean ctrl = (e.getModifiers() & InputEvent.CTRL_MASK) != 0; |
| |
| switch (code) { |
| case KeyEvent.VK_Q: |
| if (!ctrl || !editable) break; |
| setKeyStart(-1); |
| fixHex(); |
| break; |
| case KeyEvent.VK_V: |
| if (!ctrl) break; |
| if (!editable) { |
| this.getToolkit().beep(); |
| } else { |
| paste(); |
| } |
| break; |
| case KeyEvent.VK_C: |
| if (!ctrl) break; |
| copy(); |
| break; |
| case KeyEvent.VK_X: |
| if (!ctrl) break; |
| if (!editable) { |
| this.getToolkit().beep(); |
| } else { |
| copy(); |
| insertText(""); |
| } |
| break; |
| case KeyEvent.VK_A: |
| if (!ctrl) break; |
| setKeyStart(-1); |
| select(Integer.MAX_VALUE, 0, false); |
| break; |
| case KeyEvent.VK_RIGHT: |
| setKeyStart(-1); |
| tempSelection.set(selection); |
| tempSelection.nextBound(ctrl ? wordBreaker : charBreaker, +1, shift); |
| select(tempSelection); |
| break; |
| case KeyEvent.VK_LEFT: |
| setKeyStart(-1); |
| tempSelection.set(selection); |
| tempSelection.nextBound(ctrl ? wordBreaker : charBreaker, -1, shift); |
| select(tempSelection); |
| break; |
| case KeyEvent.VK_UP: // LIU: Add support for up arrow |
| setKeyStart(-1); |
| tempSelection.set(selection); |
| tempSelection.caret = lineDelta(tempSelection.caret, -1); |
| if (!shift) { |
| tempSelection.anchor = tempSelection.caret; |
| } |
| select(tempSelection); |
| break; |
| case KeyEvent.VK_DOWN: // LIU: Add support for down arrow |
| setKeyStart(-1); |
| tempSelection.set(selection); |
| tempSelection.caret = lineDelta(tempSelection.caret, +1); |
| if (!shift) { |
| tempSelection.anchor = tempSelection.caret; |
| } |
| select(tempSelection); |
| break; |
| case KeyEvent.VK_DELETE: // LIU: Add delete key support |
| if (!editable) break; |
| setKeyStart(-1); |
| if (contents.length() == 0) break; |
| start = selection.getStart(); |
| end = selection.getEnd(); |
| if (start == end) { |
| ++end; |
| if (end > contents.length()) { |
| getToolkit().beep(); |
| return; |
| } |
| } |
| replaceRange("", start, end); |
| break; |
| } |
| } |
| |
| void copy() { |
| Clipboard cb = this.getToolkit().getSystemClipboard(); |
| StringSelection ss = new StringSelection( |
| contents.substring(selection.getStart(), selection.getEnd())); |
| cb.setContents(ss, ss); |
| } |
| |
| void paste () { |
| Clipboard cb = this.getToolkit().getSystemClipboard(); |
| Transferable t = cb.getContents(this); |
| if (t == null) { |
| this.getToolkit().beep(); |
| return; |
| } |
| try { |
| String temp = (String) t.getTransferData(DataFlavor.stringFlavor); |
| insertText(temp); |
| } catch (Exception e) { |
| this.getToolkit().beep(); |
| } |
| } |
| |
| /** |
| * LIU: Given an offset into contents, moves up or down by lines, |
| * according to lineStarts[]. |
| * @param off the offset into contents |
| * @param delta how many lines to move up (< 0) or down (> 0) |
| * @return the new offset into contents |
| */ |
| private int lineDelta(int off, int delta) { |
| int line = findLine(off, false); |
| int posInLine = off - lineStarts[line]; |
| // System.out.println("off=" + off + " at " + line + ":" + posInLine); |
| line += delta; |
| if (line < 0) { |
| line = posInLine = 0; |
| } else if (line >= lineCount) { |
| return contents.length(); |
| } |
| off = lineStarts[line] + posInLine; |
| if (off >= lineStarts[line+1]) { |
| off = lineStarts[line+1] - 1; |
| } |
| return off; |
| } |
| |
| public void keyReleased(KeyEvent e) { |
| int code = e.getKeyCode(); |
| if (DEBUG) System.out.println("keyReleased " |
| + hex((char)code) + ", " + hex((char)e.getModifiers())); |
| } |
| |
| public void keyTyped(KeyEvent e) { |
| char ch = e.getKeyChar(); |
| if (DEBUG) System.out.println("keyTyped " |
| + hex((char)ch) + ", " + hex((char)e.getModifiers())); |
| if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) return; |
| int start, end; |
| switch (ch) { |
| case KeyEvent.CHAR_UNDEFINED: |
| break; |
| case KeyEvent.VK_BACK_SPACE: |
| //setKeyStart(-1); |
| if (!editable) break; |
| if (contents.length() == 0) break; |
| start = selection.getStart(); |
| end = selection.getEnd(); |
| if (start == end) { |
| --start; |
| if (start < 0) { |
| getToolkit().beep(); // LIU: Add audio feedback of NOP |
| return; |
| } |
| } |
| replaceRange("", start, end); |
| break; |
| case KeyEvent.VK_DELETE: |
| //setKeyStart(-1); |
| if (!editable) break; |
| if (contents.length() == 0) break; |
| start = selection.getStart(); |
| end = selection.getEnd(); |
| if (start == end) { |
| ++end; |
| if (end > contents.length()) { |
| getToolkit().beep(); // LIU: Add audio feedback of NOP |
| return; |
| } |
| } |
| replaceRange("", start, end); |
| break; |
| default: |
| if (!editable) break; |
| // LIU: Dispatch to subclass API |
| handleKeyTyped(e); |
| break; |
| } |
| } |
| |
| // LIU: Subclass API for handling of key typing |
| protected void handleKeyTyped(KeyEvent e) { |
| insertText(String.valueOf(e.getKeyChar())); |
| } |
| |
| protected void setKeyStart(int keyStart) { |
| if (activeStart != keyStart) { |
| activeStart = keyStart; |
| repaint(10); |
| } |
| } |
| |
| protected void validateKeyStart() { |
| if (activeStart > selection.getStart()) { |
| activeStart = selection.getStart(); |
| repaint(10); |
| } |
| } |
| |
| protected int getKeyStart() { |
| return activeStart; |
| } |
| |
| // ===================== Control ====================== |
| |
| public synchronized void setEditable(boolean b) { |
| editable = b; |
| } |
| |
| public boolean isEditable() { |
| return editable; |
| } |
| |
| public void select(Selection newSelection) { |
| newSelection.pin(contents); |
| if (!selection.equals(newSelection)) { |
| selection.set(newSelection); |
| if (selectionListener != null) { |
| selectionListener.actionPerformed( |
| new ActionEvent(this, ActionEvent.ACTION_PERFORMED, |
| "Selection Changed", 0)); |
| } |
| repaint(10); |
| valid = false; |
| } |
| } |
| |
| public void select(int start, int end) { |
| select(start, end, false); |
| } |
| |
| public void select(int start, int end, boolean clickAfter) { |
| tempSelection.set(start, end, clickAfter); |
| select(tempSelection); |
| } |
| |
| public int getSelectionStart() { |
| return selection.getStart(); |
| } |
| |
| public int getSelectionEnd() { |
| return selection.getEnd(); |
| } |
| |
| public void setBounds(int x, int y, int w, int h) { |
| super.setBounds(x,y,w,h); |
| redoLines = true; |
| } |
| |
| public Dimension getPreferredSize() { |
| return new Dimension(lastWidth,lastHeight); |
| } |
| |
| public Dimension getMaximumSize() { |
| return new Dimension(lastWidth,lastHeight); |
| } |
| |
| public Dimension getMinimumSize() { |
| return new Dimension(lastHeight,lastHeight); |
| } |
| |
| public void setText(String text) { |
| setText2(text); |
| select(tempSelection.set(selection).pin(contents)); |
| } |
| |
| public void setText2(String text) { |
| contents = text; |
| charBreaker.setText(text); |
| wordBreaker.setText(text); |
| lineBreaker.setText(text); |
| redoLines = true; |
| if (textListener != null) |
| textListener.textValueChanged( |
| new TextEvent(this, TextEvent.TEXT_VALUE_CHANGED)); |
| repaint(16); |
| } |
| |
| public void insertText(String text) { |
| if (activeStart == -1) activeStart = selection.getStart(); |
| replaceRange(text, selection.getStart(), selection.getEnd()); |
| } |
| |
| public void replaceRange(String s, int start, int end) { |
| setText2(contents.substring(0,start) + s |
| + contents.substring(end)); |
| select(tempSelection.set(selection). |
| fixAfterReplace(start, end, s.length())); |
| validateKeyStart(); |
| } |
| |
| public String getText() { |
| return contents; |
| } |
| |
| public void setFont(Font font) { |
| super.setFont(font); |
| redoLines = true; |
| repaint(16); |
| } |
| |
| // ================== Graphics ====================== |
| |
| public void update(Graphics g) { |
| if (DEBUG) System.out.println("update"); |
| paint(g); |
| } |
| |
| public void paint(Graphics g) { |
| mySize = getSize(); |
| if (cacheImage == null |
| || cacheImage.getHeight(this) != mySize.height |
| || cacheImage.getWidth(this) != mySize.width) { |
| cacheImage = createImage(mySize.width, mySize.height); |
| valid = false; |
| } |
| if (!valid || redoLines) { |
| if (DEBUG) System.out.println("painting"); |
| paint2(cacheImage.getGraphics()); |
| valid = true; |
| } |
| //getToolkit().sync(); |
| if (DEBUG) System.out.println("copying"); |
| g.drawImage(cacheImage, |
| 0, 0, mySize.width, mySize.height, |
| 0, 0, mySize.width, mySize.height, |
| this); |
| } |
| |
| public void paint2(Graphics g) { |
| g.clearRect(0, 0, mySize.width, mySize.height); |
| if (DEBUG) System.out.println("print"); |
| if (focus) g.setColor(Color.black); |
| else g.setColor(Color.gray); |
| g.drawRect(0,0,mySize.width-1,mySize.height-1); |
| g.setClip(1,1, |
| mySize.width-2,mySize.height-2); |
| g.setColor(Color.black); |
| g.setFont(getFont()); |
| fm = g.getFontMetrics(); |
| lineAscent = fm.getAscent(); |
| lineLeading = fm.getLeading(); |
| lineHeight = lineAscent + fm.getDescent() + lineLeading; |
| int y = yInset + lineAscent; |
| String lastSubstring = ""; |
| if (redoLines) fixLineStarts(mySize.width-xInset-xInset); |
| for (int i = 0; i < lineCount; y += lineHeight, ++i) { |
| // LIU: Don't display terminating ^M characters |
| int lim = lineStarts[i+1]; |
| if (lim > 0 && contents.length() > 0 && |
| contents.charAt(lim-1) == CR) --lim; |
| lastSubstring = contents.substring(lineStarts[i],lim); |
| g.drawString(lastSubstring, xInset, y); |
| } |
| drawSelection(g, lastSubstring); |
| lastHeight = y + yInset - lineHeight + yInset; |
| lastWidth = mySize.width-xInset-xInset; |
| } |
| |
| void paintRect(Graphics g, int x, int y, int w, int h) { |
| if (focus) { |
| g.fillRect(x, y, w, h); |
| } else { |
| g.drawRect(x, y, w-1, h-1); |
| } |
| } |
| |
| public void drawSelection(Graphics g, String lastSubstring) { |
| g.setXORMode(Color.black); |
| if (activeStart != -1) { |
| offset2Point(activeStart, false, activePoint); |
| g.setColor(Color.magenta); |
| int line = activePoint.x - 1; |
| g.fillRect(line, activePoint.y, 1, lineHeight); |
| } |
| if (selection.isCaret()) { |
| offset2Point(selection.caret, selection.clickAfter, caretPoint); |
| } else { |
| if (focus) g.setColor(Color.blue); |
| else g.setColor(Color.yellow); |
| offset2Point(selection.getStart(), true, startPoint); |
| offset2Point(selection.getEnd(), false, endPoint); |
| if (selection.getStart() == selection.caret) |
| caretPoint.setLocation(startPoint); |
| else caretPoint.setLocation(endPoint); |
| if (startPoint.y == endPoint.y) { |
| paintRect(g, startPoint.x, startPoint.y, |
| Math.max(1,endPoint.x-startPoint.x), lineHeight); |
| } else { |
| paintRect(g, startPoint.x, startPoint.y, |
| (mySize.width-xInset)-startPoint.x, lineHeight); |
| if (startPoint.y + lineHeight < endPoint.y) |
| paintRect(g, xInset, startPoint.y + lineHeight, |
| (mySize.width-xInset)-xInset, endPoint.y - startPoint.y - lineHeight); |
| paintRect(g, xInset, endPoint.y, endPoint.x-xInset, lineHeight); |
| } |
| } |
| if (focus || selection.isCaret()) { |
| if (focus) g.setColor(Color.green); |
| else g.setColor(Color.red); |
| int line = caretPoint.x - (selection.clickAfter ? 0 : 1); |
| g.fillRect(line, caretPoint.y, 1, lineHeight); |
| int w = lineHeight/12 + 1; |
| int braces = line - (selection.clickAfter ? -1 : w); |
| g.fillRect(braces, caretPoint.y, w, 1); |
| g.fillRect(braces, caretPoint.y + lineHeight - 1, w, 1); |
| } |
| } |
| |
| public Point offset2Point(int off, boolean start, Point p) { |
| int line = findLine(off, start); |
| int width = 0; |
| try { |
| width = fm.stringWidth( |
| contents.substring(lineStarts[line], off)); |
| } catch (Exception e) { |
| System.out.println(e); |
| } |
| p.x = width + xInset; |
| if (p.x > mySize.width - xInset) |
| p.x = mySize.width - xInset; |
| p.y = lineHeight * line + yInset; |
| return p; |
| } |
| |
| private int findLine(int off, boolean start) { |
| // if it is start, then go to the next line! |
| if (start) ++off; |
| for (int i = 1; i < lineCount; ++i) { |
| // LIU: This was <= ; changed to < to make caret after |
| // final CR in line appear at START of next line. |
| if (off < lineStarts[i]) return i-1; |
| } |
| // LIU: Check for special case; after CR at end of the last line |
| if (off == lineStarts[lineCount] && |
| off > 0 && contents.length() > 0 && contents.charAt(off-1) == CR) { |
| return lineCount; |
| } |
| return lineCount-1; |
| } |
| |
| // offsets on any line will go from start,true to end,false |
| // excluding start,false and end,true |
| public Selection point2Offset(Point p, Selection o) { |
| if (p.y < yInset) { |
| o.caret = 0; |
| o.clickAfter = true; |
| return o; |
| } |
| int line = (p.y - yInset)/lineHeight; |
| if (line >= lineCount) { |
| o.caret = contents.length(); |
| o.clickAfter = false; |
| return o; |
| } |
| int target = p.x - xInset; |
| if (target <= 0) { |
| o.caret = lineStarts[line]; |
| o.clickAfter = true; |
| return o; |
| } |
| int lowGuess = lineStarts[line]; |
| int lowWidth = 0; |
| int highGuess = lineStarts[line+1]; |
| int highWidth = fm.stringWidth(contents.substring(lineStarts[line],highGuess)); |
| if (target >= highWidth) { |
| o.caret = lineStarts[line+1]; |
| o.clickAfter = false; |
| return o; |
| } |
| while (lowGuess < highGuess - 1) { |
| int guess = (lowGuess + highGuess)/2; |
| int width = fm.stringWidth(contents.substring(lineStarts[line],guess)); |
| if (width <= target) { |
| lowGuess = guess; |
| lowWidth = width; |
| if (width == target) break; |
| } else { |
| highGuess = guess; |
| highWidth = width; |
| } |
| } |
| // at end, either lowWidth < target < width(low+1), or lowWidth = target |
| int highBound = charBreaker.following(lowGuess); |
| int lowBound = charBreaker.previous(); |
| // we are now at character boundaries |
| if (lowBound != lowGuess) |
| lowWidth = fm.stringWidth(contents.substring(lineStarts[line],lowBound)); |
| if (highBound != highGuess) |
| highWidth = fm.stringWidth(contents.substring(lineStarts[line],highBound)); |
| // we now have the right widths |
| if (target - lowWidth < highWidth - target) { |
| o.caret = lowBound; |
| o.clickAfter = true; |
| } else { |
| o.caret = highBound; |
| o.clickAfter = false; |
| } |
| // we now have the closest! |
| return o; |
| } |
| |
| private void fixLineStarts(int width) { |
| lineCount = 1; |
| lineStarts[0] = 0; |
| if (contents.length() == 0) { |
| lineStarts[1] = 0; |
| return; |
| } |
| int end = 0; |
| // LIU: Add check for MAX_LINES |
| for (int start = 0; start < contents.length() && lineCount < MAX_LINES; |
| start = end) { |
| end = nextLine(fm, start, width); |
| lineStarts[lineCount++] = end; |
| if (end == start) { // LIU: Assertion |
| throw new RuntimeException("nextLine broken"); |
| } |
| } |
| --lineCount; |
| redoLines = false; |
| } |
| |
| // LIU: Enhanced to wrap long lines. Bug with return of start fixed. |
| public int nextLine(FontMetrics fMtr, int start, int width) { |
| int len = contents.length(); |
| for (int i = start; i < len; ++i) { |
| // check for line separator |
| char ch = (contents.charAt(i)); |
| if (ch >= 0x000A && ch <= 0x000D || ch == 0x2028 || ch == 0x2029) { |
| len = i + 1; |
| if (ch == 0x000D && i+1 < len && contents.charAt(i+1) == 0x000A) // crlf |
| ++len; // grab extra char |
| break; |
| } |
| } |
| String subject = contents.substring(start,len); |
| if (visibleWidth(fMtr, subject) <= width) |
| return len; |
| |
| // LIU: Remainder of this method rewritten to accomodate lines |
| // longer than the component width by first trying to break |
| // into lines; then words; finally chars. |
| int n = findFittingBreak(fMtr, subject, width, lineBreaker); |
| if (n == 0) { |
| n = findFittingBreak(fMtr, subject, width, wordBreaker); |
| } |
| if (n == 0) { |
| n = findFittingBreak(fMtr, subject, width, charBreaker); |
| } |
| return n > 0 ? start + n : len; |
| } |
| |
| /** |
| * LIU: Finds the longest substring that fits a given width |
| * composed of subunits returned by a BreakIterator. If the smallest |
| * subunit is too long, returns 0. |
| * @param fMtr metrics to use |
| * @param line the string to be fix into width |
| * @param width line.substring(0, result) must be <= width |
| * @param breaker the BreakIterator that will be used to find subunits |
| * @return maximum characters, at boundaries returned by breaker, |
| * that fit into width, or zero on failure |
| */ |
| private int findFittingBreak(FontMetrics fMtr, String line, int width, |
| BreakIterator breaker) { |
| breaker.setText(line); |
| int last = breaker.first(); |
| int end = breaker.next(); |
| while (end != BreakIterator.DONE && |
| visibleWidth(fMtr, line.substring(0, end)) <= width) { |
| last = end; |
| end = breaker.next(); |
| } |
| return last; |
| } |
| |
| public int visibleWidth(FontMetrics fMtr, String s) { |
| int i; |
| for (i = s.length()-1; i >= 0; --i) { |
| char ch = s.charAt(i); |
| if (!(ch == ' ' || ch >= 0x000A && ch <= 0x000D || ch == 0x2028 || ch == 0x2029)) |
| return fMtr.stringWidth(s.substring(0,i+1)); |
| } |
| return 0; |
| } |
| |
| // =============== Utility ==================== |
| |
| private void fixHex() { |
| if (selection.getEnd() == 0) return; |
| int store = 0; |
| int places = 1; |
| int count = 0; |
| int min = Math.min(8,selection.getEnd()); |
| for (int i = 0; i < min; ++i) { |
| char ch = contents.charAt(selection.getEnd()-1-i); |
| int value = Character.getNumericValue(ch); |
| if (value < 0 || value > 15) break; |
| store += places * value; |
| ++count; |
| places *= 16; |
| } |
| String add = ""; |
| int bottom = store & 0xFFFF; |
| if (store >= 0xD8000000 && store < 0xDC000000 |
| && bottom >= 0xDC00 && bottom < 0xE000) { // surrogates |
| add = "" + (char)(store >> 16) + (char)bottom; |
| } else if (store > 0xFFFF && store <= 0x10FFFF) { |
| store -= 0x10000; |
| add = "" + (char)(((store >> 10) & 0x3FF) + 0xD800) |
| + (char)((store & 0x3FF) + 0xDC00); |
| |
| } else if (count >= 4) { |
| count = 4; |
| add = ""+(char)(store & 0xFFFF); |
| } else { |
| count = 1; |
| char ch = contents.charAt(selection.getEnd()-1); |
| add = hex(ch); |
| if (ch >= 0xDC00 && ch <= 0xDFFF && selection.getEnd() > 1) { |
| ch = contents.charAt(selection.getEnd()-2); |
| if (ch >= 0xD800 && ch <= 0xDBFF) { |
| count = 2; |
| add = hex(ch) + add; |
| } |
| } |
| } |
| replaceRange(add, selection.getEnd()-count, selection.getEnd()); |
| } |
| |
| public static String hex(char ch) { |
| String result = Integer.toString(ch,16).toUpperCase(); |
| result = "0000".substring(result.length(),4) + result; |
| return result; |
| } |
| } |