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.AWTEventMulticaster;
025: import java.awt.Color;
026: import java.awt.Cursor;
027: import java.awt.Dimension;
028: import java.awt.Graphics;
029: import java.awt.Point;
030: import java.awt.Rectangle;
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: import java.awt.event.TextEvent;
036: import java.awt.event.TextListener;
037:
038: /** <code>WingTextField</code> is a component that allows the editing
039: * of a single line of text.
040: * <br>
041: * <b>This class is thread safe.</b>
042: **/
043: public class WingTextField extends WingComponent {
044: protected Style stFocused;
045: protected Style stReadonly;
046: protected Style stSelected;
047: protected Color caretColor;
048: protected int caretWidth;
049:
050: private int cols;
051: private int alignment;
052: private String text;
053: private int[] textWidth;
054: private String oldText;
055:
056: private boolean caretBlink;
057: private boolean holdBlink;
058: private int caretPos;
059: private int caretX;
060: private int selectEnd;
061: private/*final*/WingTimer caretTimer;
062: private int dx;
063: private String echo;
064: protected boolean readonly;
065: protected boolean focused;
066: private TextListener textListener;
067:
068: /**
069: * Constructs a new text field.
070: */
071: public WingTextField() {
072: this (10);
073: }
074:
075: /**
076: * Constructs a new empty text field with the specified number
077: * of columns.
078: * @param cols the number of columns
079: */
080: public WingTextField(int cols) {
081: this (null, cols);
082: }
083:
084: /**
085: * Constructs a new text field with the specified custom style ID
086: * initialized with the specified text
087: * and wide enough to hold the specified
088: * number of columns.
089: * @param styleId custom style ID
090: * @param text the text to be displayed. If
091: * <code>text</code> is <code>null</code>, the empty
092: * string <code>""</code> will be displayed.
093: * @param cols the number of columns.
094: */
095: public WingTextField(String text, int cols) {
096: this .cols = cols;
097: this .alignment = LEFT;
098: caretTimer = new WingTimer(500, true, this );
099:
100: setWingFocusable(true);
101: enableEvents(AWTEvent.FOCUS_EVENT_MASK);
102:
103: setEditable(true);
104: setText(text);
105: }
106:
107: /**
108: * Loads skin resources.<br>
109: * <pre>
110: * styles:
111: * [optional styleID.]textfield.doc.normal
112: * [optional styleID.]textfield.doc.selected
113: * [optional styleID.]textfield.doc.focused
114: * [optional styleID.]textfield.doc.disabled
115: * [optional styleID.]textfield.doc.readonly
116: * extra properties:
117: * [optional styleID.]textfield.caret.color
118: * [optional styleID.]textfield.caret.width
119: * </pre>
120: * @see Style
121: * @see WingSkin
122: */
123: public void loadSkin() {
124: stNormal = WingSkin.getStyle(styleId, "textfield",
125: DOC | NORMAL, null);
126: stSelected = WingSkin.getStyle(styleId, "textfield",
127: DOC | SELECTED, stNormal).merge(stTop);
128: stFocused = WingSkin.getStyle(styleId, "textfield",
129: DOC | FOCUSED, stNormal).merge(stTop);
130: stDisabled = WingSkin.getStyle(styleId, "textfield",
131: DOC | DISABLED, stNormal).merge(stTop);
132: stReadonly = WingSkin.getStyle(styleId, "textfield",
133: DOC | READONLY, stNormal).merge(stTop);
134: stNormal = stNormal.merge(stTop);
135: caretColor = WingSkin.getColor(styleId, "textfield.caret");
136: caretWidth = WingSkin.getInteger(styleId,
137: "textfield.caret.width", 2);
138:
139: textWidth = null; //force relayout
140: setText(text);
141: }
142:
143: /**
144: * Adds the specified text event listener to receive text events
145: * from this text component.
146: * If <code>l</code> is <code>null</code>, no exception is
147: * thrown and no action is performed.
148: * @param l the text event listener
149: */
150: public void addTextListener(TextListener l) {
151: textListener = AWTEventMulticaster.add(textListener, l);
152: }
153:
154: /**
155: * Sets the echo characters for this text field.
156: * <p>
157: * An echo is useful for text fields where
158: * user input should not be echoed to the screen, as in
159: * the case of a text field for entering a password.
160: * Setting <code>echo</code> = <code>null</code> allows
161: * user input to be echoed to the screen again.
162: * @param echo the echo characters for this text field.
163: */
164: public synchronized void setEcho(String echo) {
165: this .echo = echo;
166: textWidth = null; //force relayout
167: setText(text);
168: }
169:
170: /**
171: * Sets the number of columns in this text field.
172: * @param columns the number of columns.
173: */
174: public void setColumns(int columns) {
175: this .cols = columns;
176: revalidateAndRepaint();
177: }
178:
179: /**
180: * Sets the flag that determines whether or not this
181: * text field is editable.
182: * @param b a flag indicating whether this text field
183: * is user editable.
184: */
185: public void setEditable(boolean b) {
186: readonly = !b;
187: setCursor(Cursor
188: .getPredefinedCursor((isEnabled() && !readonly) ? Cursor.TEXT_CURSOR
189: : Cursor.DEFAULT_CURSOR));
190: repaint();
191: }
192:
193: /**
194: * Sets the horizontal alignment of the text.
195: * Valid keys are:
196: * <ul>
197: * <li><code>WingTextField.LEFT</code>
198: * <li><code>WingTextField.RIGHT</code>
199: * </ul>
200: * @param alignment the alignment
201: * @since WingS 1.1.1
202: */
203: public synchronized void setHorizontalAlignment(int alignment) {
204: this .alignment = alignment;
205: repaintVisible();
206: }
207:
208: /**
209: * Sets the text that is presented by this
210: * text filed to be the specified text.
211: * @param text the new text.
212: */
213: public synchronized void setText(String text) {
214: if (text == null)
215: text = "";
216:
217: if (text.equals(this .text) && textWidth != null)
218: return;
219:
220: this .text = text;
221: this .oldText = text;
222:
223: WingFont f = getWingFont();
224: if (f != null) {
225: if (textWidth == null || textWidth.length < text.length())
226: textWidth = new int[text.length()];
227: if (echo == null) {
228: char[] ca = text.toCharArray();
229: for (int i = 0; i < text.length(); i++) {
230: textWidth[i] = f.charsWidth(ca, i, 1);
231: }
232: } else {
233: int ew = f.stringWidth(echo);
234: for (int i = 0; i < text.length(); i++) {
235: textWidth[i] = ew;
236: }
237: }
238: }
239: setCaretPos((alignment == LEFT) ? 0 : text.length(), false);
240: repaint();
241: }
242:
243: /**
244: * Returns the text that is presented by this text field.
245: * By default, this is an empty string.
246: *
247: * @return the value of this <code>WingTextComponent</code>
248: */
249: public String getText() {
250: return text;
251: }
252:
253: /**
254: * Selects all the text in this text component.
255: */
256: public synchronized void selectAll() {
257: selectEnd = 0;
258: setCaretPos(text.length(), true);
259: }
260:
261: /**
262: * Replaces the currently selected content with new content
263: * represented by the given string. If there is no selection
264: * this amounts to an insert of the given text. If there
265: * is no replacement text this amounts to a removal of the
266: * current selection.
267: * @param text the content to replace the selection with
268: */
269: public synchronized void replaceSelection(String text) {
270: int start = (caretPos < selectEnd) ? caretPos : selectEnd;
271: int end = (caretPos < selectEnd) ? selectEnd : caretPos;
272: if (start < end)
273: remove(start, end);
274: insert(start, text);
275: }
276:
277: /**
278: * Inserts the specified text at the specified position
279: * in this text area.
280: * This method allows negative position values.
281: * The text with negative position is inserted at <code>getText().length()+pos+1</code>
282: * which means at the end of the field for pos = -1
283: *
284: * @param text the text to insert
285: * @param pos the position at which to insert
286: */
287: public synchronized void insert(int pos, String text) {
288: if (text != null) {
289: int ttlength = this .text.length();
290: if (pos < 0) {
291: pos = ttlength + pos + 1;
292: if (pos < 0)
293: pos = 0;
294: }
295: StringBuffer b = new StringBuffer(ttlength);
296: if (pos > 0)
297: b.append(this .text.substring(0, pos));
298: b.append(text);
299: if (pos < ttlength)
300: b.append(this .text.substring(pos));
301: setText(b.toString());
302: setCaretPos(pos + text.length(), false);
303: }
304: }
305:
306: /**
307: * Removes text between the specified start and end positions
308: * @param start the start position
309: * @param end the end position
310: */
311: public synchronized void remove(int start, int end) {
312: if (start < end) {
313: StringBuffer b = new StringBuffer(text.length());
314: if (start > 0)
315: b.append(text.substring(0, start));
316: if (end < text.length())
317: b.append(text.substring(end));
318: setText(b.toString());
319: setCaretPos(start, false);
320: }
321: }
322:
323: /**
324: * Returns the selected text from the text that is
325: * presented by this text field.
326: * @return the selected text of this text field
327: */
328: public synchronized String getSelectedText() {
329: int start = (caretPos < selectEnd) ? caretPos : selectEnd;
330: int end = (caretPos < selectEnd) ? selectEnd : caretPos;
331: return text.substring(start, end);
332: }
333:
334: /**
335: * Transfers the contents of the system clipboard into the
336: * text field.
337: */
338: public void paste() {
339: replaceSelection(getClipboardText());
340: }
341:
342: /**
343: * Transfers the currently selected range in the
344: * text field to the system clipboard, leaving the contents
345: * in the text field.
346: */
347: public void copy() {
348: setClipboardText(getSelectedText());
349: }
350:
351: /**
352: * Transfers the currently selected range in the
353: * text field to the system clipboard, removing the contents
354: * from the text field.
355: */
356: public void cut() {
357: copy();
358: replaceSelection(null);
359: }
360:
361: private synchronized void setCaretPos(int pos, boolean dragSelection) {
362: if (pos > text.length() || pos < 0)
363: return;
364: caretPos = pos;
365: if (!dragSelection)
366: selectEnd = pos;
367: Style st = getStyle();
368: if (st == null)
369: return;
370: caretX = st.margin.left;
371: if (textWidth != null) {
372: int i;
373: int viewEnd = getSize().width - st.margin.right;
374: for (i = 0; i < caretPos; i++) {
375: caretX += textWidth[i];
376: }
377: if (alignment == RIGHT) {
378: int textEnd = caretX;
379: for (; i < text.length(); i++) {
380: textEnd += textWidth[i];
381: }
382: if (viewEnd > dx + textEnd)
383: dx = viewEnd - textEnd;
384: if (pos == text.length())
385: caretX -= caretWidth / 2;
386: }
387: if (pos > 0)
388: caretX -= caretWidth / 2;
389:
390: if (dx + caretX < st.margin.left) {
391: dx = st.margin.left - caretX;
392: } else {
393: if (dx + caretX + caretWidth >= viewEnd) {
394: dx = viewEnd - (caretX + caretWidth);
395: }
396: }
397: }
398: repaint();
399: }
400:
401: public void doLayout() {
402: setCaretPos(caretPos, true);
403: }
404:
405: public Dimension getPreferredSize() {
406: Dimension prefSize = wingPrefSize;
407: if (prefSize == null) {
408: if (cols == 0)
409: cols = text.length();
410:
411: int columns = cols;
412: if (columns == 0)
413: columns = 10;
414:
415: prefSize = new Dimension();
416: WingFont f = getWingFont();
417: prefSize.height = f.getAscent() + f.getDescent()
418: + stNormal.margin.top + stNormal.margin.bottom;
419: prefSize.width = columns * f.stringWidth("0")
420: + stNormal.margin.left + stNormal.margin.right;
421: wingPrefSize = prefSize;
422: }
423: return prefSize;
424: }
425:
426: /**
427: * @see com.javujavu.javux.wings.WingComponent#getStyle()
428: */
429: public Style getStyle() {
430: return (!isEnabled()) ? stDisabled : (readonly) ? stReadonly
431: : (focused) ? stFocused : stNormal;
432: }
433:
434: /**
435: * @see com.javujavu.javux.wings.WingComponent#wingPaint(java.awt.Graphics)
436: */
437: public void wingPaint(Graphics g) {
438: getPreferredSize();
439: Dimension d = getSize();
440: Style st = getStyle();
441: WingFont font = getWingFont();
442: int dy = 0;
443: int mtop = st.margin.top;
444: int mbottom = st.margin.bottom;
445: int dm = getPreferredSize().height - d.height;
446: if (dm > 0) {
447: mtop -= dm / 2;
448: mbottom -= (dm - dm / 2);
449: if (mtop < 0)
450: mtop = 0;
451: if (mbottom < 0)
452: mbottom = 0;
453: if (mtop + mbottom == 0) {
454: dy = -((dm - st.margin.top - st.margin.bottom) / 2);
455: }
456: }
457: Rectangle tb = new Rectangle(st.margin.left, mtop, d.width
458: - st.margin.left - st.margin.right, d.height - mtop
459: - mbottom);
460: Rectangle cb = g.getClipBounds();
461: if (cb != null)
462: cb = cb.intersection(tb);
463: else
464: cb = tb;
465: g.setClip(cb);
466: int so, se, dx, cx;
467: int[] w;
468: char[] chs;
469: String echo;
470: synchronized (this ) {
471: if (textWidth == null)
472: setText(text);
473: if (caretPos > selectEnd) {
474: so = selectEnd;
475: se = caretPos;
476: } else {
477: se = selectEnd;
478: so = caretPos;
479: }
480: w = textWidth;
481: chs = text.toCharArray();
482: dx = this .dx;
483: cx = this .caretX;
484: echo = this .echo;
485: }
486: int x, xe, y, h;
487: x = dx + st.margin.left;
488: xe = d.width - st.margin.right;
489: y = dy + mtop + font.getAscent();
490: h = font.getAscent() + font.getDescent();
491: if (xe > cb.x + cb.width)
492: xe = cb.x + cb.width;
493: g.setColor(getForeground());
494: boolean first = true;
495: for (int i = 0; i < chs.length && x <= xe; i++) {
496: if (x + w[i] >= cb.x) {
497: if (i >= so && i < se && isEnabled()) {
498: g.setColor(stSelected.background);
499: g.fillRect(x, mtop, w[i], h);
500: g
501: .setColor((stSelected.foreground != null) ? stSelected.foreground
502: : getForeground());
503: } else
504: g.setColor(getForeground());
505: if (echo == null) {
506: font.drawChars(g, chs, i, 1, x, y, first);
507: first = false;
508: } else
509: font.drawString(g, echo, x, y);
510: }
511: x += w[i];
512: }
513:
514: if ((caretBlink || holdBlink) && !readonly && isEnabled()) {
515: g.setColor(caretColor);
516: g.fillRect(dx + cx, mtop, caretWidth, h);
517: }
518: }
519:
520: protected void processFocusEvent(FocusEvent e) {
521: int id = e.getID();
522: focused = (id == FocusEvent.FOCUS_GAINED);
523: repaint();
524: if (readonly)
525: return;
526:
527: holdBlink = false;
528: if (id == FocusEvent.FOCUS_GAINED) {
529: caretBlink = true;
530: repaintCaret();
531: caretTimer.start();
532: } else if (id == FocusEvent.FOCUS_LOST) {
533: caretTimer.stop();
534: caretBlink = holdBlink = false;
535: repaintCaret();
536:
537: if (!e.isTemporary() && !text.equals(oldText)) {
538: wingProcessActionEvent(new ActionEvent(this ,
539: ActionEvent.ACTION_PERFORMED, "edited"));
540: // postOnEventThread(new ActionEvent(this,
541: // ActionEvent.ACTION_PERFORMED, "edited"));
542: }
543: oldText = text;
544: }
545: super .processFocusEvent(e);
546: }
547:
548: protected void wingProcessKeyEvent(KeyEvent e,
549: WingComponent redirecting) {
550: int id = e.getID();
551: int mod = e.getModifiers();
552: boolean ctrl = (mod == KeyEvent.CTRL_MASK), shift = (mod == KeyEvent.SHIFT_MASK), normal = (mod == 0);
553: int vk = e.getKeyCode();
554:
555: String oldText = this .text;
556: String veryOld = this .oldText;
557: if ((id == KeyEvent.KEY_PRESSED)) {
558: holdBlink = true;
559:
560: boolean consume = true;
561: if (vk >= KeyEvent.VK_PAGE_UP && vk <= KeyEvent.VK_DOWN
562: && (normal || shift)) {
563: if (vk == KeyEvent.VK_END) {
564: setCaretPos(text.length(), shift);
565: } else if (vk == KeyEvent.VK_HOME) {
566: setCaretPos(0, shift);
567: } else if (vk == KeyEvent.VK_LEFT) {
568: setCaretPos(caretPos - 1, shift);
569: } else if (vk == KeyEvent.VK_RIGHT) {
570: setCaretPos(caretPos + 1, shift);
571: } else
572: consume = false;
573: } else if ((vk == KeyEvent.VK_X && ctrl)
574: || (vk == KeyEvent.VK_DELETE && shift)) {
575: if (echo != null)
576: replaceSelection(null);
577: else if (!readonly)
578: cut();
579: else
580: copy();
581: } else if ((vk == KeyEvent.VK_C && ctrl)
582: || (vk == KeyEvent.VK_INSERT && ctrl)) {
583: if (echo == null)
584: copy();
585: } else if ((vk == KeyEvent.VK_V && ctrl)
586: || (vk == KeyEvent.VK_INSERT && shift)) {
587: if (!readonly)
588: paste();
589: } else if (vk == KeyEvent.VK_A && ctrl) {
590: selectAll();
591: } else if (vk == KeyEvent.VK_BACK_SPACE && normal
592: && !readonly) {
593: if (caretPos != selectEnd)
594: replaceSelection(null);
595: else
596: remove(caretPos - 1, caretPos);
597: } else if (vk == KeyEvent.VK_DELETE && normal && !readonly) {
598: if (caretPos != selectEnd)
599: replaceSelection(null);
600: else
601: remove(caretPos, caretPos + 1);
602: } else
603: consume = false;
604: if (consume)
605: e.consume();
606: } else if (id == KeyEvent.KEY_TYPED && !readonly) {
607: char c = e.getKeyChar();
608: // if((c>=0x20) && (c!=0x7F)) swing's version
609: if (!Character.isISOControl(c)) {
610: replaceSelection(String.valueOf(c));
611: e.consume();
612: }
613: } else if (id == KeyEvent.KEY_RELEASED) {
614: holdBlink = false;
615: }
616: this .oldText = veryOld;
617: TextListener l = textListener;
618: if (l != null && !oldText.equals(text)) {
619: l.textValueChanged(new TextEvent(this ,
620: TextEvent.TEXT_VALUE_CHANGED));
621: }
622: if (!e.isControlDown() && !e.isActionKey() && vk != 0xC0
623: && vk != KeyEvent.VK_TAB && vk != KeyEvent.VK_ENTER
624: && vk != KeyEvent.VK_ESCAPE) {
625: e.consume();
626: }
627: super .wingProcessKeyEvent(e, redirecting);
628: }
629:
630: protected void wingProcessMouseEvent(MouseEvent e) {
631: int id = e.getID();
632: if (id == MouseEvent.MOUSE_PRESSED) {
633: setCaretPos(posFromPoint(e.getPoint()), false);
634: // if(wingFocusable && !focused) wingRequestFocusInWindow();
635: if (wingFocusable && !focused)
636: requestFocus();
637: holdBlink = true;
638: } else if (id == MouseEvent.MOUSE_RELEASED) {
639: holdBlink = false;
640: } else if (id == MouseEvent.MOUSE_CLICKED
641: && e.getClickCount() > 1) {
642: selectAll();
643: } else if (id == MouseEvent.MOUSE_DRAGGED) {
644: setCaretPos(posFromPoint(e.getPoint()), true);
645: }
646: }
647:
648: private synchronized int posFromPoint(Point p) {
649: if (textWidth == null)
650: return -1;
651: int i, x = p.x - dx - getStyle().margin.left;
652: for (i = 0; i < text.length(); i++) {
653: if (x <= (textWidth[i] / 2))
654: break;
655: x -= textWidth[i];
656: }
657: return i;
658: }
659:
660: private synchronized void repaintCaret() {
661: repaint(dx + caretX, 0, caretWidth, getSize().height);
662: }
663:
664: public void wingProcessActionEvent(ActionEvent e) {
665: if (e.getSource() == caretTimer) {
666: caretBlink = !caretBlink;
667: repaintCaret();
668: } else
669: super.wingProcessActionEvent(e);
670: }
671: }
|