001: /*
002: * Javu WingS - Lightweight Java Component Set
003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
004: * e-mail: ksadlocha@programics.com
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package com.javujavu.javux.wings;
022:
023: import java.awt.AWTEvent;
024: import java.awt.Color;
025: import java.awt.Cursor;
026: import java.awt.Dimension;
027: import java.awt.Graphics;
028: import java.awt.Point;
029: import java.awt.Rectangle;
030: import java.awt.Toolkit;
031: import java.awt.event.ActionEvent;
032: import java.awt.event.FocusEvent;
033: import java.awt.event.KeyEvent;
034: import java.awt.event.MouseEvent;
035:
036: import com.javujavu.javux.wings.text.BreakRowNode;
037: import com.javujavu.javux.wings.text.LayoutContext;
038: import com.javujavu.javux.wings.text.RootNode;
039: import com.javujavu.javux.wings.text.TextNode;
040: import com.javujavu.javux.wings.text.TextPaneNode;
041:
042: /**
043: * This class provide a multi-line region
044: * that displays and allows editing text.
045: * <br>
046: * <br>
047: * <b>This class is thread safe.</b>
048: **/
049: public class WingTextPane extends WingComponent {
050: public static final int TAB_WIDTH = 4;
051:
052: protected Style stFocused;
053: protected Style stReadonly;
054: protected Style stSelected;
055: protected Color caretColor;
056: protected int caretWidth;
057:
058: protected/*final*/RootNode root;
059: private static final int ROOT_OUTERS = 1;
060: private/*final*/WingTimer caretTimer;
061:
062: protected int wrap;
063: private int lastWidth;
064: private int lastViewWidth;
065: private boolean revalidate;
066:
067: private boolean caretBlink;
068: private boolean holdBlink;
069: private int caretPos;
070: private Rectangle caretRect;
071: private int selectEnd;
072: private boolean readonly;
073: private boolean focused;
074:
075: private int dragX = -1;
076:
077: /**
078: * NOTICE: This component will evolve to WingHtmlPane and WingTextArea.
079: */
080: public WingTextPane() {
081: lastWidth = -1;
082: root = new RootNode(this );
083: caretTimer = new WingTimer(500, true, this );
084: enableEvents(AWTEvent.FOCUS_EVENT_MASK
085: | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK
086: | AWTEvent.MOUSE_MOTION_EVENT_MASK);
087: setWingFocusable(true);
088: setEditable(true);
089: setWrap(LINE_WRAP);
090: }
091:
092: public void loadSkin() {
093: stNormal = WingSkin.getStyle(styleId, "textpane", DOC | NORMAL,
094: null);
095: stSelected = WingSkin.getStyle(styleId, "textpane",
096: DOC | SELECTED, stNormal).merge(stTop);
097: stFocused = WingSkin.getStyle(styleId, "textpane",
098: DOC | FOCUSED, stNormal).merge(stTop);
099: stDisabled = WingSkin.getStyle(styleId, "textpane",
100: DOC | DISABLED, stNormal).merge(stTop);
101: stReadonly = WingSkin.getStyle(styleId, "textpane",
102: DOC | READONLY, stNormal).merge(stTop);
103: stNormal = stNormal.merge(stTop);
104:
105: caretColor = WingSkin.getColor(styleId, "textpane.caret",
106: Color.black);
107: caretWidth = WingSkin.getInteger(styleId,
108: "textpane.caret.width", 2);
109: revalidate = true;
110: }
111:
112: public Style getStyle() {
113: return (!isEnabled()) ? stDisabled : (readonly) ? stReadonly
114: : (focused) ? stFocused : stNormal;
115: }
116:
117: public void setEditable(boolean editable) {
118: readonly = !editable;
119: setCursor(Cursor
120: .getPredefinedCursor((isEnabled() && !readonly) ? Cursor.TEXT_CURSOR
121: : Cursor.DEFAULT_CURSOR));
122: repaint();
123: }
124:
125: public void setWrap(int wrap) {
126: this .wrap = wrap;
127: revalidateAndRepaint();
128: }
129:
130: ////////////////////////////////////////////////////////////
131: // root accessors
132:
133: public int length() {
134: return root.length() - 2 * ROOT_OUTERS;
135: }
136:
137: public synchronized String getText(int start, int end) {
138: return root.getText(start + ROOT_OUTERS, end + ROOT_OUTERS);
139: }
140:
141: private void rootInsert(int pos, TextPaneNode node) {
142: if (node == null)
143: return;
144: root.insert(pos + ROOT_OUTERS, node);
145: }
146:
147: private void rootRemove(int start, int end) {
148: root.remove(start + ROOT_OUTERS, end + ROOT_OUTERS);
149: }
150:
151: public synchronized Rectangle getCharRect(int pos) {
152: return root.getCharBounds(pos + ROOT_OUTERS);
153: }
154:
155: public synchronized int posAtPoint(int x, int y) {
156: int r = root.posAtPoint(TextPaneNode.nearestX(root.getBounds(),
157: x), TextPaneNode.nearestY(root.getBounds(), y));
158: if (r >= ROOT_OUTERS)
159: r -= ROOT_OUTERS;
160: if (r > length())
161: r = length();
162: return r;
163: }
164:
165: private Style textStyleAt(int pos) {
166: TextPaneNode n = root.nodeAt(pos + ROOT_OUTERS, true);
167: if (n instanceof TextNode)
168: return n.style;
169: return null;
170: }
171:
172: //////////////////////////////////////////////////////////
173:
174: public synchronized String getText() {
175: return getText(0, length());
176: }
177:
178: public synchronized String getSelectedText() {
179: int start = (caretPos < selectEnd) ? caretPos : selectEnd;
180: int end = (caretPos < selectEnd) ? selectEnd : caretPos;
181: return getText(start, end);
182: }
183:
184: public void setText(String text) {
185: setText(text, null);
186: }
187:
188: public synchronized void setText(String text, Style style) {
189: rootRemove(0, length());
190: setCaretPos(0, false);
191: if (text != null && text.length() > 0) {
192: insert(0, text, style);
193: }
194: revalidateAndRepaint();
195: }
196:
197: public void append(String text) {
198: insert(-1, text);
199: }
200:
201: public void append(String text, Style style) {
202: insert(-1, text, style);
203: }
204:
205: public void append(TextPaneNode node) {
206: insert(-1, node);
207: }
208:
209: public synchronized void insert(int pos, String text) {
210: int oldLen = length();
211: if (pos < 0)
212: pos = oldLen + 1 + pos;
213: if (pos < 0 || pos > oldLen)
214: pos = oldLen;
215: insert(pos, text, textStyleAt(pos));
216: }
217:
218: /**
219: *
220: * @param pos if negative pos => pos= length()+1+pos
221: * insert(-1,...) equals to append(...)
222: * @param text
223: * @param style
224: */
225: public synchronized void insert(int pos, String text, Style style) {
226: if (text == null)
227: return;
228:
229: int oldLen = length();
230: if (pos < 0)
231: pos = oldLen + 1 + pos;
232: if (pos < 0 || pos > oldLen)
233: pos = oldLen;
234:
235: StringBuffer r = new StringBuffer();
236: char c;
237: int p = pos;
238: for (int i = 0; i < text.length(); i++) {
239: c = text.charAt(i);
240: if (c == '\t') {
241: for (int j = 0; j < TAB_WIDTH; j++)
242: r.append(' ');
243: } else if (c == '\r')
244: ;
245: else if (c == '\n') {
246: if (r.length() > 0) {
247: TextNode ts = new TextNode(r.toString(), style);
248: rootInsert(p, ts);
249: p += r.length();
250: r.setLength(0);
251: }
252: BreakRowNode br = new BreakRowNode(style);
253: rootInsert(p, br);
254: p += br.length();
255: } else
256: r.append(c);
257: }
258: if (r.length() > 0) {
259: TextNode ts = new TextNode(r.toString(), style);
260: rootInsert(p, ts);
261: p += r.length();
262: }
263: if (caretPos >= pos) {
264: setCaretPos(caretPos + length() - oldLen, false);
265: }
266: revalidateAndRepaint();
267: }
268:
269: public synchronized void insert(int pos, TextPaneNode node) {
270: int oldLen = length();
271: if (pos < 0)
272: pos = oldLen + 1 + pos;
273: if (pos < 0 || pos > oldLen)
274: pos = oldLen;
275:
276: rootInsert(pos, node);
277: if (caretPos >= pos) {
278: setCaretPos(caretPos + length() - oldLen, false);
279: }
280: revalidateAndRepaint();
281: }
282:
283: // public synchronized void replace(int start, int end, String text, Style style)
284: // {
285: // if(start<end) rootRemove(start, end);
286: // int len= length();
287: // insert(start, text, style);
288: // setCaretPos(start+length()-len, false);
289: // revalidateAndRepaint();
290: // }
291: public synchronized void replaceSelection(String text) {
292: replaceSelection(text,
293: textStyleAt((caretPos < selectEnd) ? caretPos
294: : selectEnd));
295: }
296:
297: public synchronized void replaceSelection(String text, Style style) {
298: int start = (caretPos < selectEnd) ? caretPos : selectEnd;
299: int end = (caretPos < selectEnd) ? selectEnd : caretPos;
300: if (start < end)
301: rootRemove(start, end);
302: if (caretPos != start)
303: setCaretPos(start, false);
304: insert(start, text, style);
305: revalidateAndRepaint();
306: }
307:
308: public synchronized void replaceSelection(TextPaneNode node) {
309: int start = (caretPos < selectEnd) ? caretPos : selectEnd;
310: int end = (caretPos < selectEnd) ? selectEnd : caretPos;
311: if (start < end)
312: rootRemove(start, end);
313: if (caretPos != start)
314: setCaretPos(start, false);
315: insert(start, node);
316: revalidateAndRepaint();
317: }
318:
319: public synchronized void remove(int start, int end) {
320: if (start < 0 || start >= end || end > length())
321: return;
322: rootRemove(start, end);
323: if (caretPos > start && caretPos <= end)
324: setCaretPos(start, false);
325: else if (caretPos > end)
326: setCaretPos(caretPos - (end - start), false);
327: revalidateAndRepaint();
328: }
329:
330: public synchronized void selectAll() {
331: select(0, length());
332: }
333:
334: public synchronized void select(int start, int end) {
335: setCaretPos(start, false);
336: setCaretPos(end, true);
337: }
338:
339: public void paste() {
340: replaceSelection(getClipboardText());
341: }
342:
343: public void copy() {
344: setClipboardText(getSelectedText());
345: }
346:
347: public void cut() {
348: copy();
349: replaceSelection((String) null);
350: }
351:
352: ///////////////////////////////////////////////////////////
353:
354: public void getScrollIncrements(Point unit, Point block) {
355: super .getScrollIncrements(unit, block);
356: WingFont f = getWingFont();
357: unit.x = f.stringWidth("O");
358: unit.y = f.getHeight();
359: }
360:
361: public Dimension getPreferredSize() {
362: return checkPreferredSize(false);
363: }
364:
365: /**
366: * <br><br><strong>This method acquire TreeLock</strong>
367: */
368: public void doLayout() {
369: checkPreferredSize(true);
370: // scrollToCaret();
371: }
372:
373: private Dimension checkPreferredSize(boolean fixed) {
374: Dimension viewSize = getViewOrSize();
375: Dimension size = getSize();
376: Dimension prefSize = wingPrefSize;
377: if (prefSize == null || !root.isValid() || revalidate
378: || lastWidth != size.width
379: || (lastViewWidth != viewSize.width)) {
380: lastWidth = size.width;
381: lastViewWidth = viewSize.width;
382:
383: int prefWidth, wrap = this .wrap;
384: if (fixed || wrap == NO_WRAP)
385: prefWidth = size.width;
386: else
387: prefWidth = viewSize.width;
388: if (prefWidth == 0)
389: prefWidth = Toolkit.getDefaultToolkit().getScreenSize().width / 2;
390:
391: LayoutContext lc = new LayoutContext(prefWidth, 0, 0, LEFT,
392: fixed /*|| layoutAgain*/, wrap, revalidate);
393: synchronized (this ) {
394: root.layout(lc);
395: Dimension ps = root.getBounds().getSize();
396: if (!wingPrefSizeSet || prefSize == null)
397: wingPrefSize = prefSize = ps;
398: caretRect = null;
399: }
400:
401: if (prefSize.width != lastWidth && !fixed)
402: lastWidth = prefSize.width;
403: revalidate = false;
404: }
405: return prefSize;
406: }
407:
408: public synchronized void wingPaint(Graphics g) {
409: int selectionStart = (caretPos < selectEnd) ? caretPos
410: : selectEnd;
411: int selectionEnd = (caretPos < selectEnd) ? selectEnd
412: : caretPos;
413:
414: root.paint(g, 0, 0, selectionStart + ROOT_OUTERS, selectionEnd
415: + ROOT_OUTERS, stSelected);
416:
417: if (focused && (caretBlink || holdBlink)) {
418: Rectangle caretRect = getCaretRect();
419: g.setColor(caretColor);
420: g.fillRect(caretRect.x, caretRect.y, caretRect.width,
421: caretRect.height);
422: }
423: }
424:
425: /**
426: * <br><br><strong>This method acquire TreeLock</strong>
427: */
428: public void setCaretPosition(int pos) {
429: setCaretPos(pos, false);
430: scrollToCaret();
431: }
432:
433: private synchronized void setCaretPos(int position,
434: boolean dragSelection) {
435: boolean repaintAll;
436:
437: if (position < 0)
438: position = 0;
439: if (position > length())
440: position = length();
441: if (position == caretPos && position == selectEnd)
442: return;
443: repaintAll = (dragSelection || caretPos != selectEnd);
444:
445: if (!repaintAll)
446: repaintCaret();
447:
448: caretPos = position;
449: caretRect = null;
450: if (!dragSelection)
451: selectEnd = position;
452:
453: if (!repaintAll)
454: repaintCaret();
455: else
456: repaint();
457: }
458:
459: private synchronized Rectangle getCaretRect() {
460: if (caretRect == null) {
461: Rectangle cr = getCharRect(caretPos);
462: if (cr != null) {
463: cr.x -= caretWidth / 2;
464: cr.width = caretWidth;
465: }
466: caretRect = cr;
467: }
468: return caretRect;
469: }
470:
471: /**
472: * <br><br><strong>This method acquire TreeLock</strong>
473: */
474: public void scrollToCaret() {
475: synchronized (getTreeLock()) {
476: wingValidate();
477: Rectangle cr = getCaretRect();
478: if (cr != null)
479: scrollRectToVisible(cr);
480: }
481: }
482:
483: private void repaintCaret() {
484: Rectangle cr = getCaretRect();
485: if (cr != null)
486: repaint(cr.x, cr.y, cr.width, cr.height);
487: }
488:
489: public void wingProcessActionEvent(ActionEvent e) {
490: if (e.getSource() == caretTimer) {
491: caretBlink = !caretBlink;
492: repaintCaret();
493: } else
494: super .wingProcessActionEvent(e);
495: }
496:
497: protected void processFocusEvent(FocusEvent e) {
498: int id = e.getID();
499: focused = (id == FocusEvent.FOCUS_GAINED);
500: repaint();
501:
502: holdBlink = false;
503: if (id == FocusEvent.FOCUS_GAINED) {
504: caretBlink = true;
505: repaintCaret();
506: caretTimer.start();
507: } else if (id == FocusEvent.FOCUS_LOST) {
508: caretTimer.stop();
509: caretBlink = false;
510: repaintCaret();
511: }
512: }
513:
514: /**
515: * <br><br><strong>This method acquire TreeLock</strong>
516: */
517: protected void wingProcessMouseEvent(MouseEvent e) {
518: int id = e.getID();
519: if (id == MouseEvent.MOUSE_PRESSED) {
520: Point p = e.getPoint();
521: setCaretPos(posAtPoint(p.x, p.y), false);
522: scrollToCaret();
523: if (wingFocusable && !focused)
524: requestFocus(); //wingRequestFocusInWindow();
525: holdBlink = true;
526: } else if (id == MouseEvent.MOUSE_RELEASED) {
527: holdBlink = false;
528: } else if (id == MouseEvent.MOUSE_DRAGGED) {
529: Point p = e.getPoint();
530: setCaretPos(posAtPoint(p.x, p.y), true);
531: scrollToCaret();
532: }
533: }
534:
535: /**
536: * <br><br><strong>This method acquire TreeLock</strong>
537: */
538: protected void wingProcessKeyEvent(KeyEvent e,
539: WingComponent redirecting) {
540: int mod = e.getModifiers();
541: boolean ctrl = (mod == KeyEvent.CTRL_MASK), shift = (mod == KeyEvent.SHIFT_MASK), normal = (mod == 0);
542: int id = e.getID();
543: boolean consume = false;
544: int vk = e.getKeyCode();
545:
546: if (id == KeyEvent.KEY_PRESSED) {
547: holdBlink = true;
548:
549: consume = true;
550: if (vk >= KeyEvent.VK_PAGE_UP && vk <= KeyEvent.VK_DOWN
551: && (normal || shift)) {
552: synchronized (this ) {
553: Rectangle caretRect = getCaretRect();
554:
555: if (vk == KeyEvent.VK_END) {
556: setCaretPos(posAtPoint(getSize().width - 1,
557: caretRect.y), shift);
558: } else if (vk == KeyEvent.VK_HOME) {
559: setCaretPos(posAtPoint(0, caretRect.y), shift);
560: } else if (vk == KeyEvent.VK_LEFT) {
561: setCaretPos(caretPos - 1, shift);
562: } else if (vk == KeyEvent.VK_RIGHT) {
563: setCaretPos(caretPos + 1, shift);
564: } else {
565: if (dragX == -1)
566: dragX = caretRect.x;
567: if (vk == KeyEvent.VK_UP) {
568: for (int y = caretRect.y - 1; y >= 0; y--) {
569: int pos = posAtPoint(dragX, y);
570: Rectangle r = getCharRect(pos);
571: if (r.y + r.height <= caretRect.y) {
572: setCaretPos(pos, shift);
573: break;
574: }
575: }
576: } else if (vk == KeyEvent.VK_DOWN) {
577: int start = caretRect.y + caretRect.height, end = getSize().height, pos;
578: for (int y = start; y < end; y++) {
579: Rectangle r = getCharRect(pos = posAtPoint(
580: dragX, y));
581: if (r.y >= start) {
582: setCaretPos(pos, shift);
583: break;
584: }
585: }
586: } else if (vk == KeyEvent.VK_PAGE_UP) {
587: setCaretPos(posAtPoint(dragX, caretRect.y
588: - 1 - (getViewOrSize().height * 8)
589: / 10), shift);
590: } else if (vk == KeyEvent.VK_PAGE_DOWN) {
591: setCaretPos(
592: posAtPoint(
593: dragX,
594: caretRect.y
595: + caretRect.height
596: + (getViewOrSize().height * 8)
597: / 10), shift);
598: } else
599: consume = false;
600: }
601: }
602: } else if (vk == KeyEvent.VK_ENTER && normal && !readonly) {
603: replaceSelection("\n");
604: } else if ((vk == KeyEvent.VK_X && ctrl)
605: || (vk == KeyEvent.VK_DELETE && shift)) {
606: if (!readonly)
607: cut();
608: else
609: copy();
610: } else if ((vk == KeyEvent.VK_C && ctrl)
611: || (vk == KeyEvent.VK_INSERT && ctrl)) {
612: copy();
613: } else if ((vk == KeyEvent.VK_V && ctrl)
614: || (vk == KeyEvent.VK_INSERT && shift)) {
615: if (!readonly)
616: paste();
617: else
618: consume = false;
619: } else if (vk == KeyEvent.VK_BACK_SPACE && normal
620: && !readonly) {
621: if (caretPos != selectEnd)
622: replaceSelection((String) null);
623: else
624: remove(caretPos - 1, caretPos);
625: } else if (vk == KeyEvent.VK_DELETE && normal && !readonly) {
626: if (caretPos != selectEnd)
627: replaceSelection((String) null);
628: else
629: remove(caretPos, caretPos + 1);
630: } else if (vk == KeyEvent.VK_A && ctrl) {
631: selectAll();
632: e.consume();
633: consume = false;
634: } else
635: consume = false;
636: } else if (id == KeyEvent.KEY_TYPED && !readonly) {
637: char c = e.getKeyChar();
638: if (!Character.isISOControl(c)) {
639: replaceSelection(String.valueOf(c));
640: consume = true;
641: }
642: } else if (id == KeyEvent.KEY_RELEASED) {
643: holdBlink = false;
644: dragX = -1;
645: }
646: if (consume
647: || (!e.isControlDown() && !e.isActionKey()
648: && vk != 0xC0 && vk != KeyEvent.VK_TAB && vk != KeyEvent.VK_ESCAPE)) {
649: e.consume();
650: }
651: if (consume)
652: scrollToCaret();
653:
654: super.wingProcessKeyEvent(e, redirecting);
655: }
656: }
|