2 * (C) Copyright IBM Corp. 1998-2005. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
14 7/1/97 - caret blinks
\r
16 7/3/97 - fAnchor is no longer restricted to the start or end of the selection. {jbr}
\r
17 Also, removed fVisible - it was identical to enabled().
\r
20 package com.ibm.richtext.textpanel;
\r
22 import java.awt.Graphics;
\r
23 import java.awt.Color;
\r
24 import java.awt.Rectangle;
\r
26 import java.text.BreakIterator;
\r
28 import java.awt.event.MouseEvent;
\r
29 import java.awt.event.KeyEvent;
\r
30 import java.awt.event.FocusEvent;
\r
32 import com.ibm.richtext.styledtext.MConstText;
\r
33 import com.ibm.richtext.textformat.TextOffset;
\r
35 import com.ibm.richtext.textformat.MFormatter;
\r
37 class TextSelection extends Behavior implements Runnable {
\r
38 static final String COPYRIGHT =
\r
39 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
40 static final Color HIGHLIGHTCOLOR = Color.pink;
\r
42 private TextComponent fTextComponent;
\r
43 private MConstText fText;
\r
44 private TextOffset fStart;
\r
45 private TextOffset fLimit;
\r
46 private TextOffset fAnchor;
\r
47 private TextOffset fUpDownAnchor = null;
\r
48 private BreakIterator fBoundaries = null;
\r
49 private Color fHighlightColor = HIGHLIGHTCOLOR;
\r
50 private PanelEventBroadcaster fListener;
\r
51 private RunStrategy fRunStrategy;
\r
52 private boolean fMouseDown = false;
\r
53 private boolean fHandlingKeyOrCommand = false;
\r
55 private boolean fCaretShouldBlink;
\r
56 private boolean fCaretIsVisible;
\r
57 private int fCaretCount;
\r
59 // formerly in base class
\r
60 private boolean fEnabled;
\r
62 private MouseEvent fPendingMouseEvent = null;
\r
64 private static final int kCaretInterval = 500;
\r
68 final Runnable blinkCaret = new Runnable() {
\r
70 fCaretIsVisible = !fCaretIsVisible;
\r
71 Graphics g = fTextComponent.getGraphics();
\r
73 //System.out.println("caretIsVisible: " + fCaretIsVisible);
\r
74 drawSelection(g, fCaretIsVisible);
\r
77 // Not sure what else to do:
\r
78 fCaretShouldBlink = false;
\r
86 synchronized(this) {
\r
88 while (!fCaretShouldBlink) {
\r
92 catch(InterruptedException e) {
\r
93 System.out.println("Caught InterruptedException in caret thread.");
\r
99 if (fCaretCount % 2 == 0) {
\r
100 fRunStrategy.doIt(blinkCaret);
\r
105 Thread.sleep(kCaretInterval);
\r
107 catch(InterruptedException e) {
\r
114 public TextSelection(TextComponent textComponent,
\r
115 PanelEventBroadcaster listener,
\r
116 RunStrategy runStrategy) {
\r
118 fTextComponent = textComponent;
\r
119 fText = textComponent.getText();
\r
120 fListener = listener;
\r
121 fRunStrategy = runStrategy;
\r
123 fStart = new TextOffset();
\r
124 fLimit = new TextOffset();
\r
125 fAnchor = new TextOffset();
\r
126 fMouseDown = false;
\r
129 fCaretIsVisible = true;
\r
130 fCaretShouldBlink = false;
\r
133 Thread caretThread = new Thread(this);
\r
134 caretThread.setDaemon(true);
\r
135 caretThread.start();
\r
138 boolean enabled() {
\r
143 private void setEnabled(boolean enabled) {
\r
145 fEnabled = enabled;
\r
148 public boolean textControlEventOccurred(Behavior.EventType event, Object what) {
\r
151 fHandlingKeyOrCommand = true;
\r
153 if (event == Behavior.SELECT) {
\r
154 select((TextRange) what);
\r
157 else if (event == Behavior.COPY) {
\r
158 fTextComponent.getClipboard().setContents(fText.extract(fStart.fOffset, fLimit.fOffset));
\r
159 fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
\r
166 fHandlingKeyOrCommand = false;
\r
170 protected void advanceToNextBoundary(TextOffset offset) {
\r
172 // If there's no boundaries object, or if position at the end of the
\r
173 // document, return the offset unchanged
\r
174 if (fBoundaries == null) {
\r
178 int position = offset.fOffset;
\r
180 if (position >= fText.length()) {
\r
184 // If position is at a boundary and offset is before position,
\r
185 // leave it unchanged. Otherwise move to next boundary.
\r
186 int nextPos = fBoundaries.following(position);
\r
187 if (fBoundaries.previous() == position &&
\r
188 offset.fPlacement==TextOffset.BEFORE_OFFSET) {
\r
192 offset.setOffset(nextPos, TextOffset.AFTER_OFFSET);
\r
195 protected void advanceToPreviousBoundary(TextOffset offset) {
\r
197 advanceToPreviousBoundary(offset, false);
\r
200 private void advanceToPreviousBoundary(TextOffset offset, boolean alwaysMove) {
\r
201 // if there's no boundaries object, or if we're sitting at the beginning
\r
202 // of the document, return the offset unchanged
\r
203 if (fBoundaries == null) {
\r
207 int position = offset.fOffset;
\r
209 if (position == 0) {
\r
213 // If position is at a boundary, leave it unchanged. Otherwise
\r
214 // move to previous boundary.
\r
215 if (position == fText.length()) {
\r
216 fBoundaries.last();
\r
219 fBoundaries.following(position);
\r
222 int prevPos = fBoundaries.previous();
\r
224 if (prevPos == position) {
\r
225 if (!alwaysMove && offset.fPlacement==TextOffset.AFTER_OFFSET) {
\r
229 prevPos = fBoundaries.previous();
\r
232 // and finally update the real offset with this new position we've found
\r
233 offset.setOffset(prevPos, TextOffset.AFTER_OFFSET);
\r
236 private void doArrowKey(KeyEvent e, int key) {
\r
238 // when there's a selection range, the left and up arrow keys place an
\r
239 // insertion point at the beginning of the range, and the right and down
\r
240 // keys place an insertion point at the end of the range (unless the shift
\r
241 // key is down, of course)
\r
243 if (!fStart.equals(fLimit) && !e.isShiftDown()) {
\r
244 if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_UP)
\r
245 setSelRangeAndDraw(fStart, fStart, fStart);
\r
247 setSelRangeAndDraw(fLimit, fLimit, fLimit);
\r
250 if (!fAnchor.equals(fStart))
\r
251 fAnchor.assign(fLimit);
\r
253 TextOffset liveEnd = (fStart.equals(fAnchor)) ? fLimit : fStart;
\r
254 TextOffset newPos = new TextOffset();
\r
256 // if the control key is down, the left and right arrow keys move by whole
\r
257 // word in the appropriate direction (we use a line break object so that we're
\r
258 // not treating spaces and punctuation as words)
\r
259 if (e.isControlDown() && (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)) {
\r
260 fUpDownAnchor = null;
\r
261 fBoundaries = BreakIterator.getLineInstance();
\r
262 fBoundaries.setText(fText.createCharacterIterator());
\r
264 newPos.assign(liveEnd);
\r
265 if (key == KeyEvent.VK_RIGHT)
\r
266 advanceToNextBoundary(newPos);
\r
268 advanceToPreviousBoundary(newPos, true);
\r
271 // if we get down to here, this is a plain-vanilla insertion-point move,
\r
272 // or the shift key is down and we're extending or shortening the selection
\r
275 // fUpDownAnchor is used to keep track of the horizontal position
\r
276 // across a run of up or down arrow keys (this prevents accumulated
\r
277 // error from destroying our horizontal position)
\r
278 if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)
\r
279 fUpDownAnchor = null;
\r
281 if (fUpDownAnchor == null) {
\r
282 fUpDownAnchor = new TextOffset(liveEnd);
\r
286 short direction = MFormatter.eRight; // just to have a default...
\r
289 case KeyEvent.VK_UP: direction = MFormatter.eUp; break;
\r
290 case KeyEvent.VK_DOWN: direction = MFormatter.eDown; break;
\r
291 case KeyEvent.VK_LEFT: direction = MFormatter.eLeft; break;
\r
292 case KeyEvent.VK_RIGHT: direction = MFormatter.eRight; break;
\r
295 // use the formatter to determine the actual effect of the arrow key
\r
296 fTextComponent.findNewInsertionOffset(newPos, fUpDownAnchor, liveEnd, direction);
\r
299 // if the shift key is down, the selection range is from the anchor point
\r
300 // the site of the last insertion point or the beginning point of the last
\r
301 // selection drag operation) to the newly-calculated position; if the
\r
302 // shift key is down, the newly-calculated position is the insertion point position
\r
303 if (!e.isShiftDown())
\r
304 setSelRangeAndDraw(newPos, newPos, newPos);
\r
306 if (newPos.lessThan(fAnchor))
\r
307 setSelRangeAndDraw(newPos, fAnchor, fAnchor);
\r
309 setSelRangeAndDraw(fAnchor, newPos, fAnchor);
\r
313 scrollToShowSelectionEnd();
\r
314 fBoundaries = null;
\r
317 private void doEndKey(KeyEvent e) {
\r
318 // ctrl-end moves the insertsion point to the end of the document,
\r
319 // ctrl-shift-end extends the selection so that it ends at the end
\r
322 TextOffset activeEnd, anchor;
\r
324 if (fAnchor.equals(fStart)) {
\r
325 activeEnd = new TextOffset(fStart);
\r
326 anchor = new TextOffset(fLimit);
\r
329 activeEnd = new TextOffset(fLimit);
\r
330 anchor = new TextOffset(fStart);
\r
333 if (e.isControlDown()) {
\r
334 TextOffset end = new TextOffset(fText.length(), TextOffset.BEFORE_OFFSET);
\r
336 if (e.isShiftDown())
\r
337 setSelRangeAndDraw(anchor, end, anchor);
\r
339 setSelRangeAndDraw(end, end, end);
\r
342 // end moves the insertion point to the end of the line containing
\r
343 // the end of the current selection
\r
344 // shift-end extends the selection to the end of the line containing
\r
345 // the end of the current selection
\r
349 int oldOffset = activeEnd.fOffset;
\r
351 activeEnd.fOffset = fTextComponent.lineRangeLimit(fTextComponent.lineContaining(activeEnd));
\r
352 activeEnd.fPlacement = TextOffset.BEFORE_OFFSET;
\r
354 if (fText.paragraphLimit(oldOffset) == activeEnd.fOffset &&
\r
355 activeEnd.fOffset != fText.length() && activeEnd.fOffset > oldOffset) {
\r
356 activeEnd.fOffset--;
\r
357 activeEnd.fPlacement = TextOffset.AFTER_OFFSET;
\r
360 if (!e.isShiftDown())
\r
361 setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);
\r
363 if (activeEnd.lessThan(anchor))
\r
364 setSelRangeAndDraw(activeEnd, anchor, anchor);
\r
366 setSelRangeAndDraw(anchor, activeEnd, anchor);
\r
370 scrollToShowSelectionEnd();
\r
371 fBoundaries = null;
\r
372 fUpDownAnchor = null;
\r
375 private void doHomeKey(KeyEvent e) {
\r
376 // ctrl-home moves the insertion point to the beginning of the document,
\r
377 // ctrl-shift-home extends the selection so that it begins at the beginning
\r
380 TextOffset activeEnd, anchor;
\r
382 if (fAnchor.equals(fStart)) {
\r
383 activeEnd = new TextOffset(fStart);
\r
384 anchor = new TextOffset(fLimit);
\r
387 activeEnd = new TextOffset(fLimit);
\r
388 anchor = new TextOffset(fStart);
\r
391 if (e.isControlDown()) {
\r
393 TextOffset start = new TextOffset(0, TextOffset.AFTER_OFFSET);
\r
394 if (e.isShiftDown())
\r
395 setSelRangeAndDraw(start, anchor, anchor);
\r
397 setSelRangeAndDraw(start, start, start);
\r
400 // home moves the insertion point to the beginning of the line containing
\r
401 // the beginning of the current selection
\r
402 // shift-home extends the selection to the beginning of the line containing
\r
403 // the beginning of the current selection
\r
407 activeEnd.fOffset = fTextComponent.lineRangeLow(fTextComponent.lineContaining(activeEnd));
\r
408 activeEnd.fPlacement = TextOffset.AFTER_OFFSET;
\r
410 if (!e.isShiftDown())
\r
411 setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);
\r
413 if (activeEnd.lessThan(anchor))
\r
414 setSelRangeAndDraw(activeEnd, anchor, anchor);
\r
416 setSelRangeAndDraw(anchor, activeEnd, anchor);
\r
420 scrollToShowSelectionEnd();
\r
421 fBoundaries = null;
\r
422 fUpDownAnchor = null;
\r
425 /** draws or erases the current selection
\r
426 * Draws or erases the highlight region or insertion caret for the current selection
\r
428 * @param g The graphics environment to draw into
\r
429 * @param visible If true, draw the selection; if false, erase it
\r
431 protected void drawSelection(Graphics g, boolean visible) {
\r
432 drawSelectionRange(g, fStart, fLimit, visible);
\r
435 /** draws or erases a selection highlight at the specfied positions
\r
436 * Draws or erases a selection highlight or insertion caret corresponding to
\r
437 * the specified selecion range
\r
438 * @param g The graphics environment to draw into. If null, this method does nothing.
\r
439 * @param start The beginning of the range to highlight
\r
440 * @param limit The end of the range to highlight
\r
441 * @param visible If true, draw; if false, erase
\r
443 protected void drawSelectionRange( Graphics g,
\r
450 Rectangle selBounds = fTextComponent.getBoundingRect(start, limit);
\r
452 selBounds.width = Math.max(1, selBounds.width);
\r
453 selBounds.height = Math.max(1, selBounds.height);
\r
455 fTextComponent.drawText(g, selBounds, visible, start, limit, fHighlightColor);
\r
458 protected TextOffset getAnchor() {
\r
462 public TextOffset getEnd() {
\r
466 public Color getHighlightColor() {
\r
467 return fHighlightColor;
\r
470 public TextOffset getStart() {
\r
474 public TextRange getSelectionRange() {
\r
476 return new TextRange(fStart.fOffset, fLimit.fOffset);
\r
479 public boolean focusGained(FocusEvent e) {
\r
482 drawSelection(fTextComponent.getGraphics(), true);
\r
484 restartCaretBlinking(true);
\r
485 if (fPendingMouseEvent != null) {
\r
486 mousePressed(fPendingMouseEvent);
\r
487 fPendingMouseEvent = null;
\r
489 fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
\r
494 public boolean focusLost(FocusEvent e) {
\r
495 stopCaretBlinking();
\r
497 drawSelection(fTextComponent.getGraphics(), false);
\r
502 * Return true if the given key event can affect the selection
\r
505 public static boolean keyAffectsSelection(KeyEvent e) {
\r
507 if (e.getID() != KeyEvent.KEY_PRESSED) {
\r
511 int key = e.getKeyCode();
\r
514 case KeyEvent.VK_HOME:
\r
515 case KeyEvent.VK_END:
\r
516 case KeyEvent.VK_LEFT:
\r
517 case KeyEvent.VK_RIGHT:
\r
518 case KeyEvent.VK_UP:
\r
519 case KeyEvent.VK_DOWN:
\r
527 public boolean keyPressed(KeyEvent e) {
\r
529 fHandlingKeyOrCommand = true;
\r
530 int key = e.getKeyCode();
\r
531 boolean result = true;
\r
534 case KeyEvent.VK_HOME:
\r
538 case KeyEvent.VK_END:
\r
542 case KeyEvent.VK_LEFT:
\r
543 case KeyEvent.VK_RIGHT:
\r
544 case KeyEvent.VK_UP:
\r
545 case KeyEvent.VK_DOWN:
\r
546 doArrowKey(e, key);
\r
550 fUpDownAnchor = null;
\r
555 fHandlingKeyOrCommand = false;
\r
559 public boolean mousePressed(MouseEvent e) {
\r
562 fPendingMouseEvent = e;
\r
563 fTextComponent.requestFocus();
\r
568 throw new Error("fMouseDown is out of sync with mouse in TextSelection.");
\r
571 stopCaretBlinking();
\r
573 int x = e.getX(), y = e.getY();
\r
574 boolean wasZeroLength = rangeIsZeroLength(fStart, fLimit, fAnchor);
\r
576 TextOffset current = fTextComponent.pointToTextOffset(null, x, y, null, true);
\r
577 TextOffset anchorStart = new TextOffset();
\r
578 TextOffset anchorEnd = new TextOffset();
\r
580 fUpDownAnchor = null;
\r
582 // if we're not extending the selection...
\r
583 if (!e.isShiftDown()) {
\r
585 // if there are multiple clicks, create the appopriate type of BreakIterator
\r
586 // object for finding text boundaries (single clicks don't use a BreakIterator
\r
588 if (e.getClickCount() == 2)
\r
589 fBoundaries = BreakIterator.getWordInstance();
\r
590 else if (e.getClickCount() == 3)
\r
591 fBoundaries = BreakIterator.getSentenceInstance();
\r
593 fBoundaries = null;
\r
595 // if we're using a BreakIterator object, use it to find the nearest boundaries
\r
596 // on either side of the mouse-click position and make them our anchor range
\r
597 if (fBoundaries != null)
\r
598 fBoundaries.setText(fText.createCharacterIterator());
\r
600 anchorStart.assign(current);
\r
601 advanceToPreviousBoundary(anchorStart);
\r
602 anchorEnd.assign(current);
\r
603 advanceToNextBoundary(anchorEnd);
\r
606 // if we _are_ extending the selection, determine our anchor range as follows:
\r
607 // fAnchor is the start of the anchor range;
\r
608 // the next boundary (after fAnchor) is the limit of the anchor range.
\r
612 if (fBoundaries != null)
\r
613 fBoundaries.setText(fText.createCharacterIterator());
\r
615 anchorStart.assign(fAnchor);
\r
616 anchorEnd.assign(anchorStart);
\r
618 advanceToNextBoundary(anchorEnd);
\r
621 SelectionDragInteractor interactor = new SelectionDragInteractor(this,
\r
631 interactor.addToOwner(fTextComponent);
\r
636 public boolean mouseReleased(MouseEvent e) {
\r
638 fPendingMouseEvent = null;
\r
642 // drag interactor calls this
\r
643 void mouseReleased(boolean zeroLengthChange) {
\r
645 fMouseDown = false;
\r
647 if (zeroLengthChange) {
\r
648 fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);
\r
650 fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);
\r
651 fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
\r
653 // if caret drawing during mouse drags is supressed, draw caret now.
\r
655 restartCaretBlinking(true);
\r
659 /** draws the selection
\r
660 * Provided, of course, that the selection is visible, the adorner is enabled,
\r
661 * and we're calling it to adorn the view it actually belongs to
\r
662 * @param g The graphics environment to draw into
\r
663 * @return true if we actually drew
\r
665 public boolean paint(Graphics g, Rectangle drawRect) {
\r
666 // don't draw anything unless we're enabled and the selection is visible
\r
670 fTextComponent.drawText(g, drawRect, true, fStart, fLimit, fHighlightColor);
\r
674 /** scrolls the view to reveal the live end of the selection
\r
675 * (i.e., the end that moves if you use the arrow keys with the shift key down)
\r
677 public void scrollToShowSelection() {
\r
678 Rectangle selRect = fTextComponent.getBoundingRect(fStart, fLimit);
\r
680 fTextComponent.scrollToShow(selRect);
\r
683 /** scrolls the view to reveal the live end of the selection
\r
684 * (i.e., the end that moves if you use the arrow keys with the shift key down)
\r
686 public void scrollToShowSelectionEnd() {
\r
687 TextOffset liveEnd;
\r
688 // variable not used Point[] points;
\r
691 if (fAnchor.equals(fStart))
\r
696 //points = fTextComponent.textOffsetToPoint(liveEnd);
\r
697 //caret = new Rectangle(points[0]);
\r
698 //caret = caret.union(new Rectangle(points[1]));
\r
699 caret = fTextComponent.getCaretRect(liveEnd);
\r
700 fTextComponent.scrollToShow(caret);
\r
703 private void select(TextRange range) {
\r
704 // variable not used int textLength = fTextComponent.getText().length();
\r
706 TextOffset start = new TextOffset(range.start);
\r
708 stopCaretBlinking();
\r
709 setSelRangeAndDraw(start, new TextOffset(range.limit), start);
\r
710 restartCaretBlinking(true);
\r
713 public void setHighlightColor(Color newColor) {
\r
714 fHighlightColor = newColor;
\r
716 drawSelection(fTextComponent.getGraphics(), true);
\r
719 static boolean rangeIsZeroLength(TextOffset start, TextOffset limit, TextOffset anchor) {
\r
721 return start.fOffset == limit.fOffset && anchor.fOffset == limit.fOffset;
\r
724 // sigh... look out for aliasing
\r
725 public void setSelectionRange(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) {
\r
727 boolean zeroLengthChange = rangeIsZeroLength(newStart, newLimit, newAnchor) !=
\r
728 rangeIsZeroLength(fStart, fLimit, fAnchor);
\r
729 TextOffset tempNewAnchor;
\r
730 if (newAnchor == fStart || newAnchor == fLimit) {
\r
731 tempNewAnchor = new TextOffset(newAnchor); // clone in case of aliasing
\r
734 tempNewAnchor = newAnchor;
\r
739 if (newStart.greaterThan(newLimit))
\r
740 throw new IllegalArgumentException("Selection limit is before selection start.");
\r
742 if (newLimit != fStart) {
\r
743 fStart.assign(newStart);
\r
744 fLimit.assign(newLimit);
\r
747 fLimit.assign(newLimit);
\r
748 fStart.assign(newStart);
\r
751 fAnchor.assign(tempNewAnchor);
\r
753 if (fStart.fOffset == fLimit.fOffset) {
\r
754 fStart.fPlacement = fAnchor.fPlacement;
\r
755 fLimit.fPlacement = fAnchor.fPlacement;
\r
759 if (zeroLengthChange) {
\r
760 fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);
\r
762 fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);
\r
763 if (fHandlingKeyOrCommand) {
\r
764 fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
\r
769 private void sortOffsets(TextOffset offsets[]) {
\r
773 for (i=0; i < offsets.length-1; i++) {
\r
774 for (j=i+1; j < offsets.length; j++) {
\r
775 if (offsets[j].lessThan(offsets[i])) {
\r
776 TextOffset temp = offsets[j];
\r
777 offsets[j] = offsets[i];
\r
784 for (i=0; i < offsets.length-1; i++)
\r
785 if (offsets[i].greaterThan(offsets[i+1]))
\r
786 throw new Error("sortOffsets failed!");
\r
789 private Rectangle getSelectionChangeRect(
\r
790 TextOffset rangeStart, TextOffset rangeLimit,
\r
791 TextOffset oldStart, TextOffset oldLimit,
\r
792 TextOffset newStart, TextOffset newLimit,
\r
793 boolean drawIfInsPoint) {
\r
795 if (!rangeStart.equals(rangeLimit))
\r
796 return fTextComponent.getBoundingRect(rangeStart, rangeLimit);
\r
798 // here, rangeStart and rangeLimit are equal
\r
800 if (rangeStart.equals(oldLimit)) {
\r
802 // range start is OLD insertion point. Redraw if caret is currently visible.
\r
804 if (fCaretIsVisible)
\r
805 return fTextComponent.getBoundingRect(rangeStart, rangeStart);
\r
807 else if (rangeStart.equals(newLimit)) {
\r
809 // range start is NEW insertion point.
\r
811 if (drawIfInsPoint)
\r
812 return fTextComponent.getBoundingRect(rangeStart, rangeStart);
\r
818 private static boolean rectanglesOverlapVertically(Rectangle r1, Rectangle r2) {
\r
820 if (r1 == null || r2 == null) {
\r
824 return r1.y <= r2.y + r2.height || r2.y <= r1.y + r1.height;
\r
827 // Update to show new selection, redrawing as little as possible
\r
829 private void updateSelectionDisplay(
\r
830 TextOffset oldStart, TextOffset oldLimit,
\r
831 TextOffset newStart, TextOffset newLimit, boolean drawIfInsPoint) {
\r
833 //System.out.println("newStart:" + newStart + "; newLimit:" + newLimit);
\r
835 TextOffset off[] = new TextOffset[4];
\r
844 Rectangle r1 = getSelectionChangeRect(off[0], off[1], oldStart, oldLimit, newStart, newLimit, drawIfInsPoint);
\r
845 Rectangle r2 = getSelectionChangeRect(off[2], off[3], oldStart, oldLimit, newStart, newLimit, drawIfInsPoint);
\r
847 boolean drawSelection = drawIfInsPoint || !newStart.equals(newLimit);
\r
849 if (rectanglesOverlapVertically(r1, r2)) {
\r
851 fTextComponent.drawText(fTextComponent.getGraphics(), r1.union(r2), drawSelection, newStart, newLimit, fHighlightColor);
\r
855 fTextComponent.drawText(fTextComponent.getGraphics(), r1, drawSelection, newStart, newLimit, fHighlightColor);
\r
857 fTextComponent.drawText(fTextComponent.getGraphics(), r2, drawSelection, newStart, newLimit, fHighlightColor);
\r
861 public void setSelRangeAndDraw(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) {
\r
863 // if the old and new selection ranges are the same, don't do anything
\r
864 if (fStart.equals(newStart) && fLimit.equals(newLimit) && fAnchor.equals(newAnchor))
\r
868 stopCaretBlinking();
\r
870 // update the selection on screen if we're enabled and visible
\r
872 TextOffset oldStart = new TextOffset(fStart), oldLimit = new TextOffset(fLimit);
\r
874 setSelectionRange(newStart, newLimit, newAnchor);
\r
878 // To supress drawing a caret during a mouse drag, pass !fMouseDown instead of true:
\r
879 updateSelectionDisplay(oldStart, oldLimit, fStart, fLimit, true);
\r
882 if (!fMouseDown && enabled())
\r
883 restartCaretBlinking(true);
\r
886 public void stopCaretBlinking() {
\r
888 synchronized(this) {
\r
889 fCaretShouldBlink = false;
\r
894 * Resume blinking the caret, if the selection is an insertion point.
\r
895 * @param caretIsVisible true if the caret is displayed when this is called.
\r
896 * This method relies on the client to display (or not display) the caret.
\r
898 public void restartCaretBlinking(boolean caretIsVisible) {
\r
900 synchronized(this) {
\r
901 fCaretShouldBlink = fStart.equals(fLimit);
\r
903 fCaretIsVisible = caretIsVisible;
\r
905 if (fCaretShouldBlink) {
\r
909 catch (IllegalMonitorStateException e) {
\r
910 System.out.println("Caught IllegalMonitorStateException: "+e);
\r
916 public void removeFromOwner() {
\r
918 stopCaretBlinking();
\r
919 super.removeFromOwner();
\r