xref: /titanic_41/usr/src/cmd/krb5/kadmin/gui/visualrt/sunsoft/jws/visual/rt/awt/EditLine.java (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright (c) 2000 by Sun Microsystems, Inc.
26  * All rights reserved.
27  */
28 
29 /**
30  * Copyright 1996 Active Software Inc.
31  *
32  * @version @(#)EditLine.java 1.13 97/06/18
33  */
34 
35 
36 package sunsoft.jws.visual.rt.awt;
37 
38 import sunsoft.jws.visual.rt.base.Global;
39 import java.awt.*;
40 
41 /**
42  * An EditLine allows for the editing of text within some other component.
43  */
44 
45 public class EditLine implements Runnable
46 {
47     public static final int REPAINT = 87000;
48     public static final int APPLY = 87001;
49     public static final int CANCEL = 87002;
50 
51     public static final int BACKSPACE_KEY = 8;
52     public static final int TAB_KEY = 9;
53     public static final int RETURN_KEY = 10;
54     public static final int ESCAPE_KEY = 27;
55     public static final int DELETE_KEY = 127;
56 
57     private Component comp;
58     private String text;
59     private int textX, textY;
60     private Color fg, bg;
61     private Font font;
62     private FontMetrics metrics;
63 
64     private int x, y, w, h;
65     private int initWidth;
66     private String initText;
67 
68     private boolean justStartedEditing;
69     private boolean cancelApply;
70     private boolean applying;
71 
72     private int scrollPos;
73     private int cursorPos;
74     private int selectPos;
75     private boolean dragging;
76     private Frame frame;
77 
78     private boolean cursorState;
79     private boolean paintCursor;
80     private Thread cursorThread;
81     private long cursorTime;
82     private static final long CURSOR_DELAY = 500;
83 
84     private static final int XOFF = 2;
85     private static final int MINWIDTH = 12;
86     static final int XPAD = 15;
87     static final int YPAD = 4;
88 
EditLine(Component comp, String text, int textX, int textY)89     public EditLine(Component comp, String text, int textX, int textY) {
90         if (text == null)
91             text = /* NOI18N */"";
92 
93         this.comp = comp;
94         this.text = text;
95         this.textX = textX;
96         this.textY = textY;
97         this.initText = text;
98 
99         selectPos = 0;
100         cursorPos = text.length();
101         scrollPos = 0;
102         justStartedEditing = true;
103 
104         cacheDims();
105         this.initWidth = w;
106         mouseMove(textX, textY);
107         comp.requestFocus();
108 
109         cursorThread = new Thread(this, /* NOI18N */"Edit Line Cursor");
110         cursorThread.setDaemon(true);
111         cursorTime = System.currentTimeMillis();
112         cursorState = true;
113         cursorThread.start();
114     }
115 
setText(String text)116     public void setText(String text) {
117         if (text == null)
118             text = /* NOI18N */"";
119 
120         this.text = text;
121         cursorPos = text.length();
122         selectPos = cursorPos;
123         scrollPos = 0;
124         cacheText();
125     }
126 
getText()127     public String getText() {
128         return text;
129     }
130 
setSelection(int start, int end)131     public void setSelection(int start, int end) {
132         int len = text.length();
133         start = Math.max(0, start);
134         start = Math.min(start, len);
135         end = Math.max(start, end);
136         end = Math.min(end, len);
137 
138         selectPos = start;
139         cursorPos = end;
140         makeVisible(cursorPos);
141         repaint();
142     }
143 
getSelection()144     public String getSelection() {
145         return text.substring(selectStart(), selectEnd());
146     }
147 
setFont(Font font)148     public void setFont(Font font) {
149         this.font = font;
150         cacheDims();
151         comp.repaint();
152     }
153 
getFont()154     public Font getFont() {
155         if (font == null)
156             return comp.getFont();
157         else
158             return font;
159     }
160 
setForeground(Color fg)161     public void setForeground(Color fg) {
162         this.fg = fg;
163     }
164 
getForeground()165     public Color getForeground() {
166         if (fg == null)
167             return comp.getForeground();
168         else
169             return fg;
170     }
171 
setBackground(Color bg)172     public void setBackground(Color bg) {
173         this.bg = bg;
174     }
175 
getBackground()176     public Color getBackground() {
177         if (bg == null)
178             return comp.getBackground();
179         else
180             return bg;
181     }
182 
applyChanges()183     public boolean applyChanges() {
184         if (!text.equals(initText)) {
185             applying = true;
186             cancelApply = false;
187             comp.postEvent(new Event(this, APPLY, text));
188             applying = false;
189             return !cancelApply;
190         } else {
191             comp.postEvent(new Event(this, CANCEL, null));
192             return true;
193         }
194     }
195 
cancelApply()196     public void cancelApply() {
197         cancelApply = true;
198     }
199 
cacheText()200     private void cacheText() {
201         int len = text.length();
202         if (selectPos > len)
203             selectPos = len;
204         if (cursorPos > len)
205             cursorPos = len;
206         if (scrollPos > len)
207             scrollPos = 0;
208 
209         Rectangle paintRect = cacheHorizontal();
210         makeVisible(cursorPos);
211         repaint(paintRect);
212     }
213 
cacheDims()214     private void cacheDims() {
215         metrics = comp.getFontMetrics(getFont());
216         cacheHorizontal();
217         cacheVertical();
218     }
219 
cacheHorizontal()220     private Rectangle cacheHorizontal() {
221         Dimension d = comp.size();
222         int prevLeft = x;
223         int prevRight = x+w;
224 
225         x = textX - XOFF;
226         w = metrics.stringWidth(text) + XPAD;
227         w = Math.max(w, MINWIDTH);
228         w = Math.max(w, initWidth);
229 
230         if (w > (d.width-x)) {
231             x = d.width - w;
232             if (x < 0) {
233                 x = 0;
234                 w = d.width;
235             }
236         }
237 
238         int left = x;
239         int right = x+w;
240 
241         if (left > prevLeft && right < prevRight)
242             return new Rectangle(prevLeft, y, prevRight-prevLeft, h);
243         else if (left > prevLeft)
244             return new Rectangle(prevLeft, y, left - prevLeft, h);
245         else if (right < prevRight)
246             return new Rectangle(right, y, prevRight - right, h);
247         else
248             return null;
249     }
250 
cacheVertical()251     private void cacheVertical() {
252         Dimension d = comp.size();
253         y = textY - (metrics.getAscent() + YPAD/2);
254         h = metrics.getHeight() + YPAD;
255 
256         if (h > (d.height-y)) {
257             y = d.height - h;
258             if (y < 0) {
259                 y = 0;
260                 h = d.height;
261             }
262         }
263     }
264 
handleEvent(Event evt)265     public boolean handleEvent(Event evt) {
266         if (evt.id == Event.MOUSE_DOWN) {
267             if (justStartedEditing && evt.clickCount == 2) {
268                 justStartedEditing = false;
269                 comp.postEvent(new Event(this, CANCEL, null));
270                 return false;
271             } else {
272                 justStartedEditing = false;
273             }
274         }
275 
276         boolean retval = false;
277 
278         switch (evt.id) {
279 	case Event.MOUSE_DOWN:
280             if (inside(evt.x, evt.y)) {
281                 mouseDown(evt.clickCount, evt.x, evt.y);
282                 retval = true;
283             }
284             break;
285 
286 	case Event.MOUSE_DRAG:
287             if (dragging) {
288                 mouseDrag(evt.x, evt.y);
289                 retval = true;
290             }
291             break;
292 
293 	case Event.MOUSE_UP:
294             if (dragging) {
295                 mouseUp(evt.x, evt.y);
296                 retval = true;
297             }
298             break;
299 
300 	case Event.LOST_FOCUS:
301             // Don't allow the focus to get away!
302             comp.requestFocus();
303             // Intentional lack of break
304 	case Event.GOT_FOCUS:
305             mouseMove(-1, -1);
306             retval = true;
307             break;
308 
309 	case Event.MOUSE_MOVE:
310             mouseMove(evt.x, evt.y);
311             retval = true;
312             break;
313 
314 	case Event.KEY_PRESS:
315 	case Event.KEY_ACTION:
316             keyPress(evt);
317             // intentional lack of break
318 	case Event.KEY_RELEASE:
319 	case Event.KEY_ACTION_RELEASE:
320             retval = true;
321             break;
322         }
323 
324         return retval;
325     }
326 
mouseDown(int clickCount, int evtX, int evtY)327     private void mouseDown(int clickCount, int evtX, int evtY) {
328         comp.requestFocus();
329 
330         switch (clickCount) {
331 	case 2:
332             selectWord(evtX);
333             break;
334 
335 	case 3:
336             selectLine();
337             break;
338 
339 	default:
340             dragging = true;
341             setCursorX(evtX);
342             break;
343         }
344     }
345 
mouseDrag(int evtX, int evtY)346     private void mouseDrag(int evtX, int evtY) {
347         adjustSelection(evtX);
348     }
349 
mouseUp(int evtX, int evtY)350     private void mouseUp(int evtX, int evtY) {
351         mouseDrag(evtX, evtY);
352         dragging = false;
353         mouseMove(evtX, evtY);
354         resetTimer();
355     }
356 
mouseMove(int evtX, int evtY)357     private void mouseMove(int evtX, int evtY) {
358         Frame f = getFrame();
359         if (f == null)
360             return;
361 
362         if (inside(evtX, evtY)) {
363             if (f.getCursorType() != Frame.TEXT_CURSOR)
364                 f.setCursor(Frame.TEXT_CURSOR);
365         } else {
366             if (f.getCursorType() != Frame.DEFAULT_CURSOR)
367                 f.setCursor(Frame.DEFAULT_CURSOR);
368         }
369     }
370 
keyPress(Event evt)371     private void keyPress(Event evt) {
372         boolean cacheText = false;
373         boolean repaint = false;
374 
375         int key = evt.key;
376 
377         if (key == RETURN_KEY) {
378             applyChanges();
379             return;
380         } else if (key == ESCAPE_KEY) {
381             comp.postEvent(new Event(this, CANCEL, null));
382             return;
383         }
384 
385         int len = text.length();
386 
387         switch (key) {
388 	case Event.HOME:
389             if (cursorPos != 0) {
390                 setCursorPos(0);
391                 repaint = true;
392             }
393             break;
394 
395 	case Event.END:
396             if (cursorPos != len) {
397                 setCursorPos(len);
398                 repaint = true;
399             }
400             break;
401 
402 	case Event.LEFT:
403             if (cursorPos != 0) {
404                 setCursorPos(cursorPos-1);
405                 repaint = true;
406             }
407             break;
408 
409 	case Event.RIGHT:
410             if (cursorPos != len) {
411                 setCursorPos(cursorPos+1);
412                 repaint = true;
413             }
414             break;
415 
416 	case BACKSPACE_KEY:
417             if (deleteSelection()) {
418                 cacheText = true;
419             } else if (cursorPos != 0) {
420                 text = text.substring(0, cursorPos-1) +
421 		    text.substring(cursorPos);
422                 cursorPos--;
423                 selectPos--;
424                 cacheText = true;
425             }
426             break;
427 
428 	case DELETE_KEY:
429             if (deleteSelection()) {
430                 cacheText = true;
431             } else if (cursorPos != len) {
432                 text = text.substring(0, cursorPos) +
433 		    text.substring(cursorPos+1);
434                 cacheText = true;
435             }
436             break;
437 
438 	default:
439             if ((evt.modifiers & ~Event.SHIFT_MASK) == 0 &&
440 		(key >= 32 && key <= 127)) {
441                 deleteSelection();
442                 text = text.substring(0, cursorPos) +
443 		    String.valueOf((char)key) +
444 		    text.substring(cursorPos);
445                 cursorPos++;
446                 selectPos++;
447                 cacheText = true;
448             }
449             break;
450         }
451 
452         if (cacheText)
453             cacheText();
454         else if (repaint)
455             repaint();
456     }
457 
deleteSelection()458     private boolean deleteSelection() {
459         if (selectPos != cursorPos) {
460             int start = selectStart();
461             int end = selectEnd();
462             text = text.substring(0, start) + text.substring(end);
463             setCursorPos(start);
464             return true;
465         } else {
466             return false;
467         }
468     }
469 
selectWord(int evtX)470     private void selectWord(int evtX) {
471         int pos = getCursorPos(evtX);
472         selectPos = getWordStart(pos);
473         cursorPos = getWordEnd(pos);
474         makeVisible(cursorPos);
475         repaint();
476     }
477 
getWordStart(int pos)478     private int getWordStart(int pos) {
479         int i;
480         boolean hitChar = false;
481 
482         for (i = (pos-1); i >= 0; i--) {
483             char c = text.charAt(i);
484             if (hitChar && Character.isSpace(c))
485                 break;
486             else if (!hitChar && !Character.isSpace(c))
487                 hitChar = true;
488         }
489 
490         return i+1;
491     }
492 
getWordEnd(int pos)493     private int getWordEnd(int pos) {
494         int i;
495         boolean hitSpace = false;
496         int len = text.length();
497         int start = Math.max(pos-1, 0);
498 
499         for (i = start; i < len; i++) {
500             char c = text.charAt(i);
501             if (hitSpace && !Character.isSpace(c))
502                 break;
503             else if (!hitSpace && Character.isSpace(c))
504                 hitSpace = true;
505         }
506 
507         return i;
508     }
509 
selectLine()510     private void selectLine() {
511         selectPos = 0;
512         cursorPos = text.length();
513         repaint();
514     }
515 
getFrame()516     private Frame getFrame() {
517         Component c = comp;
518         if (frame == null) {
519             while (c != null && !(c instanceof Frame))
520                 c = c.getParent();
521             frame = (Frame)c;
522         }
523         return frame;
524     }
525 
location()526     public Point location() {
527         return new Point(x, y);
528     }
529 
size()530     public Dimension size() {
531         return new Dimension(w, h);
532     }
533 
setCursorX(int evtX)534     private void setCursorX(int evtX) {
535         setCursorPos(getCursorPos(evtX));
536         repaint();
537     }
538 
setCursorPos(int pos)539     private void setCursorPos(int pos) {
540         cursorPos = pos;
541         selectPos = pos;
542         makeVisible(cursorPos);
543         resetTimer();
544     }
545 
adjustSelection(int evtX)546     private void adjustSelection(int evtX) {
547         int pos = getCursorPos(evtX);
548         if (cursorPos != pos) {
549             cursorPos = pos;
550             makeVisible(cursorPos);
551             repaint();
552         }
553     }
554 
makeVisible(int pos)555     private boolean makeVisible(int pos) {
556         if (pos < scrollPos) {
557             scrollPos = Math.max(pos-6, 0);
558             return true;
559         } else if (pos > scrollPos) {
560             int width = metrics.stringWidth(text.substring(
561 							   scrollPos, pos));
562             if (width >= (w-3)) {
563                 int old = scrollPos;
564                 scrollPos = getScrollPos(pos, w-40);
565                 return true;
566             }
567         }
568 
569         return false;
570     }
571 
resetTimer()572     private synchronized void resetTimer() {
573         cursorState = true;
574         cursorTime = System.currentTimeMillis();
575         notify();
576     }
577 
getCursorPos(int evtX)578     private int getCursorPos(int evtX) {
579         int len = text.length();
580         int beginW = metrics.stringWidth(text.substring(0, scrollPos));
581 
582         int xoff = evtX - x - XOFF + beginW;
583         return findCursorOffset(xoff, text, len/2, 0, len);
584     }
585 
findCursorOffset(int xoff, String str, int cur, int lower, int upper)586     private int findCursorOffset(int xoff, String str,
587 				 int cur, int lower, int upper) {
588         if (lower == upper) {
589             return lower;
590         } else if (lower == (upper-1)) {
591             int lw = metrics.stringWidth(str.substring(0, lower));
592             int uw = metrics.stringWidth(str.substring(0, upper));
593 
594             if ((xoff - lw) < (uw - xoff))
595                 return lower;
596             else
597                 return upper;
598         }
599 
600         int width = metrics.stringWidth(str.substring(0, cur));
601 
602         if (width > xoff)
603             return findCursorOffset(xoff, str, cur - (cur-lower)/2,
604 				    lower, cur);
605         else
606             return findCursorOffset(xoff, str, cur + (upper-cur)/2,
607 				    cur, upper);
608     }
609 
getScrollPos(int pos, int textW)610     private int getScrollPos(int pos, int textW) {
611         String str = text.substring(scrollPos);
612         int offset = pos - scrollPos;
613         if (offset <= 0)
614             return scrollPos;
615 
616         pos = findScrollOffset(textW, str, offset, offset/2, 0, offset);
617         return (scrollPos + pos);
618     }
619 
findScrollOffset(int textW, String str, int len, int cur, int lower, int upper)620     private int findScrollOffset(int textW, String str, int len,
621 				 int cur, int lower, int upper) {
622         if (lower == upper) {
623             return lower;
624         } else if (lower == (upper-1)) {
625             int lw = metrics.stringWidth(str.substring(lower, len));
626             int uw = metrics.stringWidth(str.substring(upper, len));
627             if ((lw-textW) < (textW-uw))
628                 return lower;
629             else
630                 return upper;
631         }
632 
633         int width = metrics.stringWidth(str.substring(cur, len));
634         if (width > textW)
635             return findScrollOffset(textW, str, len,
636 				    cur + (upper-cur)/2, cur, upper);
637         else
638             return findScrollOffset(textW, str, len,
639 				    cur - (cur-lower)/2, lower, cur);
640     }
641 
inside(int evtX, int evtY)642     private boolean inside(int evtX, int evtY) {
643         return (evtX >= x && evtX <= (x+w) &&
644 		evtY >= y && evtY <= (y+h));
645     }
646 
selectStart()647     private int selectStart() {
648         return Math.min(selectPos, cursorPos);
649     }
650 
selectEnd()651     private int selectEnd() {
652         return Math.max(selectPos, cursorPos);
653     }
654 
repaint()655     public void repaint() {
656         repaint(null);
657     }
658 
659     // If "rect" is non-null, then the component is expected to repaint
660     // the area define by the rectangle.  The coordinates
661     // in the rectangle
662     // are relative to the component's coordinate space.
repaint(Rectangle rect)663     public void repaint(Rectangle rect) {
664         comp.postEvent(new Event(this, REPAINT, rect));
665     }
666 
paint(Graphics g)667     public synchronized void paint(Graphics g) {
668         g = g.create();
669         g.translate(x, y);
670         g.setFont(getFont());
671 
672         if (paintCursor) {
673             if (cursorState)
674                 g.setColor(getForeground());
675             else
676                 g.setColor(getBackground());
677 
678             int xoff =
679 		metrics.stringWidth(text.substring(scrollPos,
680 						   cursorPos)) + XOFF;
681             g.fillRect(xoff, YPAD/2, 1, h-YPAD);
682 
683             paintCursor = false;
684             return;
685         }
686 
687         if (bg != null) {
688             g.setColor(getBackground());
689             g.fillRect(0, 0, w, h);
690         } else {
691             g.clearRect(0, 0, w, h);
692         }
693 
694         g.setColor(getForeground());
695         g.drawRect(0, 0, w-1, h-1);
696 
697         int xoff = XOFF;
698         int yoff = h - YPAD/2 - metrics.getDescent();
699         if (Global.isMotif())
700             yoff -= 1;
701 
702         int start = selectStart();
703         int end = selectEnd();
704 
705         if (start == end) {
706             String str = text.substring(scrollPos);
707             g.drawString(str, XOFF, yoff);
708 
709             if (cursorState) {
710                 xoff += metrics.stringWidth(text.substring(scrollPos,
711 							   cursorPos));
712                 g.fillRect(xoff, YPAD/2, 1, h-YPAD);
713             }
714         } else {
715 
716             // Draw first unselected segment
717             if (start > scrollPos) {
718                 g.drawString(text.substring(scrollPos, start),
719 			     xoff, yoff);
720                 xoff += metrics.stringWidth(text.substring(
721 							   scrollPos, start));
722             }
723 
724             // Draw selected segment
725             String selectStr = text.substring(Math.max(start,
726 						       scrollPos), end);
727             int selectW = metrics.stringWidth(selectStr);
728 
729             g.setColor(new Color(0, 0, 128));
730             g.fillRect(xoff, YPAD/2, selectW, h-YPAD);
731             g.setColor(Color.white);
732             g.drawString(selectStr, xoff, yoff);
733 
734             if (cursorState)
735                 g.setColor(getForeground());
736 
737             if (cursorPos == end)
738                 xoff += selectW;
739 
740             // Draw the cursor
741             g.fillRect(xoff, YPAD/2, 1, h-YPAD);
742 
743             if (cursorPos == start)
744                 xoff += selectW;
745 
746             if (!cursorState)
747                 g.setColor(getForeground());
748 
749             // Draw last unselected segment
750             int length = text.length();
751             if (end < length) {
752                 g.drawString(text.substring(end), xoff, yoff);
753             }
754         }
755     }
756 
run()757     public synchronized void run() {
758         long waitTime = CURSOR_DELAY;
759 
760         while (Thread.currentThread() == cursorThread) {
761             try {
762                 wait(waitTime);
763             }
764             catch (InterruptedException ex) {
765             }
766 
767             comp.requestFocus();
768 
769             if (dragging) {
770                 waitTime = 0;
771             } else {
772                 long diff = System.currentTimeMillis() - cursorTime;
773                 if (diff >= CURSOR_DELAY) {
774                     waitTime = CURSOR_DELAY;
775                     cursorState = !cursorState;
776                     cursorTime = System.currentTimeMillis();
777                     paintCursor = true;
778                     repaint();
779                 } else {
780                     waitTime = CURSOR_DELAY - diff;
781                 }
782             }
783         }
784     }
785 
destroy()786     public synchronized void destroy() {
787         cursorThread = null;
788         notify();
789 
790         Frame f = getFrame();
791         if (f != null && f.getCursorType() != Frame.DEFAULT_CURSOR)
792             f.setCursor(Frame.DEFAULT_CURSOR);
793     }
794 
finalize()795     protected void finalize() {
796         destroy();
797     }
798 }
799