]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/richtext/textpanel/TextSelection.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / richtext / textpanel / TextSelection.java
1 /*\r
2  * (C) Copyright IBM Corp. 1998-2005.  All Rights Reserved.\r
3  *\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
12  */\r
13 /*\r
14     7/1/97 - caret blinks\r
15 \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
18 */\r
19 \r
20 package com.ibm.richtext.textpanel;\r
21 \r
22 import java.awt.Graphics;\r
23 import java.awt.Color;\r
24 import java.awt.Rectangle;\r
25 \r
26 import java.text.BreakIterator;\r
27 \r
28 import java.awt.event.MouseEvent;\r
29 import java.awt.event.KeyEvent;\r
30 import java.awt.event.FocusEvent;\r
31 \r
32 import com.ibm.richtext.styledtext.MConstText;\r
33 import com.ibm.richtext.textformat.TextOffset;\r
34 \r
35 import com.ibm.richtext.textformat.MFormatter;\r
36 \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
41 \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
54     \r
55     private boolean fCaretShouldBlink;\r
56     private boolean fCaretIsVisible;\r
57     private int fCaretCount;\r
58 \r
59     // formerly in base class\r
60     private boolean fEnabled;\r
61 \r
62     private MouseEvent fPendingMouseEvent = null;\r
63 \r
64     private static final int kCaretInterval = 500;\r
65 \r
66     public void run() {\r
67 \r
68         final Runnable blinkCaret = new Runnable() {\r
69             public void run() {\r
70                 fCaretIsVisible = !fCaretIsVisible;\r
71                 Graphics g = fTextComponent.getGraphics();\r
72                 if (g != null) {\r
73                     //System.out.println("caretIsVisible: " + fCaretIsVisible);\r
74                     drawSelection(g, fCaretIsVisible);\r
75                 }\r
76                 else {\r
77                     // Not sure what else to do:\r
78                     fCaretShouldBlink = false;\r
79                 }\r
80             }\r
81         };\r
82         \r
83         // blink caret\r
84         while (true) {\r
85 \r
86             synchronized(this) {\r
87 \r
88                 while (!fCaretShouldBlink) {\r
89                     try {\r
90                         wait();\r
91                     }\r
92                     catch(InterruptedException e) {\r
93                         System.out.println("Caught InterruptedException in caret thread.");\r
94                     }\r
95                 }\r
96 \r
97                 ++fCaretCount;\r
98 \r
99                 if (fCaretCount % 2 == 0) {\r
100                     fRunStrategy.doIt(blinkCaret);\r
101                 }\r
102             }\r
103 \r
104             try {\r
105                 Thread.sleep(kCaretInterval);\r
106             }\r
107             catch(InterruptedException e) {\r
108             }\r
109         }\r
110     }\r
111 \r
112 \r
113 \r
114     public TextSelection(TextComponent textComponent,\r
115                          PanelEventBroadcaster listener,\r
116                          RunStrategy runStrategy) {\r
117                             \r
118         fTextComponent = textComponent;\r
119         fText = textComponent.getText();\r
120         fListener = listener;\r
121         fRunStrategy = runStrategy;\r
122         \r
123         fStart = new TextOffset();\r
124         fLimit = new TextOffset();\r
125         fAnchor = new TextOffset();\r
126         fMouseDown = false;\r
127 \r
128         fCaretCount = 0;\r
129         fCaretIsVisible = true;\r
130         fCaretShouldBlink = false;\r
131         setEnabled(false);\r
132 \r
133         Thread caretThread = new Thread(this);\r
134         caretThread.setDaemon(true);\r
135         caretThread.start();\r
136     }\r
137 \r
138     boolean enabled() {\r
139 \r
140         return fEnabled;\r
141     }\r
142 \r
143     private void setEnabled(boolean enabled) {\r
144 \r
145         fEnabled = enabled;\r
146     }\r
147 \r
148     public boolean textControlEventOccurred(Behavior.EventType event, Object what) {\r
149 \r
150         boolean result;\r
151         fHandlingKeyOrCommand = true;\r
152         \r
153         if (event == Behavior.SELECT) {\r
154             select((TextRange) what);\r
155             result = true;\r
156         }\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
160             result = true;\r
161         }\r
162         else {\r
163             result = false;\r
164         }\r
165         \r
166         fHandlingKeyOrCommand = false;\r
167         return result;\r
168     }\r
169 \r
170     protected void advanceToNextBoundary(TextOffset offset) {\r
171     \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
175             return;\r
176         }\r
177         \r
178         int position = offset.fOffset;\r
179         \r
180         if (position >= fText.length()) {\r
181             return;\r
182         }\r
183 \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
189             return;\r
190         }\r
191         \r
192         offset.setOffset(nextPos, TextOffset.AFTER_OFFSET);\r
193     }\r
194 \r
195     protected void advanceToPreviousBoundary(TextOffset offset) {\r
196     \r
197         advanceToPreviousBoundary(offset, false);\r
198     }\r
199     \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
204             return;\r
205         }\r
206         \r
207         int position = offset.fOffset;\r
208 \r
209         if (position == 0) {\r
210             return;\r
211         }\r
212         \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
217         }\r
218         else {\r
219             fBoundaries.following(position);\r
220         }\r
221         \r
222         int prevPos = fBoundaries.previous();\r
223         \r
224         if (prevPos == position) {\r
225             if (!alwaysMove && offset.fPlacement==TextOffset.AFTER_OFFSET) {\r
226                 return;\r
227             }\r
228 \r
229             prevPos = fBoundaries.previous();\r
230         }\r
231                 \r
232         // and finally update the real offset with this new position we've found\r
233         offset.setOffset(prevPos, TextOffset.AFTER_OFFSET);\r
234     }\r
235 \r
236     private void doArrowKey(KeyEvent e, int key) {\r
237 \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
242 \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
246             else\r
247                 setSelRangeAndDraw(fLimit, fLimit, fLimit);\r
248         }\r
249         else {\r
250             if (!fAnchor.equals(fStart))\r
251                 fAnchor.assign(fLimit);\r
252 \r
253             TextOffset  liveEnd = (fStart.equals(fAnchor)) ? fLimit : fStart;\r
254             TextOffset  newPos = new TextOffset();\r
255 \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
263 \r
264                 newPos.assign(liveEnd);\r
265                 if (key == KeyEvent.VK_RIGHT)\r
266                     advanceToNextBoundary(newPos);\r
267                 else\r
268                     advanceToPreviousBoundary(newPos, true);\r
269             }\r
270 \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
273             else {\r
274 \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
280                 else {\r
281                     if (fUpDownAnchor == null) {\r
282                         fUpDownAnchor = new TextOffset(liveEnd);\r
283                     }\r
284                 }\r
285 \r
286                 short   direction = MFormatter.eRight;  // just to have a default...\r
287 \r
288                 switch (key) {\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
293                 }\r
294 \r
295                 // use the formatter to determine the actual effect of the arrow key\r
296                 fTextComponent.findNewInsertionOffset(newPos, fUpDownAnchor, liveEnd, direction);\r
297             }\r
298 \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
305             else {\r
306                 if (newPos.lessThan(fAnchor))\r
307                     setSelRangeAndDraw(newPos, fAnchor, fAnchor);\r
308                 else\r
309                     setSelRangeAndDraw(fAnchor, newPos, fAnchor);\r
310             }\r
311         }\r
312 \r
313         scrollToShowSelectionEnd();\r
314         fBoundaries = null;\r
315     }\r
316 \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
320         // of the document\r
321 \r
322         TextOffset activeEnd, anchor;\r
323 \r
324         if (fAnchor.equals(fStart)) {\r
325             activeEnd = new TextOffset(fStart);\r
326             anchor = new TextOffset(fLimit);\r
327         }\r
328         else {\r
329             activeEnd = new TextOffset(fLimit);\r
330             anchor = new TextOffset(fStart);\r
331         }\r
332 \r
333         if (e.isControlDown()) {\r
334             TextOffset end = new TextOffset(fText.length(), TextOffset.BEFORE_OFFSET);\r
335 \r
336             if (e.isShiftDown())\r
337                 setSelRangeAndDraw(anchor, end, anchor);\r
338             else\r
339                 setSelRangeAndDraw(end, end, end);\r
340         }\r
341 \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
346 \r
347         else {\r
348 \r
349             int oldOffset = activeEnd.fOffset;\r
350 \r
351             activeEnd.fOffset = fTextComponent.lineRangeLimit(fTextComponent.lineContaining(activeEnd));\r
352             activeEnd.fPlacement = TextOffset.BEFORE_OFFSET;\r
353 \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
358             }\r
359 \r
360             if (!e.isShiftDown())\r
361                 setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);\r
362             else {\r
363                 if (activeEnd.lessThan(anchor))\r
364                     setSelRangeAndDraw(activeEnd, anchor, anchor);\r
365                 else\r
366                     setSelRangeAndDraw(anchor, activeEnd, anchor);\r
367             }\r
368         }\r
369 \r
370         scrollToShowSelectionEnd();\r
371         fBoundaries = null;\r
372         fUpDownAnchor = null;\r
373     }\r
374 \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
378         // of the document\r
379 \r
380         TextOffset activeEnd, anchor;\r
381 \r
382         if (fAnchor.equals(fStart)) {\r
383             activeEnd = new TextOffset(fStart);\r
384             anchor = new TextOffset(fLimit);\r
385         }\r
386         else {\r
387             activeEnd = new TextOffset(fLimit);\r
388             anchor = new TextOffset(fStart);\r
389         }\r
390 \r
391         if (e.isControlDown()) {\r
392 \r
393             TextOffset start = new TextOffset(0, TextOffset.AFTER_OFFSET);\r
394             if (e.isShiftDown())\r
395                 setSelRangeAndDraw(start, anchor, anchor);\r
396             else\r
397                 setSelRangeAndDraw(start, start, start);\r
398         }\r
399 \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
404 \r
405         else {\r
406 \r
407             activeEnd.fOffset = fTextComponent.lineRangeLow(fTextComponent.lineContaining(activeEnd));\r
408             activeEnd.fPlacement = TextOffset.AFTER_OFFSET;\r
409 \r
410             if (!e.isShiftDown())\r
411                 setSelRangeAndDraw(activeEnd, activeEnd, activeEnd);\r
412             else {\r
413                 if (activeEnd.lessThan(anchor))\r
414                     setSelRangeAndDraw(activeEnd, anchor, anchor);\r
415                 else\r
416                     setSelRangeAndDraw(anchor, activeEnd, anchor);\r
417             }\r
418         }\r
419 \r
420         scrollToShowSelectionEnd();\r
421         fBoundaries = null;\r
422         fUpDownAnchor = null;\r
423     }\r
424 \r
425     /** draws or erases the current selection\r
426     * Draws or erases the highlight region or insertion caret for the current selection\r
427     * range.\r
428     * @param g The graphics environment to draw into\r
429     * @param visible If true, draw the selection; if false, erase it\r
430     */\r
431     protected void drawSelection(Graphics g, boolean visible) {\r
432         drawSelectionRange(g, fStart, fLimit, visible);\r
433     }\r
434 \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
442     */\r
443     protected void drawSelectionRange(  Graphics    g,\r
444                                         TextOffset  start,\r
445                                         TextOffset  limit,\r
446                                         boolean     visible) {\r
447         if (g == null) {\r
448             return;\r
449         }\r
450         Rectangle   selBounds = fTextComponent.getBoundingRect(start, limit);\r
451 \r
452         selBounds.width = Math.max(1, selBounds.width);\r
453         selBounds.height = Math.max(1, selBounds.height);\r
454 \r
455         fTextComponent.drawText(g, selBounds, visible, start, limit, fHighlightColor);\r
456     }\r
457 \r
458     protected TextOffset getAnchor() {\r
459         return fAnchor;\r
460     }\r
461 \r
462     public TextOffset getEnd() {\r
463         return fLimit;\r
464     }\r
465 \r
466     public Color getHighlightColor() {\r
467         return fHighlightColor;\r
468     }\r
469 \r
470     public TextOffset getStart() {\r
471         return fStart;\r
472     }\r
473 \r
474     public TextRange getSelectionRange() {\r
475 \r
476         return new TextRange(fStart.fOffset, fLimit.fOffset);\r
477     }\r
478 \r
479     public boolean focusGained(FocusEvent e) {\r
480 \r
481         setEnabled(true);\r
482         drawSelection(fTextComponent.getGraphics(), true);\r
483 \r
484         restartCaretBlinking(true);\r
485         if (fPendingMouseEvent != null) {\r
486             mousePressed(fPendingMouseEvent);\r
487             fPendingMouseEvent = null;\r
488         }\r
489         fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED);\r
490  \r
491         return true;\r
492     }\r
493 \r
494     public boolean focusLost(FocusEvent e) {\r
495         stopCaretBlinking();\r
496         setEnabled(false);\r
497         drawSelection(fTextComponent.getGraphics(), false);\r
498         return true;\r
499     }\r
500 \r
501     /**\r
502      * Return true if the given key event can affect the selection\r
503      * range.\r
504      */\r
505     public static boolean keyAffectsSelection(KeyEvent e) {\r
506 \r
507         if (e.getID() != KeyEvent.KEY_PRESSED) {\r
508             return false;\r
509         }\r
510 \r
511         int key = e.getKeyCode();\r
512 \r
513         switch (key) {\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
520                 return true;\r
521 \r
522             default:\r
523                 return false;\r
524         }\r
525     }\r
526 \r
527     public boolean keyPressed(KeyEvent e) {\r
528 \r
529         fHandlingKeyOrCommand = true;\r
530         int key = e.getKeyCode();\r
531         boolean result = true;\r
532         \r
533         switch (key) {\r
534             case KeyEvent.VK_HOME:\r
535                 doHomeKey(e);\r
536                 break;\r
537                 \r
538             case KeyEvent.VK_END:\r
539                 doEndKey(e);\r
540                 break;\r
541 \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
547                 break;\r
548                 \r
549             default:\r
550                 fUpDownAnchor = null;\r
551                 result = false;\r
552                 break;\r
553         }\r
554         \r
555         fHandlingKeyOrCommand = false;\r
556         return result;\r
557     }\r
558 \r
559     public boolean mousePressed(MouseEvent e) {\r
560 \r
561         if (!enabled()) {\r
562             fPendingMouseEvent = e;\r
563             fTextComponent.requestFocus();\r
564             return false;\r
565         }\r
566 \r
567         if (fMouseDown)\r
568             throw new Error("fMouseDown is out of sync with mouse in TextSelection.");\r
569 \r
570         fMouseDown = true;\r
571         stopCaretBlinking();\r
572 \r
573         int x = e.getX(), y = e.getY();\r
574         boolean wasZeroLength = rangeIsZeroLength(fStart, fLimit, fAnchor);\r
575         \r
576         TextOffset current = fTextComponent.pointToTextOffset(null, x, y, null, true);\r
577         TextOffset anchorStart = new TextOffset();\r
578         TextOffset anchorEnd = new TextOffset();\r
579 \r
580         fUpDownAnchor = null;\r
581 \r
582         // if we're not extending the selection...\r
583         if (!e.isShiftDown()) {\r
584 \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
587             // object)\r
588             if (e.getClickCount() == 2)\r
589                 fBoundaries = BreakIterator.getWordInstance();\r
590             else if (e.getClickCount() == 3)\r
591                 fBoundaries = BreakIterator.getSentenceInstance();\r
592             else\r
593                 fBoundaries = null;\r
594 \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
599 \r
600             anchorStart.assign(current);\r
601             advanceToPreviousBoundary(anchorStart);\r
602             anchorEnd.assign(current);\r
603             advanceToNextBoundary(anchorEnd);\r
604         }\r
605 \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
609 \r
610         else {\r
611 \r
612             if (fBoundaries != null)\r
613                 fBoundaries.setText(fText.createCharacterIterator());\r
614 \r
615             anchorStart.assign(fAnchor);\r
616             anchorEnd.assign(anchorStart);\r
617 \r
618             advanceToNextBoundary(anchorEnd);\r
619         }\r
620 \r
621         SelectionDragInteractor interactor = new SelectionDragInteractor(this, \r
622                                                                          fTextComponent,\r
623                                                                          fRunStrategy,\r
624                                                                          anchorStart,\r
625                                                                          anchorEnd,\r
626                                                                          current,\r
627                                                                          x,\r
628                                                                          y,\r
629                                                                          wasZeroLength);\r
630 \r
631         interactor.addToOwner(fTextComponent);\r
632 \r
633         return true;\r
634     }\r
635 \r
636     public boolean mouseReleased(MouseEvent e) {\r
637 \r
638         fPendingMouseEvent = null;\r
639         return false;\r
640     }\r
641 \r
642     // drag interactor calls this\r
643     void mouseReleased(boolean zeroLengthChange) {\r
644 \r
645         fMouseDown = false;\r
646 \r
647         if (zeroLengthChange) {\r
648             fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);\r
649         }\r
650         fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);\r
651         fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);\r
652 \r
653         // if caret drawing during mouse drags is supressed, draw caret now.\r
654 \r
655         restartCaretBlinking(true);\r
656     }\r
657 \r
658 \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
664     */\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
667         if (!enabled())\r
668             return false;\r
669 \r
670         fTextComponent.drawText(g, drawRect, true, fStart, fLimit, fHighlightColor);\r
671         return true;\r
672     }\r
673 \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
676     */\r
677     public void scrollToShowSelection() {\r
678         Rectangle   selRect = fTextComponent.getBoundingRect(fStart, fLimit);\r
679 \r
680         fTextComponent.scrollToShow(selRect);\r
681     }\r
682 \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
685     */\r
686     public void scrollToShowSelectionEnd() {\r
687         TextOffset  liveEnd;\r
688         // variable not used Point[]     points;\r
689         Rectangle   caret;\r
690 \r
691         if (fAnchor.equals(fStart))\r
692             liveEnd = fLimit;\r
693         else\r
694             liveEnd = fStart;\r
695 \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
701     }\r
702 \r
703     private void select(TextRange range) {\r
704         // variable not used int textLength = fTextComponent.getText().length();\r
705 \r
706         TextOffset start = new TextOffset(range.start);\r
707 \r
708         stopCaretBlinking();\r
709         setSelRangeAndDraw(start, new TextOffset(range.limit), start);\r
710         restartCaretBlinking(true);\r
711     }\r
712 \r
713     public void setHighlightColor(Color newColor) {\r
714         fHighlightColor = newColor;\r
715         if (enabled())\r
716             drawSelection(fTextComponent.getGraphics(), true);\r
717     }\r
718     \r
719     static boolean rangeIsZeroLength(TextOffset start, TextOffset limit, TextOffset anchor) {\r
720         \r
721         return start.fOffset == limit.fOffset && anchor.fOffset == limit.fOffset;\r
722     }\r
723 \r
724     // sigh... look out for aliasing\r
725     public void setSelectionRange(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) {\r
726 \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
732         }\r
733         else {\r
734             tempNewAnchor = newAnchor;\r
735         }\r
736 \r
737         // DEBUG {jbr}\r
738 \r
739         if (newStart.greaterThan(newLimit))\r
740             throw new IllegalArgumentException("Selection limit is before selection start.");\r
741 \r
742         if (newLimit != fStart) {\r
743             fStart.assign(newStart);\r
744             fLimit.assign(newLimit);\r
745         }\r
746         else {\r
747             fLimit.assign(newLimit);\r
748             fStart.assign(newStart);\r
749         }\r
750 \r
751         fAnchor.assign(tempNewAnchor);\r
752 \r
753         if (fStart.fOffset == fLimit.fOffset) {\r
754             fStart.fPlacement = fAnchor.fPlacement;\r
755             fLimit.fPlacement = fAnchor.fPlacement;\r
756         }\r
757         \r
758         if (!fMouseDown) {\r
759             if (zeroLengthChange) {\r
760                 fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED);\r
761             }\r
762             fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED);\r
763             if (fHandlingKeyOrCommand) {\r
764                 fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED);\r
765             }\r
766         }\r
767     }\r
768 \r
769     private void sortOffsets(TextOffset offsets[]) {\r
770 \r
771         int i, j;\r
772 \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
778                     offsets[i] = temp;\r
779                 }\r
780             }\r
781         }\r
782 \r
783         // DEBUG {jbr}\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
787     }\r
788 \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
794 \r
795         if (!rangeStart.equals(rangeLimit))\r
796             return fTextComponent.getBoundingRect(rangeStart, rangeLimit);\r
797 \r
798         // here, rangeStart and rangeLimit are equal\r
799 \r
800         if (rangeStart.equals(oldLimit)) {\r
801 \r
802             // range start is OLD insertion point.  Redraw if caret is currently visible.\r
803 \r
804             if (fCaretIsVisible)\r
805                 return fTextComponent.getBoundingRect(rangeStart, rangeStart);\r
806         }\r
807         else if (rangeStart.equals(newLimit)) {\r
808 \r
809             // range start is NEW insertion point.\r
810 \r
811             if (drawIfInsPoint)\r
812                 return fTextComponent.getBoundingRect(rangeStart, rangeStart);\r
813         }\r
814 \r
815         return null;\r
816     }\r
817 \r
818     private static boolean rectanglesOverlapVertically(Rectangle r1, Rectangle r2) {\r
819         \r
820         if (r1 == null || r2 == null) {\r
821             return false;\r
822         }\r
823         \r
824         return r1.y <= r2.y + r2.height || r2.y <= r1.y + r1.height;\r
825     }\r
826     \r
827     // Update to show new selection, redrawing as little as possible\r
828 \r
829     private void updateSelectionDisplay(\r
830                             TextOffset oldStart, TextOffset oldLimit,\r
831                             TextOffset newStart, TextOffset newLimit, boolean drawIfInsPoint) {\r
832 \r
833         //System.out.println("newStart:" + newStart + "; newLimit:" + newLimit);\r
834 \r
835         TextOffset off[] = new TextOffset[4];\r
836 \r
837         off[0] = oldStart;\r
838         off[1] = oldLimit;\r
839         off[2] = newStart;\r
840         off[3] = newLimit;\r
841 \r
842         sortOffsets(off);\r
843 \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
846 \r
847         boolean drawSelection = drawIfInsPoint || !newStart.equals(newLimit);\r
848 \r
849         if (rectanglesOverlapVertically(r1, r2)) {\r
850 \r
851             fTextComponent.drawText(fTextComponent.getGraphics(), r1.union(r2), drawSelection, newStart, newLimit, fHighlightColor);\r
852         }\r
853         else {\r
854             if (r1 != null)\r
855                 fTextComponent.drawText(fTextComponent.getGraphics(), r1, drawSelection, newStart, newLimit, fHighlightColor);\r
856             if (r2 != null)\r
857                 fTextComponent.drawText(fTextComponent.getGraphics(), r2, drawSelection, newStart, newLimit, fHighlightColor);\r
858         }\r
859     }\r
860 \r
861     public void setSelRangeAndDraw(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) {\r
862 \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
865             return;\r
866 \r
867         if (enabled())\r
868             stopCaretBlinking();\r
869 \r
870         // update the selection on screen if we're enabled and visible\r
871 \r
872         TextOffset oldStart = new TextOffset(fStart), oldLimit = new TextOffset(fLimit);\r
873 \r
874         setSelectionRange(newStart, newLimit, newAnchor);\r
875 \r
876         if (enabled()) {\r
877 \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
880         }\r
881 \r
882         if (!fMouseDown && enabled())\r
883             restartCaretBlinking(true);\r
884     }\r
885 \r
886     public void stopCaretBlinking() {\r
887 \r
888         synchronized(this) {\r
889             fCaretShouldBlink = false;\r
890         }\r
891     }\r
892 \r
893 /**\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
897 */\r
898     public void restartCaretBlinking(boolean caretIsVisible) {\r
899 \r
900         synchronized(this) {\r
901             fCaretShouldBlink = fStart.equals(fLimit);\r
902             fCaretCount = 0;\r
903             fCaretIsVisible = caretIsVisible;\r
904 \r
905             if (fCaretShouldBlink) {\r
906                 try {\r
907                     notify();\r
908                 }\r
909                 catch (IllegalMonitorStateException e) {\r
910                     System.out.println("Caught IllegalMonitorStateException: "+e);\r
911                 }\r
912             }\r
913         }\r
914     }\r
915 \r
916     public void removeFromOwner() {\r
917 \r
918         stopCaretBlinking();\r
919         super.removeFromOwner();\r
920     }\r
921 \r
922 }\r