001: /*
002: * (C) Copyright IBM Corp. 1998-2005. All Rights Reserved.
003: *
004: * The program is provided "as is" without any warranty express or
005: * implied, including the warranty of non-infringement and the implied
006: * warranties of merchantibility and fitness for a particular purpose.
007: * IBM will not be liable for any damages suffered by you as a result
008: * of using the Program. In no event will IBM be liable for any
009: * special, indirect or consequential damages or lost profits even if
010: * IBM has been advised of the possibility of their occurrence. IBM
011: * will not be liable for any third party claims against you.
012: */
013: /*
014: 7/1/97 - caret blinks
015:
016: 7/3/97 - fAnchor is no longer restricted to the start or end of the selection. {jbr}
017: Also, removed fVisible - it was identical to enabled().
018: */
019:
020: package com.ibm.richtext.textpanel;
021:
022: import java.awt.Graphics;
023: import java.awt.Color;
024: import java.awt.Rectangle;
025:
026: import java.text.BreakIterator;
027:
028: import java.awt.event.MouseEvent;
029: import java.awt.event.KeyEvent;
030: import java.awt.event.FocusEvent;
031:
032: import com.ibm.richtext.styledtext.MConstText;
033: import com.ibm.richtext.textformat.TextOffset;
034:
035: import com.ibm.richtext.textformat.MFormatter;
036:
037: class TextSelection extends Behavior implements Runnable {
038: static final String COPYRIGHT = "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
039: static final Color HIGHLIGHTCOLOR = Color.pink;
040:
041: private TextComponent fTextComponent;
042: private MConstText fText;
043: private TextOffset fStart;
044: private TextOffset fLimit;
045: private TextOffset fAnchor;
046: private TextOffset fUpDownAnchor = null;
047: private BreakIterator fBoundaries = null;
048: private Color fHighlightColor = HIGHLIGHTCOLOR;
049: private PanelEventBroadcaster fListener;
050: private RunStrategy fRunStrategy;
051: private boolean fMouseDown = false;
052: private boolean fHandlingKeyOrCommand = false;
053:
054: private boolean fCaretShouldBlink;
055: private boolean fCaretIsVisible;
056: private int fCaretCount;
057:
058: // formerly in base class
059: private boolean fEnabled;
060:
061: private MouseEvent fPendingMouseEvent = null;
062:
063: private static final int kCaretInterval = 500;
064:
065: public void run() {
066:
067: final Runnable blinkCaret = new Runnable() {
068: public void run() {
069: fCaretIsVisible = !fCaretIsVisible;
070: Graphics g = fTextComponent.getGraphics();
071: if (g != null) {
072: //System.out.println("caretIsVisible: " + fCaretIsVisible);
073: drawSelection(g, fCaretIsVisible);
074: } else {
075: // Not sure what else to do:
076: fCaretShouldBlink = false;
077: }
078: }
079: };
080:
081: // blink caret
082: while (true) {
083:
084: synchronized (this ) {
085:
086: while (!fCaretShouldBlink) {
087: try {
088: wait();
089: } catch (InterruptedException e) {
090: System.out
091: .println("Caught InterruptedException in caret thread.");
092: }
093: }
094:
095: ++fCaretCount;
096:
097: if (fCaretCount % 2 == 0) {
098: fRunStrategy.doIt(blinkCaret);
099: }
100: }
101:
102: try {
103: Thread.sleep(kCaretInterval);
104: } catch (InterruptedException e) {
105: }
106: }
107: }
108:
109: public TextSelection(TextComponent textComponent,
110: PanelEventBroadcaster listener, RunStrategy runStrategy) {
111:
112: fTextComponent = textComponent;
113: fText = textComponent.getText();
114: fListener = listener;
115: fRunStrategy = runStrategy;
116:
117: fStart = new TextOffset();
118: fLimit = new TextOffset();
119: fAnchor = new TextOffset();
120: fMouseDown = false;
121:
122: fCaretCount = 0;
123: fCaretIsVisible = true;
124: fCaretShouldBlink = false;
125: setEnabled(false);
126:
127: Thread caretThread = new Thread(this );
128: caretThread.setDaemon(true);
129: caretThread.start();
130: }
131:
132: boolean enabled() {
133:
134: return fEnabled;
135: }
136:
137: private void setEnabled(boolean enabled) {
138:
139: fEnabled = enabled;
140: }
141:
142: public boolean textControlEventOccurred(Behavior.EventType event,
143: Object what) {
144:
145: boolean result;
146: fHandlingKeyOrCommand = true;
147:
148: if (event == Behavior.SELECT) {
149: select((TextRange) what);
150: result = true;
151: } else if (event == Behavior.COPY) {
152: fTextComponent.getClipboard().setContents(
153: fText.extract(fStart.fOffset, fLimit.fOffset));
154: fListener
155: .textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
156: result = true;
157: } else {
158: result = false;
159: }
160:
161: fHandlingKeyOrCommand = false;
162: return result;
163: }
164:
165: protected void advanceToNextBoundary(TextOffset offset) {
166:
167: // If there's no boundaries object, or if position at the end of the
168: // document, return the offset unchanged
169: if (fBoundaries == null) {
170: return;
171: }
172:
173: int position = offset.fOffset;
174:
175: if (position >= fText.length()) {
176: return;
177: }
178:
179: // If position is at a boundary and offset is before position,
180: // leave it unchanged. Otherwise move to next boundary.
181: int nextPos = fBoundaries.following(position);
182: if (fBoundaries.previous() == position
183: && offset.fPlacement == TextOffset.BEFORE_OFFSET) {
184: return;
185: }
186:
187: offset.setOffset(nextPos, TextOffset.AFTER_OFFSET);
188: }
189:
190: protected void advanceToPreviousBoundary(TextOffset offset) {
191:
192: advanceToPreviousBoundary(offset, false);
193: }
194:
195: private void advanceToPreviousBoundary(TextOffset offset,
196: boolean alwaysMove) {
197: // if there's no boundaries object, or if we're sitting at the beginning
198: // of the document, return the offset unchanged
199: if (fBoundaries == null) {
200: return;
201: }
202:
203: int position = offset.fOffset;
204:
205: if (position == 0) {
206: return;
207: }
208:
209: // If position is at a boundary, leave it unchanged. Otherwise
210: // move to previous boundary.
211: if (position == fText.length()) {
212: fBoundaries.last();
213: } else {
214: fBoundaries.following(position);
215: }
216:
217: int prevPos = fBoundaries.previous();
218:
219: if (prevPos == position) {
220: if (!alwaysMove
221: && offset.fPlacement == TextOffset.AFTER_OFFSET) {
222: return;
223: }
224:
225: prevPos = fBoundaries.previous();
226: }
227:
228: // and finally update the real offset with this new position we've found
229: offset.setOffset(prevPos, TextOffset.AFTER_OFFSET);
230: }
231:
232: private void doArrowKey(KeyEvent e, int key) {
233:
234: // when there's a selection range, the left and up arrow keys place an
235: // insertion point at the beginning of the range, and the right and down
236: // keys place an insertion point at the end of the range (unless the shift
237: // key is down, of course)
238:
239: if (!fStart.equals(fLimit) && !e.isShiftDown()) {
240: if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_UP)
241: setSelRangeAndDraw(fStart, fStart, fStart);
242: else
243: setSelRangeAndDraw(fLimit, fLimit, fLimit);
244: } else {
245: if (!fAnchor.equals(fStart))
246: fAnchor.assign(fLimit);
247:
248: TextOffset liveEnd = (fStart.equals(fAnchor)) ? fLimit
249: : fStart;
250: TextOffset newPos = new TextOffset();
251:
252: // if the control key is down, the left and right arrow keys move by whole
253: // word in the appropriate direction (we use a line break object so that we're
254: // not treating spaces and punctuation as words)
255: if (e.isControlDown()
256: && (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)) {
257: fUpDownAnchor = null;
258: fBoundaries = BreakIterator.getLineInstance();
259: fBoundaries.setText(fText.createCharacterIterator());
260:
261: newPos.assign(liveEnd);
262: if (key == KeyEvent.VK_RIGHT)
263: advanceToNextBoundary(newPos);
264: else
265: advanceToPreviousBoundary(newPos, true);
266: }
267:
268: // if we get down to here, this is a plain-vanilla insertion-point move,
269: // or the shift key is down and we're extending or shortening the selection
270: else {
271:
272: // fUpDownAnchor is used to keep track of the horizontal position
273: // across a run of up or down arrow keys (this prevents accumulated
274: // error from destroying our horizontal position)
275: if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)
276: fUpDownAnchor = null;
277: else {
278: if (fUpDownAnchor == null) {
279: fUpDownAnchor = new TextOffset(liveEnd);
280: }
281: }
282:
283: short direction = MFormatter.eRight; // just to have a default...
284:
285: switch (key) {
286: case KeyEvent.VK_UP:
287: direction = MFormatter.eUp;
288: break;
289: case KeyEvent.VK_DOWN:
290: direction = MFormatter.eDown;
291: break;
292: case KeyEvent.VK_LEFT:
293: direction = MFormatter.eLeft;
294: break;
295: case KeyEvent.VK_RIGHT:
296: direction = MFormatter.eRight;
297: break;
298: }
299:
300: // use the formatter to determine the actual effect of the arrow key
301: fTextComponent.findNewInsertionOffset(newPos,
302: fUpDownAnchor, liveEnd, direction);
303: }
304:
305: // if the shift key is down, the selection range is from the anchor point
306: // the site of the last insertion point or the beginning point of the last
307: // selection drag operation) to the newly-calculated position; if the
308: // shift key is down, the newly-calculated position is the insertion point position
309: if (!e.isShiftDown())
310: setSelRangeAndDraw(newPos, newPos, newPos);
311: else {
312: if (newPos.lessThan(fAnchor))
313: setSelRangeAndDraw(newPos, fAnchor, fAnchor);
314: else
315: setSelRangeAndDraw(fAnchor, newPos, fAnchor);
316: }
317: }
318:
319: scrollToShowSelectionEnd();
320: fBoundaries = null;
321: }
322:
323: private void doEndKey(KeyEvent e) {
324: // ctrl-end moves the insertsion point to the end of the document,
325: // ctrl-shift-end extends the selection so that it ends at the end
326: // of the document
327:
328: TextOffset activeEnd, anchor;
329:
330: if (fAnchor.equals(fStart)) {
331: activeEnd = new TextOffset(fStart);
332: anchor = new TextOffset(fLimit);
333: } else {
334: activeEnd = new TextOffset(fLimit);
335: anchor = new TextOffset(fStart);
336: }
337:
338: if (e.isControlDown()) {
339: TextOffset end = new TextOffset(fText.length(),
340: TextOffset.BEFORE_OFFSET);
341:
342: if (e.isShiftDown())
343: setSelRangeAndDraw(anchor, end, anchor);
344: else
345: setSelRangeAndDraw(end, end, end);
346: }
347:
348: // end moves the insertion point to the end of the line containing
349: // the end of the current selection
350: // shift-end extends the selection to the end of the line containing
351: // the end of the current selection
352:
353: else {
354:
355: int oldOffset = activeEnd.fOffset;
356:
357: activeEnd.fOffset = fTextComponent
358: .lineRangeLimit(fTextComponent
359: .lineContaining(activeEnd));
360: activeEnd.fPlacement = TextOffset.BEFORE_OFFSET;
361:
362: if (fText.paragraphLimit(oldOffset) == activeEnd.fOffset
363: && activeEnd.fOffset != fText.length()
364: && activeEnd.fOffset > oldOffset) {
365: activeEnd.fOffset--;
366: activeEnd.fPlacement = TextOffset.AFTER_OFFSET;
367: }
368:
369: if (!e.isShiftDown())
370: setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);
371: else {
372: if (activeEnd.lessThan(anchor))
373: setSelRangeAndDraw(activeEnd, anchor, anchor);
374: else
375: setSelRangeAndDraw(anchor, activeEnd, anchor);
376: }
377: }
378:
379: scrollToShowSelectionEnd();
380: fBoundaries = null;
381: fUpDownAnchor = null;
382: }
383:
384: private void doHomeKey(KeyEvent e) {
385: // ctrl-home moves the insertion point to the beginning of the document,
386: // ctrl-shift-home extends the selection so that it begins at the beginning
387: // of the document
388:
389: TextOffset activeEnd, anchor;
390:
391: if (fAnchor.equals(fStart)) {
392: activeEnd = new TextOffset(fStart);
393: anchor = new TextOffset(fLimit);
394: } else {
395: activeEnd = new TextOffset(fLimit);
396: anchor = new TextOffset(fStart);
397: }
398:
399: if (e.isControlDown()) {
400:
401: TextOffset start = new TextOffset(0,
402: TextOffset.AFTER_OFFSET);
403: if (e.isShiftDown())
404: setSelRangeAndDraw(start, anchor, anchor);
405: else
406: setSelRangeAndDraw(start, start, start);
407: }
408:
409: // home moves the insertion point to the beginning of the line containing
410: // the beginning of the current selection
411: // shift-home extends the selection to the beginning of the line containing
412: // the beginning of the current selection
413:
414: else {
415:
416: activeEnd.fOffset = fTextComponent
417: .lineRangeLow(fTextComponent
418: .lineContaining(activeEnd));
419: activeEnd.fPlacement = TextOffset.AFTER_OFFSET;
420:
421: if (!e.isShiftDown())
422: setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);
423: else {
424: if (activeEnd.lessThan(anchor))
425: setSelRangeAndDraw(activeEnd, anchor, anchor);
426: else
427: setSelRangeAndDraw(anchor, activeEnd, anchor);
428: }
429: }
430:
431: scrollToShowSelectionEnd();
432: fBoundaries = null;
433: fUpDownAnchor = null;
434: }
435:
436: /** draws or erases the current selection
437: * Draws or erases the highlight region or insertion caret for the current selection
438: * range.
439: * @param g The graphics environment to draw into
440: * @param visible If true, draw the selection; if false, erase it
441: */
442: protected void drawSelection(Graphics g, boolean visible) {
443: drawSelectionRange(g, fStart, fLimit, visible);
444: }
445:
446: /** draws or erases a selection highlight at the specfied positions
447: * Draws or erases a selection highlight or insertion caret corresponding to
448: * the specified selecion range
449: * @param g The graphics environment to draw into. If null, this method does nothing.
450: * @param start The beginning of the range to highlight
451: * @param limit The end of the range to highlight
452: * @param visible If true, draw; if false, erase
453: */
454: protected void drawSelectionRange(Graphics g, TextOffset start,
455: TextOffset limit, boolean visible) {
456: if (g == null) {
457: return;
458: }
459: Rectangle selBounds = fTextComponent.getBoundingRect(start,
460: limit);
461:
462: selBounds.width = Math.max(1, selBounds.width);
463: selBounds.height = Math.max(1, selBounds.height);
464:
465: fTextComponent.drawText(g, selBounds, visible, start, limit,
466: fHighlightColor);
467: }
468:
469: protected TextOffset getAnchor() {
470: return fAnchor;
471: }
472:
473: public TextOffset getEnd() {
474: return fLimit;
475: }
476:
477: public Color getHighlightColor() {
478: return fHighlightColor;
479: }
480:
481: public TextOffset getStart() {
482: return fStart;
483: }
484:
485: public TextRange getSelectionRange() {
486:
487: return new TextRange(fStart.fOffset, fLimit.fOffset);
488: }
489:
490: public boolean focusGained(FocusEvent e) {
491:
492: setEnabled(true);
493: drawSelection(fTextComponent.getGraphics(), true);
494:
495: restartCaretBlinking(true);
496: if (fPendingMouseEvent != null) {
497: mousePressed(fPendingMouseEvent);
498: fPendingMouseEvent = null;
499: }
500: fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);
501:
502: return true;
503: }
504:
505: public boolean focusLost(FocusEvent e) {
506: stopCaretBlinking();
507: setEnabled(false);
508: drawSelection(fTextComponent.getGraphics(), false);
509: return true;
510: }
511:
512: /**
513: * Return true if the given key event can affect the selection
514: * range.
515: */
516: public static boolean keyAffectsSelection(KeyEvent e) {
517:
518: if (e.getID() != KeyEvent.KEY_PRESSED) {
519: return false;
520: }
521:
522: int key = e.getKeyCode();
523:
524: switch (key) {
525: case KeyEvent.VK_HOME:
526: case KeyEvent.VK_END:
527: case KeyEvent.VK_LEFT:
528: case KeyEvent.VK_RIGHT:
529: case KeyEvent.VK_UP:
530: case KeyEvent.VK_DOWN:
531: return true;
532:
533: default:
534: return false;
535: }
536: }
537:
538: public boolean keyPressed(KeyEvent e) {
539:
540: fHandlingKeyOrCommand = true;
541: int key = e.getKeyCode();
542: boolean result = true;
543:
544: switch (key) {
545: case KeyEvent.VK_HOME:
546: doHomeKey(e);
547: break;
548:
549: case KeyEvent.VK_END:
550: doEndKey(e);
551: break;
552:
553: case KeyEvent.VK_LEFT:
554: case KeyEvent.VK_RIGHT:
555: case KeyEvent.VK_UP:
556: case KeyEvent.VK_DOWN:
557: doArrowKey(e, key);
558: break;
559:
560: default:
561: fUpDownAnchor = null;
562: result = false;
563: break;
564: }
565:
566: fHandlingKeyOrCommand = false;
567: return result;
568: }
569:
570: public boolean mousePressed(MouseEvent e) {
571:
572: if (!enabled()) {
573: fPendingMouseEvent = e;
574: fTextComponent.requestFocus();
575: return false;
576: }
577:
578: if (fMouseDown)
579: throw new Error(
580: "fMouseDown is out of sync with mouse in TextSelection.");
581:
582: fMouseDown = true;
583: stopCaretBlinking();
584:
585: int x = e.getX(), y = e.getY();
586: boolean wasZeroLength = rangeIsZeroLength(fStart, fLimit,
587: fAnchor);
588:
589: TextOffset current = fTextComponent.pointToTextOffset(null, x,
590: y, null, true);
591: TextOffset anchorStart = new TextOffset();
592: TextOffset anchorEnd = new TextOffset();
593:
594: fUpDownAnchor = null;
595:
596: // if we're not extending the selection...
597: if (!e.isShiftDown()) {
598:
599: // if there are multiple clicks, create the appopriate type of BreakIterator
600: // object for finding text boundaries (single clicks don't use a BreakIterator
601: // object)
602: if (e.getClickCount() == 2)
603: fBoundaries = BreakIterator.getWordInstance();
604: else if (e.getClickCount() == 3)
605: fBoundaries = BreakIterator.getSentenceInstance();
606: else
607: fBoundaries = null;
608:
609: // if we're using a BreakIterator object, use it to find the nearest boundaries
610: // on either side of the mouse-click position and make them our anchor range
611: if (fBoundaries != null)
612: fBoundaries.setText(fText.createCharacterIterator());
613:
614: anchorStart.assign(current);
615: advanceToPreviousBoundary(anchorStart);
616: anchorEnd.assign(current);
617: advanceToNextBoundary(anchorEnd);
618: }
619:
620: // if we _are_ extending the selection, determine our anchor range as follows:
621: // fAnchor is the start of the anchor range;
622: // the next boundary (after fAnchor) is the limit of the anchor range.
623:
624: else {
625:
626: if (fBoundaries != null)
627: fBoundaries.setText(fText.createCharacterIterator());
628:
629: anchorStart.assign(fAnchor);
630: anchorEnd.assign(anchorStart);
631:
632: advanceToNextBoundary(anchorEnd);
633: }
634:
635: SelectionDragInteractor interactor = new SelectionDragInteractor(
636: this , fTextComponent, fRunStrategy, anchorStart,
637: anchorEnd, current, x, y, wasZeroLength);
638:
639: interactor.addToOwner(fTextComponent);
640:
641: return true;
642: }
643:
644: public boolean mouseReleased(MouseEvent e) {
645:
646: fPendingMouseEvent = null;
647: return false;
648: }
649:
650: // drag interactor calls this
651: void mouseReleased(boolean zeroLengthChange) {
652:
653: fMouseDown = false;
654:
655: if (zeroLengthChange) {
656: fListener
657: .textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);
658: }
659: fListener
660: .textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);
661: fListener
662: .textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
663:
664: // if caret drawing during mouse drags is supressed, draw caret now.
665:
666: restartCaretBlinking(true);
667: }
668:
669: /** draws the selection
670: * Provided, of course, that the selection is visible, the adorner is enabled,
671: * and we're calling it to adorn the view it actually belongs to
672: * @param g The graphics environment to draw into
673: * @return true if we actually drew
674: */
675: public boolean paint(Graphics g, Rectangle drawRect) {
676: // don't draw anything unless we're enabled and the selection is visible
677: if (!enabled())
678: return false;
679:
680: fTextComponent.drawText(g, drawRect, true, fStart, fLimit,
681: fHighlightColor);
682: return true;
683: }
684:
685: /** scrolls the view to reveal the live end of the selection
686: * (i.e., the end that moves if you use the arrow keys with the shift key down)
687: */
688: public void scrollToShowSelection() {
689: Rectangle selRect = fTextComponent.getBoundingRect(fStart,
690: fLimit);
691:
692: fTextComponent.scrollToShow(selRect);
693: }
694:
695: /** scrolls the view to reveal the live end of the selection
696: * (i.e., the end that moves if you use the arrow keys with the shift key down)
697: */
698: public void scrollToShowSelectionEnd() {
699: TextOffset liveEnd;
700: // variable not used Point[] points;
701: Rectangle caret;
702:
703: if (fAnchor.equals(fStart))
704: liveEnd = fLimit;
705: else
706: liveEnd = fStart;
707:
708: //points = fTextComponent.textOffsetToPoint(liveEnd);
709: //caret = new Rectangle(points[0]);
710: //caret = caret.union(new Rectangle(points[1]));
711: caret = fTextComponent.getCaretRect(liveEnd);
712: fTextComponent.scrollToShow(caret);
713: }
714:
715: private void select(TextRange range) {
716: // variable not used int textLength = fTextComponent.getText().length();
717:
718: TextOffset start = new TextOffset(range.start);
719:
720: stopCaretBlinking();
721: setSelRangeAndDraw(start, new TextOffset(range.limit), start);
722: restartCaretBlinking(true);
723: }
724:
725: public void setHighlightColor(Color newColor) {
726: fHighlightColor = newColor;
727: if (enabled())
728: drawSelection(fTextComponent.getGraphics(), true);
729: }
730:
731: static boolean rangeIsZeroLength(TextOffset start,
732: TextOffset limit, TextOffset anchor) {
733:
734: return start.fOffset == limit.fOffset
735: && anchor.fOffset == limit.fOffset;
736: }
737:
738: // sigh... look out for aliasing
739: public void setSelectionRange(TextOffset newStart,
740: TextOffset newLimit, TextOffset newAnchor) {
741:
742: boolean zeroLengthChange = rangeIsZeroLength(newStart,
743: newLimit, newAnchor) != rangeIsZeroLength(fStart,
744: fLimit, fAnchor);
745: TextOffset tempNewAnchor;
746: if (newAnchor == fStart || newAnchor == fLimit) {
747: tempNewAnchor = new TextOffset(newAnchor); // clone in case of aliasing
748: } else {
749: tempNewAnchor = newAnchor;
750: }
751:
752: // DEBUG {jbr}
753:
754: if (newStart.greaterThan(newLimit))
755: throw new IllegalArgumentException(
756: "Selection limit is before selection start.");
757:
758: if (newLimit != fStart) {
759: fStart.assign(newStart);
760: fLimit.assign(newLimit);
761: } else {
762: fLimit.assign(newLimit);
763: fStart.assign(newStart);
764: }
765:
766: fAnchor.assign(tempNewAnchor);
767:
768: if (fStart.fOffset == fLimit.fOffset) {
769: fStart.fPlacement = fAnchor.fPlacement;
770: fLimit.fPlacement = fAnchor.fPlacement;
771: }
772:
773: if (!fMouseDown) {
774: if (zeroLengthChange) {
775: fListener
776: .textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);
777: }
778: fListener
779: .textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);
780: if (fHandlingKeyOrCommand) {
781: fListener
782: .textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);
783: }
784: }
785: }
786:
787: private void sortOffsets(TextOffset offsets[]) {
788:
789: int i, j;
790:
791: for (i = 0; i < offsets.length - 1; i++) {
792: for (j = i + 1; j < offsets.length; j++) {
793: if (offsets[j].lessThan(offsets[i])) {
794: TextOffset temp = offsets[j];
795: offsets[j] = offsets[i];
796: offsets[i] = temp;
797: }
798: }
799: }
800:
801: // DEBUG {jbr}
802: for (i = 0; i < offsets.length - 1; i++)
803: if (offsets[i].greaterThan(offsets[i + 1]))
804: throw new Error("sortOffsets failed!");
805: }
806:
807: private Rectangle getSelectionChangeRect(TextOffset rangeStart,
808: TextOffset rangeLimit, TextOffset oldStart,
809: TextOffset oldLimit, TextOffset newStart,
810: TextOffset newLimit, boolean drawIfInsPoint) {
811:
812: if (!rangeStart.equals(rangeLimit))
813: return fTextComponent.getBoundingRect(rangeStart,
814: rangeLimit);
815:
816: // here, rangeStart and rangeLimit are equal
817:
818: if (rangeStart.equals(oldLimit)) {
819:
820: // range start is OLD insertion point. Redraw if caret is currently visible.
821:
822: if (fCaretIsVisible)
823: return fTextComponent.getBoundingRect(rangeStart,
824: rangeStart);
825: } else if (rangeStart.equals(newLimit)) {
826:
827: // range start is NEW insertion point.
828:
829: if (drawIfInsPoint)
830: return fTextComponent.getBoundingRect(rangeStart,
831: rangeStart);
832: }
833:
834: return null;
835: }
836:
837: private static boolean rectanglesOverlapVertically(Rectangle r1,
838: Rectangle r2) {
839:
840: if (r1 == null || r2 == null) {
841: return false;
842: }
843:
844: return r1.y <= r2.y + r2.height || r2.y <= r1.y + r1.height;
845: }
846:
847: // Update to show new selection, redrawing as little as possible
848:
849: private void updateSelectionDisplay(TextOffset oldStart,
850: TextOffset oldLimit, TextOffset newStart,
851: TextOffset newLimit, boolean drawIfInsPoint) {
852:
853: //System.out.println("newStart:" + newStart + "; newLimit:" + newLimit);
854:
855: TextOffset off[] = new TextOffset[4];
856:
857: off[0] = oldStart;
858: off[1] = oldLimit;
859: off[2] = newStart;
860: off[3] = newLimit;
861:
862: sortOffsets(off);
863:
864: Rectangle r1 = getSelectionChangeRect(off[0], off[1], oldStart,
865: oldLimit, newStart, newLimit, drawIfInsPoint);
866: Rectangle r2 = getSelectionChangeRect(off[2], off[3], oldStart,
867: oldLimit, newStart, newLimit, drawIfInsPoint);
868:
869: boolean drawSelection = drawIfInsPoint
870: || !newStart.equals(newLimit);
871:
872: if (rectanglesOverlapVertically(r1, r2)) {
873:
874: fTextComponent.drawText(fTextComponent.getGraphics(), r1
875: .union(r2), drawSelection, newStart, newLimit,
876: fHighlightColor);
877: } else {
878: if (r1 != null)
879: fTextComponent.drawText(fTextComponent.getGraphics(),
880: r1, drawSelection, newStart, newLimit,
881: fHighlightColor);
882: if (r2 != null)
883: fTextComponent.drawText(fTextComponent.getGraphics(),
884: r2, drawSelection, newStart, newLimit,
885: fHighlightColor);
886: }
887: }
888:
889: public void setSelRangeAndDraw(TextOffset newStart,
890: TextOffset newLimit, TextOffset newAnchor) {
891:
892: // if the old and new selection ranges are the same, don't do anything
893: if (fStart.equals(newStart) && fLimit.equals(newLimit)
894: && fAnchor.equals(newAnchor))
895: return;
896:
897: if (enabled())
898: stopCaretBlinking();
899:
900: // update the selection on screen if we're enabled and visible
901:
902: TextOffset oldStart = new TextOffset(fStart), oldLimit = new TextOffset(
903: fLimit);
904:
905: setSelectionRange(newStart, newLimit, newAnchor);
906:
907: if (enabled()) {
908:
909: // To supress drawing a caret during a mouse drag, pass !fMouseDown instead of true:
910: updateSelectionDisplay(oldStart, oldLimit, fStart, fLimit,
911: true);
912: }
913:
914: if (!fMouseDown && enabled())
915: restartCaretBlinking(true);
916: }
917:
918: public void stopCaretBlinking() {
919:
920: synchronized (this ) {
921: fCaretShouldBlink = false;
922: }
923: }
924:
925: /**
926: * Resume blinking the caret, if the selection is an insertion point.
927: * @param caretIsVisible true if the caret is displayed when this is called.
928: * This method relies on the client to display (or not display) the caret.
929: */
930: public void restartCaretBlinking(boolean caretIsVisible) {
931:
932: synchronized (this ) {
933: fCaretShouldBlink = fStart.equals(fLimit);
934: fCaretCount = 0;
935: fCaretIsVisible = caretIsVisible;
936:
937: if (fCaretShouldBlink) {
938: try {
939: notify();
940: } catch (IllegalMonitorStateException e) {
941: System.out
942: .println("Caught IllegalMonitorStateException: "
943: + e);
944: }
945: }
946: }
947: }
948:
949: public void removeFromOwner() {
950:
951: stopCaretBlinking();
952: super.removeFromOwner();
953: }
954:
955: }
|