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