| /* |
| * (C) Copyright IBM Corp. 1998-2004. All Rights Reserved. |
| * |
| * The program is provided "as is" without any warranty express or |
| * implied, including the warranty of non-infringement and the implied |
| * warranties of merchantibility and fitness for a particular purpose. |
| * IBM will not be liable for any damages suffered by you as a result |
| * of using the Program. In no event will IBM be liable for any |
| * special, indirect or consequential damages or lost profits even if |
| * IBM has been advised of the possibility of their occurrence. IBM |
| * will not be liable for any third party claims against you. |
| */ |
| /* |
| 7/1/97 - caret blinks |
| |
| 7/3/97 - fAnchor is no longer restricted to the start or end of the selection. {jbr} |
| Also, removed fVisible - it was identical to enabled(). |
| */ |
| |
| package com.ibm.richtext.textpanel; |
| |
| import java.awt.Graphics; |
| import java.awt.Color; |
| import java.awt.Rectangle; |
| |
| import java.text.BreakIterator; |
| |
| import java.awt.event.MouseEvent; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.FocusEvent; |
| |
| import com.ibm.richtext.styledtext.MConstText; |
| import com.ibm.richtext.textformat.TextOffset; |
| |
| import com.ibm.richtext.textformat.MFormatter; |
| |
| class TextSelection extends Behavior implements Runnable { |
| static final String COPYRIGHT = |
| "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved"; |
| static final Color HIGHLIGHTCOLOR = Color.pink; |
| |
| private TextComponent fTextComponent; |
| private MConstText fText; |
| private TextOffset fStart; |
| private TextOffset fLimit; |
| private TextOffset fAnchor; |
| private TextOffset fUpDownAnchor = null; |
| private BreakIterator fBoundaries = null; |
| private Color fHighlightColor = HIGHLIGHTCOLOR; |
| private PanelEventBroadcaster fListener; |
| private RunStrategy fRunStrategy; |
| private boolean fMouseDown = false; |
| private boolean fHandlingKeyOrCommand = false; |
| |
| private boolean fCaretShouldBlink; |
| private boolean fCaretIsVisible; |
| private int fCaretCount; |
| |
| // formerly in base class |
| private boolean fEnabled; |
| |
| private MouseEvent fPendingMouseEvent = null; |
| |
| private static final int kCaretInterval = 500; |
| |
| public void run() { |
| |
| final Runnable blinkCaret = new Runnable() { |
| public void run() { |
| fCaretIsVisible = !fCaretIsVisible; |
| Graphics g = fTextComponent.getGraphics(); |
| if (g != null) { |
| //System.out.println("caretIsVisible: " + fCaretIsVisible); |
| drawSelection(g, fCaretIsVisible); |
| } |
| else { |
| // Not sure what else to do: |
| fCaretShouldBlink = false; |
| } |
| } |
| }; |
| |
| // blink caret |
| while (true) { |
| |
| synchronized(this) { |
| |
| while (!fCaretShouldBlink) { |
| try { |
| wait(); |
| } |
| catch(InterruptedException e) { |
| System.out.println("Caught InterruptedException in caret thread."); |
| } |
| } |
| |
| ++fCaretCount; |
| |
| if (fCaretCount % 2 == 0) { |
| fRunStrategy.doIt(blinkCaret); |
| } |
| } |
| |
| try { |
| Thread.sleep(kCaretInterval); |
| } |
| catch(InterruptedException e) { |
| } |
| } |
| } |
| |
| |
| |
| public TextSelection(TextComponent textComponent, |
| PanelEventBroadcaster listener, |
| RunStrategy runStrategy) { |
| |
| fTextComponent = textComponent; |
| fText = textComponent.getText(); |
| fListener = listener; |
| fRunStrategy = runStrategy; |
| |
| fStart = new TextOffset(); |
| fLimit = new TextOffset(); |
| fAnchor = new TextOffset(); |
| fMouseDown = false; |
| |
| fCaretCount = 0; |
| fCaretIsVisible = true; |
| fCaretShouldBlink = false; |
| setEnabled(false); |
| |
| Thread caretThread = new Thread(this); |
| caretThread.setDaemon(true); |
| caretThread.start(); |
| } |
| |
| boolean enabled() { |
| |
| return fEnabled; |
| } |
| |
| private void setEnabled(boolean enabled) { |
| |
| fEnabled = enabled; |
| } |
| |
| public boolean textControlEventOccurred(Behavior.EventType event, Object what) { |
| |
| boolean result; |
| fHandlingKeyOrCommand = true; |
| |
| if (event == Behavior.SELECT) { |
| select((TextRange) what); |
| result = true; |
| } |
| else if (event == Behavior.COPY) { |
| fTextComponent.getClipboard().setContents(fText.extract(fStart.fOffset, fLimit.fOffset)); |
| fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED); |
| result = true; |
| } |
| else { |
| result = false; |
| } |
| |
| fHandlingKeyOrCommand = false; |
| return result; |
| } |
| |
| protected void advanceToNextBoundary(TextOffset offset) { |
| |
| // If there's no boundaries object, or if position at the end of the |
| // document, return the offset unchanged |
| if (fBoundaries == null) { |
| return; |
| } |
| |
| int position = offset.fOffset; |
| |
| if (position >= fText.length()) { |
| return; |
| } |
| |
| // If position is at a boundary and offset is before position, |
| // leave it unchanged. Otherwise move to next boundary. |
| int nextPos = fBoundaries.following(position); |
| if (fBoundaries.previous() == position && |
| offset.fPlacement==TextOffset.BEFORE_OFFSET) { |
| return; |
| } |
| |
| offset.setOffset(nextPos, TextOffset.AFTER_OFFSET); |
| } |
| |
| protected void advanceToPreviousBoundary(TextOffset offset) { |
| |
| advanceToPreviousBoundary(offset, false); |
| } |
| |
| private void advanceToPreviousBoundary(TextOffset offset, boolean alwaysMove) { |
| // if there's no boundaries object, or if we're sitting at the beginning |
| // of the document, return the offset unchanged |
| if (fBoundaries == null) { |
| return; |
| } |
| |
| int position = offset.fOffset; |
| |
| if (position == 0) { |
| return; |
| } |
| |
| // If position is at a boundary, leave it unchanged. Otherwise |
| // move to previous boundary. |
| if (position == fText.length()) { |
| fBoundaries.last(); |
| } |
| else { |
| fBoundaries.following(position); |
| } |
| |
| int prevPos = fBoundaries.previous(); |
| |
| if (prevPos == position) { |
| if (!alwaysMove && offset.fPlacement==TextOffset.AFTER_OFFSET) { |
| return; |
| } |
| |
| prevPos = fBoundaries.previous(); |
| } |
| |
| // and finally update the real offset with this new position we've found |
| offset.setOffset(prevPos, TextOffset.AFTER_OFFSET); |
| } |
| |
| private void doArrowKey(KeyEvent e, int key) { |
| |
| // when there's a selection range, the left and up arrow keys place an |
| // insertion point at the beginning of the range, and the right and down |
| // keys place an insertion point at the end of the range (unless the shift |
| // key is down, of course) |
| |
| if (!fStart.equals(fLimit) && !e.isShiftDown()) { |
| if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_UP) |
| setSelRangeAndDraw(fStart, fStart, fStart); |
| else |
| setSelRangeAndDraw(fLimit, fLimit, fLimit); |
| } |
| else { |
| if (!fAnchor.equals(fStart)) |
| fAnchor.assign(fLimit); |
| |
| TextOffset liveEnd = (fStart.equals(fAnchor)) ? fLimit : fStart; |
| TextOffset newPos = new TextOffset(); |
| |
| // if the control key is down, the left and right arrow keys move by whole |
| // word in the appropriate direction (we use a line break object so that we're |
| // not treating spaces and punctuation as words) |
| if (e.isControlDown() && (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT)) { |
| fUpDownAnchor = null; |
| fBoundaries = BreakIterator.getLineInstance(); |
| fBoundaries.setText(fText.createCharacterIterator()); |
| |
| newPos.assign(liveEnd); |
| if (key == KeyEvent.VK_RIGHT) |
| advanceToNextBoundary(newPos); |
| else |
| advanceToPreviousBoundary(newPos, true); |
| } |
| |
| // if we get down to here, this is a plain-vanilla insertion-point move, |
| // or the shift key is down and we're extending or shortening the selection |
| else { |
| |
| // fUpDownAnchor is used to keep track of the horizontal position |
| // across a run of up or down arrow keys (this prevents accumulated |
| // error from destroying our horizontal position) |
| if (key == KeyEvent.VK_LEFT || key == KeyEvent.VK_RIGHT) |
| fUpDownAnchor = null; |
| else { |
| if (fUpDownAnchor == null) { |
| fUpDownAnchor = new TextOffset(liveEnd); |
| } |
| } |
| |
| short direction = MFormatter.eRight; // just to have a default... |
| |
| switch (key) { |
| case KeyEvent.VK_UP: direction = MFormatter.eUp; break; |
| case KeyEvent.VK_DOWN: direction = MFormatter.eDown; break; |
| case KeyEvent.VK_LEFT: direction = MFormatter.eLeft; break; |
| case KeyEvent.VK_RIGHT: direction = MFormatter.eRight; break; |
| } |
| |
| // use the formatter to determine the actual effect of the arrow key |
| fTextComponent.findNewInsertionOffset(newPos, fUpDownAnchor, liveEnd, direction); |
| } |
| |
| // if the shift key is down, the selection range is from the anchor point |
| // the site of the last insertion point or the beginning point of the last |
| // selection drag operation) to the newly-calculated position; if the |
| // shift key is down, the newly-calculated position is the insertion point position |
| if (!e.isShiftDown()) |
| setSelRangeAndDraw(newPos, newPos, newPos); |
| else { |
| if (newPos.lessThan(fAnchor)) |
| setSelRangeAndDraw(newPos, fAnchor, fAnchor); |
| else |
| setSelRangeAndDraw(fAnchor, newPos, fAnchor); |
| } |
| } |
| |
| scrollToShowSelectionEnd(); |
| fBoundaries = null; |
| } |
| |
| private void doEndKey(KeyEvent e) { |
| // ctrl-end moves the insertsion point to the end of the document, |
| // ctrl-shift-end extends the selection so that it ends at the end |
| // of the document |
| |
| TextOffset activeEnd, anchor; |
| |
| if (fAnchor.equals(fStart)) { |
| activeEnd = new TextOffset(fStart); |
| anchor = new TextOffset(fLimit); |
| } |
| else { |
| activeEnd = new TextOffset(fLimit); |
| anchor = new TextOffset(fStart); |
| } |
| |
| if (e.isControlDown()) { |
| TextOffset end = new TextOffset(fText.length(), TextOffset.BEFORE_OFFSET); |
| |
| if (e.isShiftDown()) |
| setSelRangeAndDraw(anchor, end, anchor); |
| else |
| setSelRangeAndDraw(end, end, end); |
| } |
| |
| // end moves the insertion point to the end of the line containing |
| // the end of the current selection |
| // shift-end extends the selection to the end of the line containing |
| // the end of the current selection |
| |
| else { |
| |
| int oldOffset = activeEnd.fOffset; |
| |
| activeEnd.fOffset = fTextComponent.lineRangeLimit(fTextComponent.lineContaining(activeEnd)); |
| activeEnd.fPlacement = TextOffset.BEFORE_OFFSET; |
| |
| if (fText.paragraphLimit(oldOffset) == activeEnd.fOffset && |
| activeEnd.fOffset != fText.length() && activeEnd.fOffset > oldOffset) { |
| activeEnd.fOffset--; |
| activeEnd.fPlacement = TextOffset.AFTER_OFFSET; |
| } |
| |
| if (!e.isShiftDown()) |
| setSelRangeAndDraw(activeEnd, activeEnd, activeEnd); |
| else { |
| if (activeEnd.lessThan(anchor)) |
| setSelRangeAndDraw(activeEnd, anchor, anchor); |
| else |
| setSelRangeAndDraw(anchor, activeEnd, anchor); |
| } |
| } |
| |
| scrollToShowSelectionEnd(); |
| fBoundaries = null; |
| fUpDownAnchor = null; |
| } |
| |
| private void doHomeKey(KeyEvent e) { |
| // ctrl-home moves the insertion point to the beginning of the document, |
| // ctrl-shift-home extends the selection so that it begins at the beginning |
| // of the document |
| |
| TextOffset activeEnd, anchor; |
| |
| if (fAnchor.equals(fStart)) { |
| activeEnd = new TextOffset(fStart); |
| anchor = new TextOffset(fLimit); |
| } |
| else { |
| activeEnd = new TextOffset(fLimit); |
| anchor = new TextOffset(fStart); |
| } |
| |
| if (e.isControlDown()) { |
| |
| TextOffset start = new TextOffset(0, TextOffset.AFTER_OFFSET); |
| if (e.isShiftDown()) |
| setSelRangeAndDraw(start, anchor, anchor); |
| else |
| setSelRangeAndDraw(start, start, start); |
| } |
| |
| // home moves the insertion point to the beginning of the line containing |
| // the beginning of the current selection |
| // shift-home extends the selection to the beginning of the line containing |
| // the beginning of the current selection |
| |
| else { |
| |
| activeEnd.fOffset = fTextComponent.lineRangeLow(fTextComponent.lineContaining(activeEnd)); |
| activeEnd.fPlacement = TextOffset.AFTER_OFFSET; |
| |
| if (!e.isShiftDown()) |
| setSelRangeAndDraw(activeEnd, activeEnd, activeEnd); |
| else { |
| if (activeEnd.lessThan(anchor)) |
| setSelRangeAndDraw(activeEnd, anchor, anchor); |
| else |
| setSelRangeAndDraw(anchor, activeEnd, anchor); |
| } |
| } |
| |
| scrollToShowSelectionEnd(); |
| fBoundaries = null; |
| fUpDownAnchor = null; |
| } |
| |
| /** draws or erases the current selection |
| * Draws or erases the highlight region or insertion caret for the current selection |
| * range. |
| * @param g The graphics environment to draw into |
| * @param visible If true, draw the selection; if false, erase it |
| */ |
| protected void drawSelection(Graphics g, boolean visible) { |
| drawSelectionRange(g, fStart, fLimit, visible); |
| } |
| |
| /** draws or erases a selection highlight at the specfied positions |
| * Draws or erases a selection highlight or insertion caret corresponding to |
| * the specified selecion range |
| * @param g The graphics environment to draw into. If null, this method does nothing. |
| * @param start The beginning of the range to highlight |
| * @param limit The end of the range to highlight |
| * @param vsible If true, draw; if false, erase |
| */ |
| protected void drawSelectionRange( Graphics g, |
| TextOffset start, |
| TextOffset limit, |
| boolean visible) { |
| if (g == null) { |
| return; |
| } |
| Rectangle selBounds = fTextComponent.getBoundingRect(start, limit); |
| |
| selBounds.width = Math.max(1, selBounds.width); |
| selBounds.height = Math.max(1, selBounds.height); |
| |
| fTextComponent.drawText(g, selBounds, visible, start, limit, fHighlightColor); |
| } |
| |
| protected TextOffset getAnchor() { |
| return fAnchor; |
| } |
| |
| public TextOffset getEnd() { |
| return fLimit; |
| } |
| |
| public Color getHighlightColor() { |
| return fHighlightColor; |
| } |
| |
| public TextOffset getStart() { |
| return fStart; |
| } |
| |
| public TextRange getSelectionRange() { |
| |
| return new TextRange(fStart.fOffset, fLimit.fOffset); |
| } |
| |
| public boolean focusGained(FocusEvent e) { |
| |
| setEnabled(true); |
| drawSelection(fTextComponent.getGraphics(), true); |
| |
| restartCaretBlinking(true); |
| if (fPendingMouseEvent != null) { |
| mousePressed(fPendingMouseEvent); |
| fPendingMouseEvent = null; |
| } |
| fListener.textStateChanged(TextPanelEvent.CLIPBOARD_CHANGED); |
| |
| return true; |
| } |
| |
| public boolean focusLost(FocusEvent e) { |
| stopCaretBlinking(); |
| setEnabled(false); |
| drawSelection(fTextComponent.getGraphics(), false); |
| return true; |
| } |
| |
| /** |
| * Return true if the given key event can affect the selection |
| * range. |
| */ |
| public static boolean keyAffectsSelection(KeyEvent e) { |
| |
| if (e.getID() != KeyEvent.KEY_PRESSED) { |
| return false; |
| } |
| |
| int key = e.getKeyCode(); |
| |
| switch (key) { |
| case KeyEvent.VK_HOME: |
| case KeyEvent.VK_END: |
| case KeyEvent.VK_LEFT: |
| case KeyEvent.VK_RIGHT: |
| case KeyEvent.VK_UP: |
| case KeyEvent.VK_DOWN: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| public boolean keyPressed(KeyEvent e) { |
| |
| fHandlingKeyOrCommand = true; |
| int key = e.getKeyCode(); |
| boolean result = true; |
| |
| switch (key) { |
| case KeyEvent.VK_HOME: |
| doHomeKey(e); |
| break; |
| |
| case KeyEvent.VK_END: |
| doEndKey(e); |
| break; |
| |
| case KeyEvent.VK_LEFT: |
| case KeyEvent.VK_RIGHT: |
| case KeyEvent.VK_UP: |
| case KeyEvent.VK_DOWN: |
| doArrowKey(e, key); |
| break; |
| |
| default: |
| fUpDownAnchor = null; |
| result = false; |
| break; |
| } |
| |
| fHandlingKeyOrCommand = false; |
| return result; |
| } |
| |
| public boolean mousePressed(MouseEvent e) { |
| |
| if (!enabled()) { |
| fPendingMouseEvent = e; |
| fTextComponent.requestFocus(); |
| return false; |
| } |
| |
| if (fMouseDown) |
| throw new Error("fMouseDown is out of sync with mouse in TextSelection."); |
| |
| fMouseDown = true; |
| stopCaretBlinking(); |
| |
| int x = e.getX(), y = e.getY(); |
| boolean wasZeroLength = rangeIsZeroLength(fStart, fLimit, fAnchor); |
| |
| TextOffset current = fTextComponent.pointToTextOffset(null, x, y, null, true); |
| TextOffset anchorStart = new TextOffset(); |
| TextOffset anchorEnd = new TextOffset(); |
| |
| fUpDownAnchor = null; |
| |
| // if we're not extending the selection... |
| if (!e.isShiftDown()) { |
| |
| // if there are multiple clicks, create the appopriate type of BreakIterator |
| // object for finding text boundaries (single clicks don't use a BreakIterator |
| // object) |
| if (e.getClickCount() == 2) |
| fBoundaries = BreakIterator.getWordInstance(); |
| else if (e.getClickCount() == 3) |
| fBoundaries = BreakIterator.getSentenceInstance(); |
| else |
| fBoundaries = null; |
| |
| // if we're using a BreakIterator object, use it to find the nearest boundaries |
| // on either side of the mouse-click position and make them our anchor range |
| if (fBoundaries != null) |
| fBoundaries.setText(fText.createCharacterIterator()); |
| |
| anchorStart.assign(current); |
| advanceToPreviousBoundary(anchorStart); |
| anchorEnd.assign(current); |
| advanceToNextBoundary(anchorEnd); |
| } |
| |
| // if we _are_ extending the selection, determine our anchor range as follows: |
| // fAnchor is the start of the anchor range; |
| // the next boundary (after fAnchor) is the limit of the anchor range. |
| |
| else { |
| |
| if (fBoundaries != null) |
| fBoundaries.setText(fText.createCharacterIterator()); |
| |
| anchorStart.assign(fAnchor); |
| anchorEnd.assign(anchorStart); |
| |
| advanceToNextBoundary(anchorEnd); |
| } |
| |
| SelectionDragInteractor interactor = new SelectionDragInteractor(this, |
| fTextComponent, |
| fRunStrategy, |
| anchorStart, |
| anchorEnd, |
| current, |
| x, |
| y, |
| wasZeroLength); |
| |
| interactor.addToOwner(fTextComponent); |
| |
| return true; |
| } |
| |
| public boolean mouseReleased(MouseEvent e) { |
| |
| fPendingMouseEvent = null; |
| return false; |
| } |
| |
| // drag interactor calls this |
| void mouseReleased(boolean zeroLengthChange) { |
| |
| fMouseDown = false; |
| |
| if (zeroLengthChange) { |
| fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED); |
| } |
| fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED); |
| fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED); |
| |
| // if caret drawing during mouse drags is supressed, draw caret now. |
| |
| restartCaretBlinking(true); |
| } |
| |
| |
| /** draws the selection |
| * Provided, of course, that the selection is visible, the adorner is enabled, |
| * and we're calling it to adorn the view it actually belongs to |
| * @param g The graphics environment to draw into |
| * @return true if we actually drew |
| */ |
| public boolean paint(Graphics g, Rectangle drawRect) { |
| // don't draw anything unless we're enabled and the selection is visible |
| if (!enabled()) |
| return false; |
| |
| fTextComponent.drawText(g, drawRect, true, fStart, fLimit, fHighlightColor); |
| return true; |
| } |
| |
| /** scrolls the view to reveal the live end of the selection |
| * (i.e., the end that moves if you use the arrow keys with the shift key down) |
| */ |
| public void scrollToShowSelection() { |
| Rectangle selRect = fTextComponent.getBoundingRect(fStart, fLimit); |
| |
| fTextComponent.scrollToShow(selRect); |
| } |
| |
| /** scrolls the view to reveal the live end of the selection |
| * (i.e., the end that moves if you use the arrow keys with the shift key down) |
| */ |
| public void scrollToShowSelectionEnd() { |
| TextOffset liveEnd; |
| // variable not used Point[] points; |
| Rectangle caret; |
| |
| if (fAnchor.equals(fStart)) |
| liveEnd = fLimit; |
| else |
| liveEnd = fStart; |
| |
| //points = fTextComponent.textOffsetToPoint(liveEnd); |
| //caret = new Rectangle(points[0]); |
| //caret = caret.union(new Rectangle(points[1])); |
| caret = fTextComponent.getCaretRect(liveEnd); |
| fTextComponent.scrollToShow(caret); |
| } |
| |
| private void select(TextRange range) { |
| // variable not used int textLength = fTextComponent.getText().length(); |
| |
| TextOffset start = new TextOffset(range.start); |
| |
| stopCaretBlinking(); |
| setSelRangeAndDraw(start, new TextOffset(range.limit), start); |
| restartCaretBlinking(true); |
| } |
| |
| public void setHighlightColor(Color newColor) { |
| fHighlightColor = newColor; |
| if (enabled()) |
| drawSelection(fTextComponent.getGraphics(), true); |
| } |
| |
| static boolean rangeIsZeroLength(TextOffset start, TextOffset limit, TextOffset anchor) { |
| |
| return start.fOffset == limit.fOffset && anchor.fOffset == limit.fOffset; |
| } |
| |
| // sigh... look out for aliasing |
| public void setSelectionRange(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) { |
| |
| boolean zeroLengthChange = rangeIsZeroLength(newStart, newLimit, newAnchor) != |
| rangeIsZeroLength(fStart, fLimit, fAnchor); |
| TextOffset tempNewAnchor; |
| if (newAnchor == fStart || newAnchor == fLimit) { |
| tempNewAnchor = new TextOffset(newAnchor); // clone in case of aliasing |
| } |
| else { |
| tempNewAnchor = newAnchor; |
| } |
| |
| // DEBUG {jbr} |
| |
| if (newStart.greaterThan(newLimit)) |
| throw new IllegalArgumentException("Selection limit is before selection start."); |
| |
| if (newLimit != fStart) { |
| fStart.assign(newStart); |
| fLimit.assign(newLimit); |
| } |
| else { |
| fLimit.assign(newLimit); |
| fStart.assign(newStart); |
| } |
| |
| fAnchor.assign(tempNewAnchor); |
| |
| if (fStart.fOffset == fLimit.fOffset) { |
| fStart.fPlacement = fAnchor.fPlacement; |
| fLimit.fPlacement = fAnchor.fPlacement; |
| } |
| |
| if (!fMouseDown) { |
| if (zeroLengthChange) { |
| fListener.textStateChanged(TextPanelEvent.SELECTION_EMPTY_CHANGED); |
| } |
| fListener.textStateChanged(TextPanelEvent.SELECTION_RANGE_CHANGED); |
| if (fHandlingKeyOrCommand) { |
| fListener.textStateChanged(TextPanelEvent.SELECTION_STYLES_CHANGED); |
| } |
| } |
| } |
| |
| private void sortOffsets(TextOffset offsets[]) { |
| |
| int i, j; |
| |
| for (i=0; i < offsets.length-1; i++) { |
| for (j=i+1; j < offsets.length; j++) { |
| if (offsets[j].lessThan(offsets[i])) { |
| TextOffset temp = offsets[j]; |
| offsets[j] = offsets[i]; |
| offsets[i] = temp; |
| } |
| } |
| } |
| |
| // DEBUG {jbr} |
| for (i=0; i < offsets.length-1; i++) |
| if (offsets[i].greaterThan(offsets[i+1])) |
| throw new Error("sortOffsets failed!"); |
| } |
| |
| private Rectangle getSelectionChangeRect( |
| TextOffset rangeStart, TextOffset rangeLimit, |
| TextOffset oldStart, TextOffset oldLimit, |
| TextOffset newStart, TextOffset newLimit, |
| boolean drawIfInsPoint) { |
| |
| if (!rangeStart.equals(rangeLimit)) |
| return fTextComponent.getBoundingRect(rangeStart, rangeLimit); |
| |
| // here, rangeStart and rangeLimit are equal |
| |
| if (rangeStart.equals(oldLimit)) { |
| |
| // range start is OLD insertion point. Redraw if caret is currently visible. |
| |
| if (fCaretIsVisible) |
| return fTextComponent.getBoundingRect(rangeStart, rangeStart); |
| } |
| else if (rangeStart.equals(newLimit)) { |
| |
| // range start is NEW insertion point. |
| |
| if (drawIfInsPoint) |
| return fTextComponent.getBoundingRect(rangeStart, rangeStart); |
| } |
| |
| return null; |
| } |
| |
| private static boolean rectanglesOverlapVertically(Rectangle r1, Rectangle r2) { |
| |
| if (r1 == null || r2 == null) { |
| return false; |
| } |
| |
| return r1.y <= r2.y + r2.height || r2.y <= r1.y + r1.height; |
| } |
| |
| // Update to show new selection, redrawing as little as possible |
| |
| private void updateSelectionDisplay( |
| TextOffset oldStart, TextOffset oldLimit, |
| TextOffset newStart, TextOffset newLimit, boolean drawIfInsPoint) { |
| |
| //System.out.println("newStart:" + newStart + "; newLimit:" + newLimit); |
| |
| TextOffset off[] = new TextOffset[4]; |
| |
| off[0] = oldStart; |
| off[1] = oldLimit; |
| off[2] = newStart; |
| off[3] = newLimit; |
| |
| sortOffsets(off); |
| |
| Rectangle r1 = getSelectionChangeRect(off[0], off[1], oldStart, oldLimit, newStart, newLimit, drawIfInsPoint); |
| Rectangle r2 = getSelectionChangeRect(off[2], off[3], oldStart, oldLimit, newStart, newLimit, drawIfInsPoint); |
| |
| boolean drawSelection = drawIfInsPoint || !newStart.equals(newLimit); |
| |
| if (rectanglesOverlapVertically(r1, r2)) { |
| |
| fTextComponent.drawText(fTextComponent.getGraphics(), r1.union(r2), drawSelection, newStart, newLimit, fHighlightColor); |
| } |
| else { |
| if (r1 != null) |
| fTextComponent.drawText(fTextComponent.getGraphics(), r1, drawSelection, newStart, newLimit, fHighlightColor); |
| if (r2 != null) |
| fTextComponent.drawText(fTextComponent.getGraphics(), r2, drawSelection, newStart, newLimit, fHighlightColor); |
| } |
| } |
| |
| public void setSelRangeAndDraw(TextOffset newStart, TextOffset newLimit, TextOffset newAnchor) { |
| |
| // if the old and new selection ranges are the same, don't do anything |
| if (fStart.equals(newStart) && fLimit.equals(newLimit) && fAnchor.equals(newAnchor)) |
| return; |
| |
| if (enabled()) |
| stopCaretBlinking(); |
| |
| // update the selection on screen if we're enabled and visible |
| |
| TextOffset oldStart = new TextOffset(fStart), oldLimit = new TextOffset(fLimit); |
| |
| setSelectionRange(newStart, newLimit, newAnchor); |
| |
| if (enabled()) { |
| |
| // To supress drawing a caret during a mouse drag, pass !fMouseDown instead of true: |
| updateSelectionDisplay(oldStart, oldLimit, fStart, fLimit, true); |
| } |
| |
| if (!fMouseDown && enabled()) |
| restartCaretBlinking(true); |
| } |
| |
| public void stopCaretBlinking() { |
| |
| synchronized(this) { |
| fCaretShouldBlink = false; |
| } |
| } |
| |
| /** |
| * Resume blinking the caret, if the selection is an insertion point. |
| * @param caretIsVisible true if the caret is displayed when this is called. |
| * This method relies on the client to display (or not display) the caret. |
| */ |
| public void restartCaretBlinking(boolean caretIsVisible) { |
| |
| synchronized(this) { |
| fCaretShouldBlink = fStart.equals(fLimit); |
| fCaretCount = 0; |
| fCaretIsVisible = caretIsVisible; |
| |
| if (fCaretShouldBlink) { |
| try { |
| notify(); |
| } |
| catch (IllegalMonitorStateException e) { |
| System.out.println("Caught IllegalMonitorStateException: "+e); |
| } |
| } |
| } |
| } |
| |
| public void removeFromOwner() { |
| |
| stopCaretBlinking(); |
| super.removeFromOwner(); |
| } |
| |
| } |