Bug 574454 - Cleanup nsNativeThemeWin's GetMinimumWidgetSize a bit. r=roc.
[mozilla-central.git] / layout / generic / nsSelection.cpp
blob2f0db0cdec9ee98fc93259dabedc6613a9a53394
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Mats Palmgren <matspal@gmail.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 * Implementation of selection: nsISelection,nsISelectionPrivate and nsFrameSelection
44 #include "nsCOMPtr.h"
45 #include "nsWeakReference.h"
46 #include "nsIFactory.h"
47 #include "nsIEnumerator.h"
48 #include "nsString.h"
49 #include "nsReadableUtils.h"
50 #include "nsFrameSelection.h"
51 #include "nsISelection.h"
52 #include "nsISelection2.h"
53 #include "nsISelection3.h"
54 #include "nsISelectionPrivate.h"
55 #include "nsISelectionListener.h"
56 #include "nsIComponentManager.h"
57 #include "nsContentCID.h"
58 #include "nsIContent.h"
59 #include "nsIDOMElement.h"
60 #include "nsIDOMNode.h"
61 #include "nsRange.h"
62 #include "nsCOMArray.h"
63 #include "nsGUIEvent.h"
64 #include "nsIDOMKeyEvent.h"
65 #include "nsITableLayout.h"
66 #include "nsITableCellLayout.h"
67 #include "nsIDOMNodeList.h"
68 #include "nsTArray.h"
69 #include "nsIScrollableFrame.h"
71 #include "nsISelectionListener.h"
72 #include "nsIContentIterator.h"
73 #include "nsIDocumentEncoder.h"
75 // for IBMBIDI
76 #include "nsFrameTraversal.h"
77 #include "nsILineIterator.h"
78 #include "nsGkAtoms.h"
79 #include "nsIFrameTraversal.h"
80 #include "nsLayoutUtils.h"
81 #include "nsLayoutCID.h"
82 #include "nsBidiPresUtils.h"
83 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
84 #include "nsTextFrame.h"
86 #include "nsIDOMText.h"
88 #include "nsContentUtils.h"
89 #include "nsThreadUtils.h"
91 //included for desired x position;
92 #include "nsPresContext.h"
93 #include "nsIPresShell.h"
94 #include "nsCaret.h"
97 #include "nsIDeviceContext.h"
98 #include "nsITimer.h"
99 #include "nsIServiceManager.h"
100 #include "nsFrameManager.h"
101 #include "nsIScrollableFrame.h"
102 // notifications
103 #include "nsIDOMDocument.h"
104 #include "nsIDocument.h"
106 #include "nsISelectionController.h"//for the enums
107 #include "nsAutoCopyListener.h"
108 #include "nsCopySupport.h"
109 #include "nsIClipboard.h"
111 #ifdef IBMBIDI
112 #include "nsIBidiKeyboard.h"
113 #endif // IBMBIDI
115 //#define DEBUG_TABLE 1
117 static NS_DEFINE_IID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
118 static NS_DEFINE_IID(kCSubtreeIteratorCID, NS_SUBTREEITERATOR_CID);
120 //PROTOTYPES
121 class nsSelectionIterator;
122 class nsFrameSelection;
123 class nsAutoScrollTimer;
125 static PRBool IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode);
127 static nsIAtom *GetTag(nsINode *aNode);
128 // returns the parent
129 static nsINode* ParentOffset(nsINode *aNode, PRInt32 *aChildOffset);
130 static nsINode* GetCellParent(nsINode *aDomNode);
132 #ifdef PRINT_RANGE
133 static void printRange(nsIRange *aDomRange);
134 #define DEBUG_OUT_RANGE(x) printRange(x)
135 #else
136 #define DEBUG_OUT_RANGE(x)
137 #endif //MOZ_DEBUG
141 //#define DEBUG_SELECTION // uncomment for printf describing every collapse and extend.
142 //#define DEBUG_NAVIGATION
145 //#define DEBUG_TABLE_SELECTION 1
147 struct CachedOffsetForFrame {
148 CachedOffsetForFrame()
149 : mCachedFrameOffset(0, 0) // nsPoint ctor
150 , mLastCaretFrame(nsnull)
151 , mLastContentOffset(0)
152 , mCanCacheFrameOffset(PR_FALSE)
155 nsPoint mCachedFrameOffset; // cached frame offset
156 nsIFrame* mLastCaretFrame; // store the frame the caret was last drawn in.
157 PRInt32 mLastContentOffset; // store last content offset
158 PRPackedBool mCanCacheFrameOffset; // cached frame offset is valid?
161 struct RangeData
163 RangeData(nsIRange* aRange) :
164 mRange(aRange) {}
166 nsCOMPtr<nsIRange> mRange;
167 nsTextRangeStyle mTextRangeStyle;
170 static RangeData sEmptyData(nsnull);
172 // Note, the ownership of nsTypedSelection depends on which way the object is
173 // created. When nsFrameSelection has created nsTypedSelection,
174 // addreffing/releasing nsTypedSelection object is aggregated to
175 // nsFrameSelection. Otherwise normal addref/release is used.
176 // This ensures that nsFrameSelection is never deleted before its
177 // nsTypedSelections.
179 class nsTypedSelection : public nsISelection2,
180 public nsISelection3,
181 public nsISelectionPrivate,
182 public nsSupportsWeakReference
184 public:
185 nsTypedSelection();
186 nsTypedSelection(nsFrameSelection *aList);
187 virtual ~nsTypedSelection();
189 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
190 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTypedSelection, nsISelection)
191 NS_DECL_NSISELECTION
192 NS_DECL_NSISELECTION2
193 NS_DECL_NSISELECTION3
194 NS_DECL_NSISELECTIONPRIVATE
196 // utility methods for scrolling the selection into view
197 nsresult GetPresContext(nsPresContext **aPresContext);
198 nsresult GetPresShell(nsIPresShell **aPresShell);
199 // Returns the position of the region, and frame that that position is relative
200 // to. The 'position' is a zero-width rectangle.
201 nsIFrame* GetSelectionAnchorGeometry(SelectionRegion aRegion, nsRect *aRect);
203 nsresult PostScrollSelectionIntoViewEvent(SelectionRegion aRegion);
204 // aDoFlush only matters if aIsSynchronous is true. If not, we'll just flush
205 // when the scroll event fires so we make sure to scroll to the right place.
206 nsresult ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous,
207 PRBool aDoFlush,
208 PRInt16 aVPercent = NS_PRESSHELL_SCROLL_ANYWHERE,
209 PRInt16 aHPercent = NS_PRESSHELL_SCROLL_ANYWHERE);
210 nsresult SubtractRange(RangeData* aRange, nsIRange* aSubtract,
211 nsTArray<RangeData>* aOutput);
212 nsresult AddItem(nsIRange *aRange, PRInt32* aOutIndex = nsnull);
213 nsresult RemoveItem(nsIRange *aRange);
214 nsresult RemoveCollapsedRanges();
215 nsresult Clear(nsPresContext* aPresContext);
216 nsresult Collapse(nsINode* aParentNode, PRInt32 aOffset);
217 nsresult Extend(nsINode* aParentNode, PRInt32 aOffset);
218 nsresult AddRange(nsIRange* aRange);
219 // The nsIRange version of RemoveRange assumes the caller is holding
220 // a strong reference to aRange.
221 nsresult RemoveRange(nsIRange* aRange);
222 nsIRange* GetRangeAt(PRInt32 aIndex);
223 nsresult GetTableSelectionType(nsIRange* aRange,
224 PRInt32* aTableSelectionType);
226 // methods for convenience. Note, these don't addref
227 nsINode* GetAnchorNode();
228 PRInt32 GetAnchorOffset();
230 nsINode* GetFocusNode();
231 PRInt32 GetFocusOffset();
233 // Get the anchor-to-focus range if we don't care which end is
234 // anchor and which end is focus.
235 const nsIRange* GetAnchorFocusRange() const {
236 return mAnchorFocusRange;
239 nsDirection GetDirection(){return mDirection;}
240 void SetDirection(nsDirection aDir){mDirection = aDir;}
241 nsresult CopyRangeToAnchorFocus(nsIRange *aRange);
242 void ReplaceAnchorFocusRange(nsIRange *aRange);
244 // NS_IMETHOD GetPrimaryFrameForRangeEndpoint(nsIDOMNode *aNode, PRInt32 aOffset, PRBool aIsEndNode, nsIFrame **aResultFrame);
245 NS_IMETHOD GetPrimaryFrameForAnchorNode(nsIFrame **aResultFrame);
246 NS_IMETHOD GetPrimaryFrameForFocusNode(nsIFrame **aResultFrame, PRInt32 *aOffset, PRBool aVisual);
247 NS_IMETHOD LookUpSelection(nsIContent *aContent, PRInt32 aContentOffset, PRInt32 aContentLength,
248 SelectionDetails **aReturnDetails, SelectionType aType, PRBool aSlowCheck);
249 NS_IMETHOD Repaint(nsPresContext* aPresContext);
251 // Note: StartAutoScrollTimer might destroy arbitrary frames etc.
252 nsresult StartAutoScrollTimer(nsIFrame *aFrame,
253 nsPoint& aPoint,
254 PRUint32 aDelay);
256 nsresult StopAutoScrollTimer();
259 private:
260 friend class nsAutoScrollTimer;
262 // Note: DoAutoScroll might destroy arbitrary frames etc.
263 nsresult DoAutoScroll(nsIFrame *aFrame, nsPoint& aPoint);
265 public:
266 SelectionType GetType(){return mType;}
267 void SetType(SelectionType aType){mType = aType;}
269 nsresult NotifySelectionListeners();
271 private:
272 friend class nsSelectionIterator;
274 class ScrollSelectionIntoViewEvent;
275 friend class ScrollSelectionIntoViewEvent;
277 class ScrollSelectionIntoViewEvent : public nsRunnable {
278 public:
279 NS_DECL_NSIRUNNABLE
280 ScrollSelectionIntoViewEvent(nsTypedSelection *aTypedSelection,
281 SelectionRegion aRegion)
282 : mTypedSelection(aTypedSelection),
283 mRegion(aRegion) {
284 NS_ASSERTION(aTypedSelection, "null parameter");
286 void Revoke() { mTypedSelection = nsnull; }
287 private:
288 nsTypedSelection *mTypedSelection;
289 SelectionRegion mRegion;
292 void setAnchorFocusRange(PRInt32 aIndex); // pass in index into mRanges;
293 // negative value clears
294 // mAnchorFocusRange
295 nsresult SelectAllFramesForContent(nsIContentIterator *aInnerIter,
296 nsIContent *aContent,
297 PRBool aSelected);
298 nsresult selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PRBool aSelect);
299 nsresult getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol);
300 nsresult addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange, PRInt32 *aOutIndex);
302 nsresult FindInsertionPoint(
303 nsTArray<RangeData>* aElementArray,
304 nsINode* aPointNode, PRInt32 aPointOffset,
305 nsresult (*aComparator)(nsINode*,PRInt32,nsIRange*,PRInt32*),
306 PRInt32* aPoint);
307 PRBool EqualsRangeAtPoint(nsINode* aBeginNode, PRInt32 aBeginOffset,
308 nsINode* aEndNode, PRInt32 aEndOffset,
309 PRInt32 aRangeIndex);
310 nsresult GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
311 nsINode* aEndNode, PRInt32 aEndOffset,
312 PRBool aAllowAdjacent,
313 nsCOMArray<nsIRange>* aRanges);
314 void GetIndicesForInterval(nsINode* aBeginNode, PRInt32 aBeginOffset,
315 nsINode* aEndNode, PRInt32 aEndOffset,
316 PRBool aAllowAdjacent,
317 PRInt32 *aStartIndex, PRInt32 *aEndIndex);
318 RangeData* FindRangeData(nsIDOMRange* aRange);
320 // These are the ranges inside this selection. They are kept sorted in order
321 // of DOM start position.
323 // This data structure is sorted by the range beginnings. As the ranges are
324 // disjoint, it is also implicitly sorted by the range endings. This allows
325 // us to perform binary searches when searching for existence of a range,
326 // giving us O(log n) search time.
328 // Inserting a new range requires finding the overlapping interval, requiring
329 // two binary searches plus up to an additional 6 DOM comparisons. If this
330 // proves to be a performance concern, then an interval tree may be a
331 // possible solution, allowing the calculation of the overlap interval in
332 // O(log n) time, though this would require rebalancing and other overhead.
333 nsTArray<RangeData> mRanges;
335 nsCOMPtr<nsIRange> mAnchorFocusRange;
336 nsRefPtr<nsFrameSelection> mFrameSelection;
337 nsWeakPtr mPresShellWeak;
338 nsRefPtr<nsAutoScrollTimer> mAutoScrollTimer;
339 nsCOMArray<nsISelectionListener> mSelectionListeners;
340 nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
341 CachedOffsetForFrame *mCachedOffsetForFrame;
342 nsDirection mDirection;
343 SelectionType mType;
346 // Stack-class to turn on/off selection batching for table selection
347 class NS_STACK_CLASS NS_FINAL_CLASS nsSelectionBatcher
349 private:
350 nsCOMPtr<nsISelectionPrivate> mSelection;
351 public:
352 nsSelectionBatcher(nsISelectionPrivate *aSelection) : mSelection(aSelection)
354 if (mSelection) mSelection->StartBatchChanges();
356 ~nsSelectionBatcher()
358 if (mSelection) mSelection->EndBatchChanges();
362 class nsSelectionIterator : public nsIBidirectionalEnumerator
364 public:
365 /*BEGIN nsIEnumerator interfaces
366 see the nsIEnumerator for more details*/
368 NS_DECL_ISUPPORTS
370 NS_DECL_NSIENUMERATOR
372 NS_DECL_NSIBIDIRECTIONALENUMERATOR
374 /*END nsIEnumerator interfaces*/
375 /*BEGIN Helper Methods*/
376 nsIRange* CurrentItem();
377 /*END Helper Methods*/
378 private:
379 friend class nsTypedSelection;
381 //lame lame lame if delete from document goes away then get rid of this unless its debug
382 friend class nsFrameSelection;
384 nsSelectionIterator(nsTypedSelection *);
385 virtual ~nsSelectionIterator();
386 PRInt32 mIndex;
387 nsTypedSelection *mDomSelection;
388 SelectionType mType;
391 class nsAutoScrollTimer : public nsITimerCallback
393 public:
395 NS_DECL_ISUPPORTS
397 nsAutoScrollTimer()
398 : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30)
402 virtual ~nsAutoScrollTimer()
404 if (mTimer)
405 mTimer->Cancel();
408 // aPoint is relative to aPresContext's root frame
409 nsresult Start(nsPresContext *aPresContext, nsPoint &aPoint)
411 mPoint = aPoint;
413 // Store the presentation context. The timer will be
414 // stopped by the selection if the prescontext is destroyed.
415 mPresContext = aPresContext;
417 mContent = nsIPresShell::GetCapturingContent();
419 if (!mTimer)
421 nsresult result;
422 mTimer = do_CreateInstance("@mozilla.org/timer;1", &result);
424 if (NS_FAILED(result))
425 return result;
428 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
431 nsresult Stop()
433 if (mTimer)
435 mTimer->Cancel();
436 mTimer = 0;
439 mContent = nsnull;
440 return NS_OK;
443 nsresult Init(nsFrameSelection *aFrameSelection, nsTypedSelection *aSelection)
445 mFrameSelection = aFrameSelection;
446 mSelection = aSelection;
447 return NS_OK;
450 nsresult SetDelay(PRUint32 aDelay)
452 mDelay = aDelay;
453 return NS_OK;
456 NS_IMETHOD Notify(nsITimer *timer)
458 if (mSelection && mPresContext)
460 nsWeakFrame frame =
461 mContent ? mPresContext->GetPrimaryFrameFor(mContent) : nsnull;
462 mContent = nsnull;
464 mFrameSelection->HandleDrag(frame, mPoint);
465 if (!frame.IsAlive())
466 return NS_OK;
468 NS_ASSERTION(frame->PresContext() == mPresContext, "document mismatch?");
469 nsPoint pt = mPoint -
470 frame->GetOffsetTo(mPresContext->PresShell()->FrameManager()->GetRootFrame());
471 mSelection->DoAutoScroll(frame, pt);
473 return NS_OK;
475 private:
476 nsFrameSelection *mFrameSelection;
477 nsTypedSelection *mSelection;
478 nsPresContext *mPresContext;
479 // relative to mPresContext's root frame
480 nsPoint mPoint;
481 nsCOMPtr<nsITimer> mTimer;
482 nsCOMPtr<nsIContent> mContent;
483 PRUint32 mDelay;
486 NS_IMPL_ISUPPORTS1(nsAutoScrollTimer, nsITimerCallback)
488 nsresult NS_NewSelection(nsFrameSelection **aFrameSelection)
490 nsFrameSelection *rlist = new nsFrameSelection;
491 if (!rlist)
492 return NS_ERROR_OUT_OF_MEMORY;
493 *aFrameSelection = rlist;
494 NS_ADDREF(rlist);
495 return NS_OK;
498 nsresult NS_NewDomSelection(nsISelection **aDomSelection)
500 nsTypedSelection *rlist = new nsTypedSelection;
501 if (!rlist)
502 return NS_ERROR_OUT_OF_MEMORY;
503 *aDomSelection = (nsISelection *)rlist;
504 NS_ADDREF(rlist);
505 return NS_OK;
508 static PRInt8
509 GetIndexFromSelectionType(SelectionType aType)
511 switch (aType)
513 case nsISelectionController::SELECTION_NORMAL: return 0; break;
514 case nsISelectionController::SELECTION_SPELLCHECK: return 1; break;
515 case nsISelectionController::SELECTION_IME_RAWINPUT: return 2; break;
516 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: return 3; break;
517 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break;
518 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break;
519 case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break;
520 case nsISelectionController::SELECTION_FIND: return 7; break;
521 default:
522 return -1; break;
524 /* NOTREACHED */
525 return 0;
528 static SelectionType
529 GetSelectionTypeFromIndex(PRInt8 aIndex)
531 switch (aIndex)
533 case 0: return nsISelectionController::SELECTION_NORMAL; break;
534 case 1: return nsISelectionController::SELECTION_SPELLCHECK; break;
535 case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break;
536 case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break;
537 case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break;
538 case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break;
539 case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break;
540 case 7: return nsISelectionController::SELECTION_FIND; break;
541 default:
542 return nsISelectionController::SELECTION_NORMAL; break;
544 /* NOTREACHED */
545 return 0;
549 The limiter is used specifically for the text areas and textfields
550 In that case it is the DIV tag that is anonymously created for the text
551 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
552 BR node the limiter will be the parent and the offset will point before or
553 after the BR node. In the case of the text node the parent content is
554 the text node itself and the offset will be the exact character position.
555 The offset is not important to check for validity. Simply look at the
556 passed in content. If it equals the limiter then the selection point is valid.
557 If its parent it the limiter then the point is also valid. In the case of
558 NO limiter all points are valid since you are in a topmost iframe. (browser
559 or composer)
561 PRBool
562 IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode)
564 if (!aFrameSel || !aNode)
565 return PR_FALSE;
567 nsIContent *limiter = aFrameSel->GetLimiter();
568 if (limiter && limiter != aNode && limiter != aNode->GetParent()) {
569 //if newfocus == the limiter. that's ok. but if not there and not parent bad
570 return PR_FALSE; //not in the right content. tLimiter said so
573 limiter = aFrameSel->GetAncestorLimiter();
574 return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter);
578 NS_IMPL_ADDREF(nsSelectionIterator)
579 NS_IMPL_RELEASE(nsSelectionIterator)
581 NS_INTERFACE_MAP_BEGIN(nsSelectionIterator)
582 NS_INTERFACE_MAP_ENTRY(nsIEnumerator)
583 NS_INTERFACE_MAP_ENTRY(nsIBidirectionalEnumerator)
584 NS_INTERFACE_MAP_END_AGGREGATED(mDomSelection)
587 ///////////BEGIN nsSelectionIterator methods
589 nsSelectionIterator::nsSelectionIterator(nsTypedSelection *aList)
590 :mIndex(0)
592 if (!aList)
594 NS_NOTREACHED("nsFrameSelection");
595 return;
597 mDomSelection = aList;
602 nsSelectionIterator::~nsSelectionIterator()
608 ////////////END nsSelectionIterator methods
610 ////////////BEGIN nsSelectionIterator methods
614 NS_IMETHODIMP
615 nsSelectionIterator::Next()
617 mIndex++;
618 PRInt32 cnt = mDomSelection->mRanges.Length();
619 if (mIndex < cnt)
620 return NS_OK;
621 return NS_ERROR_FAILURE;
626 NS_IMETHODIMP
627 nsSelectionIterator::Prev()
629 mIndex--;
630 if (mIndex >= 0 )
631 return NS_OK;
632 return NS_ERROR_FAILURE;
637 NS_IMETHODIMP
638 nsSelectionIterator::First()
640 if (!mDomSelection)
641 return NS_ERROR_NULL_POINTER;
642 mIndex = 0;
643 return NS_OK;
648 NS_IMETHODIMP
649 nsSelectionIterator::Last()
651 if (!mDomSelection)
652 return NS_ERROR_NULL_POINTER;
653 mIndex = mDomSelection->mRanges.Length() - 1;
654 return NS_OK;
659 NS_IMETHODIMP
660 nsSelectionIterator::CurrentItem(nsISupports **aItem)
662 *aItem = CurrentItem();
663 if (!*aItem) {
664 return NS_ERROR_FAILURE;
667 NS_ADDREF(*aItem);
668 return NS_OK;
671 nsIRange*
672 nsSelectionIterator::CurrentItem()
674 return mDomSelection->mRanges.SafeElementAt(mIndex, sEmptyData).mRange;
679 NS_IMETHODIMP
680 nsSelectionIterator::IsDone()
682 PRInt32 cnt = mDomSelection->mRanges.Length();
683 if (mIndex >= 0 && mIndex < cnt) {
684 return NS_ENUMERATOR_FALSE;
686 return NS_OK;
690 ////////////END nsSelectionIterator methods
693 ////////////BEGIN nsFrameSelection methods
695 nsFrameSelection::nsFrameSelection()
696 : mDelayedMouseEvent(PR_FALSE, 0, nsnull, nsMouseEvent::eReal)
698 PRInt32 i;
699 for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
700 mDomSelections[i] = new nsTypedSelection(this);
701 if (!mDomSelections[i])
702 break;
703 mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i));
705 mBatching = 0;
706 mChangesDuringBatching = PR_FALSE;
707 mNotifyFrames = PR_TRUE;
708 mLimiter = nsnull; //no default limiter.
709 mAncestorLimiter = nsnull;
711 mMouseDoubleDownState = PR_FALSE;
713 mHint = HINTLEFT;
714 #ifdef IBMBIDI
715 mCaretBidiLevel = BIDI_LEVEL_UNDEFINED;
716 #endif
717 mDragSelectingCells = PR_FALSE;
718 mSelectingTableCellMode = 0;
719 mSelectedCellIndex = 0;
721 // Check to see if the autocopy pref is enabled
722 // and add the autocopy listener if it is
723 if (nsContentUtils::GetBoolPref("clipboard.autocopy")) {
724 nsAutoCopyListener *autoCopy = nsAutoCopyListener::GetInstance();
726 if (autoCopy) {
727 PRInt8 index =
728 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
729 if (mDomSelections[index]) {
730 autoCopy->Listen(mDomSelections[index]);
735 mDisplaySelection = nsISelectionController::SELECTION_OFF;
737 mDelayedMouseEventValid = PR_FALSE;
738 mSelectionChangeReason = nsISelectionListener::NO_REASON;
742 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
743 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
744 PRInt32 i;
745 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
746 tmp->mDomSelections[i] = nsnull;
749 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCellParent)
750 tmp->mSelectingTableCellMode = 0;
751 tmp->mDragSelectingCells = PR_FALSE;
752 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStartSelectedCell)
753 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEndSelectedCell)
754 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mAppendStartSelectedCell)
755 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUnselectCellOnMouseUp)
756 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMaintainRange)
757 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
758 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
759 PRInt32 i;
760 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
761 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDomSelections[i],
762 nsISelection)
765 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCellParent)
766 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStartSelectedCell)
767 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEndSelectedCell)
768 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAppendStartSelectedCell)
769 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mUnselectCellOnMouseUp)
770 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mMaintainRange)
771 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
773 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameSelection)
774 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameSelection)
775 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameSelection)
776 NS_INTERFACE_MAP_ENTRY(nsFrameSelection)
777 NS_INTERFACE_MAP_ENTRY(nsISupports)
778 NS_INTERFACE_MAP_END
781 nsresult
782 nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down
784 if (!mShell)
786 NS_ERROR("fetch desired X failed");
787 return NS_ERROR_FAILURE;
789 if (mDesiredXSet)
791 aDesiredX = mDesiredX;
792 return NS_OK;
795 nsRefPtr<nsCaret> caret = mShell->GetCaret();
796 if (!caret)
797 return NS_ERROR_NULL_POINTER;
799 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
800 nsresult result = caret->SetCaretDOMSelection(mDomSelections[index]);
801 if (NS_FAILED(result))
802 return result;
804 nsRect coord;
805 nsIFrame* caretFrame = caret->GetGeometry(mDomSelections[index], &coord);
806 if (!caretFrame)
807 return NS_ERROR_FAILURE;
808 nsPoint viewOffset(0, 0);
809 nsIView* view = nsnull;
810 caretFrame->GetOffsetFromView(viewOffset, &view);
811 if (view)
812 coord.x += viewOffset.x;
814 aDesiredX = coord.x;
815 return NS_OK;
820 void
821 nsFrameSelection::InvalidateDesiredX() //do not listen to mDesiredX you must get another.
823 mDesiredXSet = PR_FALSE;
828 void
829 nsFrameSelection::SetDesiredX(nscoord aX) //set the mDesiredX
831 mDesiredX = aX;
832 mDesiredXSet = PR_TRUE;
835 nsresult
836 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame,
837 nsPoint& aPoint,
838 nsIFrame **aRetFrame,
839 nsPoint& aRetPoint)
842 // The whole point of this method is to return a frame and point that
843 // that lie within the same valid subtree as the anchor node's frame,
844 // for use with the method GetContentAndOffsetsFromPoint().
846 // A valid subtree is defined to be one where all the content nodes in
847 // the tree have a valid parent-child relationship.
849 // If the anchor frame and aFrame are in the same subtree, aFrame will
850 // be returned in aRetFrame. If they are in different subtrees, we
851 // return the frame for the root of the subtree.
854 if (!aFrame || !aRetFrame)
855 return NS_ERROR_NULL_POINTER;
857 *aRetFrame = aFrame;
858 aRetPoint = aPoint;
861 // Get the frame and content for the selection's anchor point!
864 nsresult result;
865 nsCOMPtr<nsIDOMNode> anchorNode;
866 PRInt32 anchorOffset = 0;
868 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
869 if (!mDomSelections[index])
870 return NS_ERROR_NULL_POINTER;
872 result = mDomSelections[index]->GetAnchorNode(getter_AddRefs(anchorNode));
874 if (NS_FAILED(result))
875 return result;
877 if (!anchorNode)
878 return NS_OK;
880 result = mDomSelections[index]->GetAnchorOffset(&anchorOffset);
882 if (NS_FAILED(result))
883 return result;
885 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode);
887 if (!anchorContent)
888 return NS_ERROR_FAILURE;
891 // Now find the root of the subtree containing the anchor's content.
894 NS_ENSURE_STATE(mShell);
895 nsIContent* anchorRoot = anchorContent->GetSelectionRootContent(mShell);
896 NS_ENSURE_TRUE(anchorRoot, NS_ERROR_UNEXPECTED);
899 // Now find the root of the subtree containing aFrame's content.
902 nsIContent* content = aFrame->GetContent();
904 if (content)
906 nsIContent* contentRoot = content->GetSelectionRootContent(mShell);
907 NS_ENSURE_TRUE(contentRoot, NS_ERROR_UNEXPECTED);
909 if (anchorRoot == contentRoot)
911 // If the aFrame's content isn't the capturing content, it should be
912 // a descendant. At this time, we can return simply.
913 nsIContent* capturedContent = nsIPresShell::GetCapturingContent();
914 if (capturedContent != content)
916 return NS_OK;
919 // Find the frame under the mouse cursor with the root frame.
920 // At this time, don't use the anchor's frame because it may not have
921 // fixed positioned frames.
922 nsIFrame* rootFrame = mShell->FrameManager()->GetRootFrame();
923 nsPoint ptInRoot = aPoint + aFrame->GetOffsetTo(rootFrame);
924 nsIFrame* cursorFrame =
925 nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
927 // If the mouse cursor in on a frame which is descendant of same
928 // selection root, we can expand the selection to the frame.
929 if (cursorFrame && cursorFrame->PresContext()->PresShell() == mShell)
931 nsIContent* cursorContent = cursorFrame->GetContent();
932 NS_ENSURE_TRUE(cursorContent, NS_ERROR_FAILURE);
933 nsIContent* cursorContentRoot =
934 cursorContent->GetSelectionRootContent(mShell);
935 NS_ENSURE_TRUE(cursorContentRoot, NS_ERROR_UNEXPECTED);
936 if (cursorContentRoot == anchorRoot)
938 *aRetFrame = cursorFrame;
939 aRetPoint = aPoint + aFrame->GetOffsetTo(cursorFrame);
940 return NS_OK;
943 // Otherwise, e.g., the cursor isn't on any frames (e.g., the mouse
944 // cursor is out of the window), we should use the frame of the anchor
945 // root.
950 // When we can't find a frame which is under the mouse cursor and has a same
951 // selection root as the anchor node's, we should return the selection root
952 // frame.
955 *aRetFrame = anchorRoot->GetPrimaryFrame();
957 if (!*aRetFrame)
958 return NS_ERROR_FAILURE;
961 // Now make sure that aRetPoint is converted to the same coordinate
962 // system used by aRetFrame.
965 aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
967 return NS_OK;
970 #ifdef IBMBIDI
971 void
972 nsFrameSelection::SetCaretBidiLevel(PRUint8 aLevel)
974 // If the current level is undefined, we have just inserted new text.
975 // In this case, we don't want to reset the keyboard language
976 PRBool afterInsert = !!(mCaretBidiLevel & BIDI_LEVEL_UNDEFINED);
977 mCaretBidiLevel = aLevel;
979 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
980 if (bidiKeyboard && !afterInsert)
981 bidiKeyboard->SetLangFromBidiLevel(aLevel);
982 return;
985 PRUint8
986 nsFrameSelection::GetCaretBidiLevel() const
988 return mCaretBidiLevel;
991 void
992 nsFrameSelection::UndefineCaretBidiLevel()
994 mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED;
996 #endif
999 #ifdef PRINT_RANGE
1000 void printRange(nsIRange *aDomRange)
1002 if (!aDomRange)
1004 printf("NULL nsIDOMRange\n");
1006 nsINode* startNode = aDomRange->GetStartParent();
1007 nsINode* endNode = aDomRange->GetEndParent();
1008 PRInt32 startOffset = aDomRange->StartOffset();
1009 PRInt32 endOffset = aDomRange->EndOffset();
1011 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
1012 (unsigned long)aDomRange,
1013 (unsigned long)startNode, (long)startOffset,
1014 (unsigned long)endNode, (long)endOffset);
1017 #endif /* PRINT_RANGE */
1019 static
1020 nsIAtom *GetTag(nsINode *aNode)
1022 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
1023 if (!content)
1025 NS_NOTREACHED("bad node passed to GetTag()");
1026 return nsnull;
1029 return content->Tag();
1032 // Returns the parent
1033 nsINode*
1034 ParentOffset(nsINode *aNode, PRInt32 *aChildOffset)
1036 if (!aNode || !aChildOffset)
1037 return nsnull;
1039 nsIContent* parent = aNode->GetParent();
1040 if (parent)
1042 *aChildOffset = parent->IndexOf(aNode);
1044 return parent;
1047 return nsnull;
1050 static nsINode*
1051 GetCellParent(nsINode *aDomNode)
1053 if (!aDomNode)
1054 return nsnull;
1055 nsINode* current = aDomNode;
1056 // Start with current node and look for a table cell
1057 while (current)
1059 nsIAtom* tag = GetTag(current);
1060 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th)
1061 return current;
1062 current = current->GetParent();
1064 return nsnull;
1068 void
1069 nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter)
1071 mShell = aShell;
1072 mMouseDownState = PR_FALSE;
1073 mDesiredXSet = PR_FALSE;
1074 mLimiter = aLimiter;
1075 mCaretMovementStyle = nsContentUtils::GetIntPref("bidi.edit.caret_movement_style", 2);
1078 nsresult
1079 nsFrameSelection::MoveCaret(PRUint32 aKeycode,
1080 PRBool aContinueSelection,
1081 nsSelectionAmount aAmount)
1083 PRBool visualMovement =
1084 (aKeycode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE ||
1085 aKeycode == nsIDOMKeyEvent::DOM_VK_DELETE ||
1086 aKeycode == nsIDOMKeyEvent::DOM_VK_HOME ||
1087 aKeycode == nsIDOMKeyEvent::DOM_VK_END) ?
1088 PR_FALSE : // Delete operations and home/end are always logical
1089 mCaretMovementStyle == 1 ||
1090 (mCaretMovementStyle == 2 && !aContinueSelection);
1092 return MoveCaret(aKeycode, aContinueSelection, aAmount, visualMovement);
1095 nsresult
1096 nsFrameSelection::MoveCaret(PRUint32 aKeycode,
1097 PRBool aContinueSelection,
1098 nsSelectionAmount aAmount,
1099 PRBool aVisualMovement)
1101 NS_ENSURE_STATE(mShell);
1102 // Flush out layout, since we need it to be up to date to do caret
1103 // positioning.
1104 mShell->FlushPendingNotifications(Flush_Layout);
1106 if (!mShell) {
1107 return NS_OK;
1110 nsPresContext *context = mShell->GetPresContext();
1111 if (!context)
1112 return NS_ERROR_FAILURE;
1114 PRBool isCollapsed;
1115 nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN
1117 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1118 nsRefPtr<nsTypedSelection> sel = mDomSelections[index];
1119 if (!sel)
1120 return NS_ERROR_NULL_POINTER;
1122 nsresult result = sel->GetIsCollapsed(&isCollapsed);
1123 if (NS_FAILED(result))
1124 return result;
1125 if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP ||
1126 aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN)
1128 result = FetchDesiredX(desiredX);
1129 if (NS_FAILED(result))
1130 return result;
1131 SetDesiredX(desiredX);
1134 PRInt32 caretStyle = nsContentUtils::GetIntPref("layout.selection.caret_style", 0);
1135 #ifdef XP_MACOSX
1136 if (caretStyle == 0) {
1137 caretStyle = 2; // put caret at the selection edge in the |aKeycode| direction
1139 #endif
1141 if (!isCollapsed && !aContinueSelection && caretStyle == 2) {
1142 switch (aKeycode){
1143 case nsIDOMKeyEvent::DOM_VK_LEFT :
1144 case nsIDOMKeyEvent::DOM_VK_UP :
1146 const nsIRange* anchorFocusRange = sel->GetAnchorFocusRange();
1147 if (anchorFocusRange) {
1148 sel->Collapse(anchorFocusRange->GetStartParent(),
1149 anchorFocusRange->StartOffset());
1151 mHint = HINTRIGHT;
1152 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1153 PR_FALSE, PR_FALSE);
1154 return NS_OK;
1157 case nsIDOMKeyEvent::DOM_VK_RIGHT :
1158 case nsIDOMKeyEvent::DOM_VK_DOWN :
1160 const nsIRange* anchorFocusRange = sel->GetAnchorFocusRange();
1161 if (anchorFocusRange) {
1162 sel->Collapse(anchorFocusRange->GetEndParent(),
1163 anchorFocusRange->EndOffset());
1165 mHint = HINTLEFT;
1166 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1167 PR_FALSE, PR_FALSE);
1168 return NS_OK;
1173 nsIFrame *frame;
1174 PRInt32 offsetused = 0;
1175 result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused,
1176 aVisualMovement);
1178 if (NS_FAILED(result) || !frame)
1179 return result?result:NS_ERROR_FAILURE;
1181 nsPeekOffsetStruct pos;
1182 //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking
1183 //when we hit scrollable views. If no limiter then just let it go ahead
1184 pos.SetData(aAmount, eDirPrevious, offsetused, desiredX,
1185 PR_TRUE, mLimiter != nsnull, PR_TRUE, aVisualMovement);
1187 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame);
1189 HINT tHint(mHint); //temporary variable so we dont set mHint until it is necessary
1190 switch (aKeycode){
1191 case nsIDOMKeyEvent::DOM_VK_RIGHT :
1192 InvalidateDesiredX();
1193 pos.mDirection = (baseLevel & 1) ? eDirPrevious : eDirNext;
1194 break;
1195 case nsIDOMKeyEvent::DOM_VK_LEFT :
1196 InvalidateDesiredX();
1197 pos.mDirection = (baseLevel & 1) ? eDirNext : eDirPrevious;
1198 break;
1199 case nsIDOMKeyEvent::DOM_VK_DELETE :
1200 InvalidateDesiredX();
1201 pos.mDirection = eDirNext;
1202 break;
1203 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE :
1204 InvalidateDesiredX();
1205 pos.mDirection = eDirPrevious;
1206 break;
1207 case nsIDOMKeyEvent::DOM_VK_DOWN :
1208 pos.mAmount = eSelectLine;
1209 pos.mDirection = eDirNext;
1210 break;
1211 case nsIDOMKeyEvent::DOM_VK_UP :
1212 pos.mAmount = eSelectLine;
1213 pos.mDirection = eDirPrevious;
1214 break;
1215 case nsIDOMKeyEvent::DOM_VK_HOME :
1216 InvalidateDesiredX();
1217 pos.mAmount = eSelectBeginLine;
1218 break;
1219 case nsIDOMKeyEvent::DOM_VK_END :
1220 InvalidateDesiredX();
1221 pos.mAmount = eSelectEndLine;
1222 break;
1223 default :return NS_ERROR_FAILURE;
1225 PostReason(nsISelectionListener::KEYPRESS_REASON);
1226 if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent)
1228 nsIFrame *theFrame;
1229 PRInt32 currentOffset, frameStart, frameEnd;
1231 if (aAmount == eSelectCharacter || aAmount == eSelectWord)
1233 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
1234 // so determine the hint here based on the result frame and offset:
1235 // If we're at the end of a text frame, set the hint to HINTLEFT to indicate that we
1236 // want the caret displayed at the end of this frame, not at the beginning of the next one.
1237 theFrame = pos.mResultFrame;
1238 theFrame->GetOffsets(frameStart, frameEnd);
1239 currentOffset = pos.mContentOffset;
1240 if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0))
1241 tHint = HINTLEFT;
1242 else
1243 tHint = HINTRIGHT;
1244 } else {
1245 // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all.
1246 // In these cases, get the frame based on the content and hint returned by PeekOffset().
1247 tHint = (HINT)pos.mAttachForward;
1248 theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
1249 tHint, &currentOffset);
1250 if (!theFrame)
1251 return NS_ERROR_FAILURE;
1253 theFrame->GetOffsets(frameStart, frameEnd);
1256 if (context->BidiEnabled())
1258 switch (aKeycode) {
1259 case nsIDOMKeyEvent::DOM_VK_HOME:
1260 case nsIDOMKeyEvent::DOM_VK_END:
1261 // set the caret Bidi level to the paragraph embedding level
1262 SetCaretBidiLevel(NS_GET_BASE_LEVEL(theFrame));
1263 break;
1265 default:
1266 // If the current position is not a frame boundary, it's enough just to take the Bidi level of the current frame
1267 if ((pos.mContentOffset != frameStart && pos.mContentOffset != frameEnd)
1268 || (eSelectLine == aAmount))
1270 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
1272 else
1273 BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, aKeycode, tHint);
1276 result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset,
1277 tHint, aContinueSelection, PR_FALSE);
1278 } else if (aKeycode == nsIDOMKeyEvent::DOM_VK_RIGHT && !aContinueSelection) {
1279 // Collapse selection if PeekOffset failed, we either
1280 // 1. bumped into the BRFrame, bug 207623
1281 // 2. had select-all in a text input (DIV range), bug 352759.
1282 PRBool isBRFrame = frame->GetType() == nsGkAtoms::brFrame;
1283 sel->Collapse(sel->GetFocusNode(), sel->GetFocusOffset());
1284 // Note: 'frame' might be dead here.
1285 if (!isBRFrame) {
1286 mHint = HINTLEFT; // We're now at the end of the frame to the left.
1288 result = NS_OK;
1290 if (NS_SUCCEEDED(result))
1292 result = mDomSelections[index]->
1293 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1294 PR_FALSE, PR_FALSE);
1297 return result;
1300 //END nsFrameSelection methods
1303 //BEGIN nsFrameSelection methods
1305 NS_IMETHODIMP
1306 nsTypedSelection::ToString(PRUnichar **aReturn)
1308 return ToStringWithFormat("text/plain",
1309 nsIDocumentEncoder::SkipInvisibleContent,
1310 0, aReturn);
1314 NS_IMETHODIMP
1315 nsTypedSelection::ToStringWithFormat(const char * aFormatType, PRUint32 aFlags,
1316 PRInt32 aWrapCol, PRUnichar **aReturn)
1318 nsresult rv = NS_OK;
1319 if (!aReturn)
1320 return NS_ERROR_NULL_POINTER;
1322 nsCAutoString formatType( NS_DOC_ENCODER_CONTRACTID_BASE );
1323 formatType.Append(aFormatType);
1324 nsCOMPtr<nsIDocumentEncoder> encoder =
1325 do_CreateInstance(formatType.get(), &rv);
1326 NS_ENSURE_SUCCESS(rv, rv);
1328 nsCOMPtr<nsIPresShell> shell;
1329 rv = GetPresShell(getter_AddRefs(shell));
1330 if (NS_FAILED(rv) || !shell) {
1331 return NS_ERROR_FAILURE;
1334 nsIDocument *doc = shell->GetDocument();
1335 NS_ENSURE_SUCCESS(rv, rv);
1337 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
1338 NS_ASSERTION(domDoc, "Need a document");
1340 // Flags should always include OutputSelectionOnly if we're coming from here:
1341 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1342 nsAutoString readstring;
1343 readstring.AssignASCII(aFormatType);
1344 rv = encoder->Init(domDoc, readstring, aFlags);
1345 NS_ENSURE_SUCCESS(rv, rv);
1347 encoder->SetSelection(this);
1348 if (aWrapCol != 0)
1349 encoder->SetWrapColumn(aWrapCol);
1351 nsAutoString tmp;
1352 rv = encoder->EncodeToString(tmp);
1353 *aReturn = ToNewUnicode(tmp);//get the unicode pointer from it. this is temporary
1354 return rv;
1357 NS_IMETHODIMP
1358 nsTypedSelection::SetInterlinePosition(PRBool aHintRight)
1360 if (!mFrameSelection)
1361 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
1362 nsFrameSelection::HINT hint;
1363 if (aHintRight)
1364 hint = nsFrameSelection::HINTRIGHT;
1365 else
1366 hint = nsFrameSelection::HINTLEFT;
1367 mFrameSelection->SetHint(hint);
1369 return NS_OK;
1372 NS_IMETHODIMP
1373 nsTypedSelection::GetInterlinePosition(PRBool *aHintRight)
1375 if (!mFrameSelection)
1376 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
1377 *aHintRight = (mFrameSelection->GetHint() == nsFrameSelection::HINTRIGHT);
1378 return NS_OK;
1381 nsPrevNextBidiLevels
1382 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1383 PRUint32 aContentOffset,
1384 PRBool aJumpLines) const
1386 return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines);
1389 nsPrevNextBidiLevels
1390 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1391 PRUint32 aContentOffset,
1392 HINT aHint,
1393 PRBool aJumpLines) const
1395 // Get the level of the frames on each side
1396 nsIFrame *currentFrame;
1397 PRInt32 currentOffset;
1398 PRInt32 frameStart, frameEnd;
1399 nsDirection direction;
1401 nsPrevNextBidiLevels levels;
1402 levels.SetData(nsnull, nsnull, 0, 0);
1404 currentFrame = GetFrameForNodeOffset(aNode, aContentOffset,
1405 aHint, &currentOffset);
1406 if (!currentFrame)
1407 return levels;
1409 currentFrame->GetOffsets(frameStart, frameEnd);
1411 if (0 == frameStart && 0 == frameEnd)
1412 direction = eDirPrevious;
1413 else if (frameStart == currentOffset)
1414 direction = eDirPrevious;
1415 else if (frameEnd == currentOffset)
1416 direction = eDirNext;
1417 else {
1418 // we are neither at the beginning nor at the end of the frame, so we have no worries
1419 levels.SetData(currentFrame, currentFrame,
1420 NS_GET_EMBEDDING_LEVEL(currentFrame),
1421 NS_GET_EMBEDDING_LEVEL(currentFrame));
1422 return levels;
1425 nsIFrame *newFrame;
1426 PRInt32 offset;
1427 PRBool jumpedLine;
1428 nsresult rv = currentFrame->GetFrameFromDirection(direction, PR_FALSE,
1429 aJumpLines, PR_TRUE,
1430 &newFrame, &offset, &jumpedLine);
1431 if (NS_FAILED(rv))
1432 newFrame = nsnull;
1434 PRUint8 baseLevel = NS_GET_BASE_LEVEL(currentFrame);
1435 PRUint8 currentLevel = NS_GET_EMBEDDING_LEVEL(currentFrame);
1436 PRUint8 newLevel = newFrame ? NS_GET_EMBEDDING_LEVEL(newFrame) : baseLevel;
1438 // If not jumping lines, disregard br frames, since they might be positioned incorrectly.
1439 // XXX This could be removed once bug 339786 is fixed.
1440 if (!aJumpLines) {
1441 if (currentFrame->GetType() == nsGkAtoms::brFrame) {
1442 currentFrame = nsnull;
1443 currentLevel = baseLevel;
1445 if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) {
1446 newFrame = nsnull;
1447 newLevel = baseLevel;
1451 if (direction == eDirNext)
1452 levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
1453 else
1454 levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
1456 return levels;
1459 nsresult
1460 nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn,
1461 nsDirection aDirection,
1462 PRUint8 aBidiLevel,
1463 nsIFrame **aFrameOut) const
1465 NS_ENSURE_STATE(mShell);
1466 PRUint8 foundLevel = 0;
1467 nsIFrame *foundFrame = aFrameIn;
1469 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
1470 nsresult result;
1471 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
1472 if (NS_FAILED(result))
1473 return result;
1475 result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1476 mShell->GetPresContext(), aFrameIn,
1477 eLeaf,
1478 PR_FALSE, // aVisual
1479 PR_FALSE, // aLockInScrollView
1480 PR_FALSE // aFollowOOFs
1482 if (NS_FAILED(result))
1483 return result;
1485 do {
1486 *aFrameOut = foundFrame;
1487 if (aDirection == eDirNext)
1488 frameTraversal->Next();
1489 else
1490 frameTraversal->Prev();
1492 foundFrame = frameTraversal->CurrentItem();
1493 if (!foundFrame)
1494 return NS_ERROR_FAILURE;
1495 foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame);
1497 } while (foundLevel > aBidiLevel);
1499 return NS_OK;
1503 nsresult
1504 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount)
1506 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1507 if (!mDomSelections[index])
1508 return NS_ERROR_NULL_POINTER;
1510 mMaintainedAmount = aAmount;
1512 const nsIRange* anchorFocusRange =
1513 mDomSelections[index]->GetAnchorFocusRange();
1514 if (anchorFocusRange) {
1515 return anchorFocusRange->CloneRange(getter_AddRefs(mMaintainRange));
1518 mMaintainRange = nsnull;
1519 return NS_OK;
1523 /** After moving the caret, its Bidi level is set according to the following rules:
1525 * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character.
1526 * After Home and End, set to the paragraph embedding level.
1527 * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters.
1528 * After mouse click, set to the level of the current frame.
1530 * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level.
1531 * BidiLevelFromMove is called when the caret is moved in response to a keyboard event
1533 * @param aPresShell is the presentation shell
1534 * @param aNode is the content node
1535 * @param aContentOffset is the new caret position, as an offset into aNode
1536 * @param aKeycode is the keyboard event that moved the caret to the new position
1537 * @param aHint is the hint indicating in what logical direction the caret moved
1539 void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell,
1540 nsIContent *aNode,
1541 PRUint32 aContentOffset,
1542 PRUint32 aKeycode,
1543 HINT aHint)
1545 switch (aKeycode) {
1547 // Right and Left: the new cursor Bidi level is the level of the character moved over
1548 case nsIDOMKeyEvent::DOM_VK_RIGHT:
1549 case nsIDOMKeyEvent::DOM_VK_LEFT:
1551 nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset,
1552 aHint, PR_FALSE);
1554 if (HINTLEFT == aHint)
1555 SetCaretBidiLevel(levels.mLevelBefore);
1556 else
1557 SetCaretBidiLevel(levels.mLevelAfter);
1558 break;
1561 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1562 case nsIDOMKeyEvent::DOM_VK_UP:
1563 case nsIDOMKeyEvent::DOM_VK_DOWN:
1564 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1565 aPresShell->SetCaretBidiLevel(NS_MIN(firstLevel, secondLevel));
1566 break;
1569 default:
1570 UndefineCaretBidiLevel();
1575 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
1577 * @param aNode is the content node
1578 * @param aContentOffset is the new caret position, as an offset into aNode
1580 void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode,
1581 PRUint32 aContentOffset)
1583 nsIFrame* clickInFrame=nsnull;
1584 PRInt32 OffsetNotUsed;
1586 clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed);
1587 if (!clickInFrame)
1588 return;
1590 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame));
1594 PRBool
1595 nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent,
1596 PRInt32 aOffset)
1598 if (!mMaintainRange)
1599 return PR_FALSE;
1601 if (!aContent) {
1602 return PR_FALSE;
1605 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1606 if (!mDomSelections[index])
1607 return PR_FALSE;
1609 nsINode* rangeStartNode = mMaintainRange->GetStartParent();
1610 nsINode* rangeEndNode = mMaintainRange->GetEndParent();
1611 PRInt32 rangeStartOffset = mMaintainRange->StartOffset();
1612 PRInt32 rangeEndOffset = mMaintainRange->EndOffset();
1614 PRInt32 relToStart =
1615 nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset,
1616 aContent, aOffset);
1617 PRInt32 relToEnd =
1618 nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset,
1619 aContent, aOffset);
1621 // If aContent/aOffset is inside the maintained selection, or if it is on the
1622 // "anchor" side of the maintained selection, we need to do something.
1623 if (relToStart < 0 && relToEnd > 0 ||
1624 (relToStart > 0 &&
1625 mDomSelections[index]->GetDirection() == eDirNext) ||
1626 (relToEnd < 0 &&
1627 mDomSelections[index]->GetDirection() == eDirPrevious)) {
1628 // Set the current range to the maintained range.
1629 mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange);
1630 if (relToStart < 0 && relToEnd > 0) {
1631 // We're inside the maintained selection, just keep it selected.
1632 return PR_TRUE;
1634 // Reverse the direction of the selection so that the anchor will be on the
1635 // far side of the maintained selection, relative to aContent/aOffset.
1636 mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext);
1638 return PR_FALSE;
1642 nsresult
1643 nsFrameSelection::HandleClick(nsIContent *aNewFocus,
1644 PRUint32 aContentOffset,
1645 PRUint32 aContentEndOffset,
1646 PRBool aContinueSelection,
1647 PRBool aMultipleSelection,
1648 PRBool aHint)
1650 if (!aNewFocus)
1651 return NS_ERROR_INVALID_ARG;
1653 InvalidateDesiredX();
1655 if (!aContinueSelection) {
1656 mMaintainRange = nsnull;
1657 if (!IsValidSelectionPoint(this, aNewFocus)) {
1658 mAncestorLimiter = nsnull;
1662 // Don't take focus when dragging off of a table
1663 if (!mDragSelectingCells)
1665 BidiLevelFromClick(aNewFocus, aContentOffset);
1666 PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON);
1667 if (aContinueSelection &&
1668 AdjustForMaintainedSelection(aNewFocus, aContentOffset))
1669 return NS_OK; //shift clicked to maintained selection. rejected.
1671 return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, HINT(aHint),
1672 aContinueSelection, aMultipleSelection);
1675 return NS_OK;
1678 void
1679 nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint)
1681 if (!aFrame || !mShell)
1682 return;
1684 nsresult result;
1685 nsIFrame *newFrame = 0;
1686 nsPoint newPoint;
1688 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint);
1689 if (NS_FAILED(result))
1690 return;
1691 if (!newFrame)
1692 return;
1694 nsIFrame::ContentOffsets offsets =
1695 newFrame->GetContentOffsetsFromPoint(newPoint);
1696 if (!offsets.content)
1697 return;
1699 if ((newFrame->GetStateBits() & NS_FRAME_SELECTED_CONTENT) &&
1700 AdjustForMaintainedSelection(offsets.content, offsets.offset))
1701 return;
1703 // Adjust offsets according to maintained amount
1704 if (mMaintainRange &&
1705 mMaintainedAmount != eSelectNoAmount) {
1707 nsINode* rangenode = mMaintainRange->GetStartParent();
1708 PRInt32 rangeOffset = mMaintainRange->StartOffset();
1709 PRInt32 relativePosition =
1710 nsContentUtils::ComparePoints(rangenode, rangeOffset,
1711 offsets.content, offsets.offset);
1713 nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext;
1714 nsSelectionAmount amount = mMaintainedAmount;
1715 if (amount == eSelectBeginLine && direction == eDirNext)
1716 amount = eSelectEndLine;
1718 PRInt32 offset;
1719 nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, HINTRIGHT, &offset);
1721 if (frame && amount == eSelectWord && direction == eDirPrevious) {
1722 // To avoid selecting the previous word when at start of word,
1723 // first move one character forward.
1724 nsPeekOffsetStruct charPos;
1725 charPos.SetData(eSelectCharacter, eDirNext, offset, 0,
1726 PR_FALSE, mLimiter != nsnull, PR_FALSE, PR_FALSE);
1727 if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
1728 frame = charPos.mResultFrame;
1729 offset = charPos.mContentOffset;
1733 nsPeekOffsetStruct pos;
1734 pos.SetData(amount, direction, offset, 0,
1735 PR_FALSE, mLimiter != nsnull, PR_FALSE, PR_FALSE);
1737 if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
1738 offsets.content = pos.mResultContent;
1739 offsets.offset = pos.mContentOffset;
1743 HandleClick(offsets.content, offsets.offset, offsets.offset,
1744 PR_TRUE, PR_FALSE, offsets.associateWithNext);
1747 nsresult
1748 nsFrameSelection::StartAutoScrollTimer(nsIFrame *aFrame,
1749 nsPoint aPoint,
1750 PRUint32 aDelay)
1752 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1753 if (!mDomSelections[index])
1754 return NS_ERROR_NULL_POINTER;
1756 return mDomSelections[index]->StartAutoScrollTimer(aFrame, aPoint, aDelay);
1759 void
1760 nsFrameSelection::StopAutoScrollTimer()
1762 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1763 if (!mDomSelections[index])
1764 return;
1766 mDomSelections[index]->StopAutoScrollTimer();
1770 hard to go from nodes to frames, easy the other way!
1772 nsresult
1773 nsFrameSelection::TakeFocus(nsIContent *aNewFocus,
1774 PRUint32 aContentOffset,
1775 PRUint32 aContentEndOffset,
1776 HINT aHint,
1777 PRBool aContinueSelection,
1778 PRBool aMultipleSelection)
1780 if (!aNewFocus)
1781 return NS_ERROR_NULL_POINTER;
1783 NS_ENSURE_STATE(mShell);
1785 if (!IsValidSelectionPoint(this,aNewFocus))
1786 return NS_ERROR_FAILURE;
1788 // Clear all table selection data
1789 mSelectingTableCellMode = 0;
1790 mDragSelectingCells = PR_FALSE;
1791 mStartSelectedCell = nsnull;
1792 mEndSelectedCell = nsnull;
1793 mAppendStartSelectedCell = nsnull;
1795 //HACKHACKHACK
1796 if (!aNewFocus->GetParent())
1797 return NS_ERROR_FAILURE;
1798 //END HACKHACKHACK /checking for root frames/content
1800 mHint = aHint;
1802 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1803 if (!mDomSelections[index])
1804 return NS_ERROR_NULL_POINTER;
1806 //traverse through document and unselect crap here
1807 if (!aContinueSelection) {//single click? setting cursor down
1808 PRUint32 batching = mBatching;//hack to use the collapse code.
1809 PRBool changes = mChangesDuringBatching;
1810 mBatching = 1;
1812 if (aMultipleSelection) {
1813 // Remove existing collapsed ranges as there's no point in having
1814 // non-anchor/focus collapsed ranges.
1815 mDomSelections[index]->RemoveCollapsedRanges();
1817 nsCOMPtr<nsIRange> newRange = new nsRange();
1818 if (!newRange) {
1819 return NS_ERROR_OUT_OF_MEMORY;
1822 newRange->SetStart(aNewFocus, aContentOffset);
1823 newRange->SetEnd(aNewFocus, aContentOffset);
1824 mDomSelections[index]->AddRange(newRange);
1825 mBatching = batching;
1826 mChangesDuringBatching = changes;
1828 else
1830 PRBool oldDesiredXSet = mDesiredXSet; //need to keep old desired X if it was set.
1831 mDomSelections[index]->Collapse(aNewFocus, aContentOffset);
1832 mDesiredXSet = oldDesiredXSet; //now reset desired X back.
1833 mBatching = batching;
1834 mChangesDuringBatching = changes;
1836 if (aContentEndOffset != aContentOffset)
1837 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);
1839 //find out if we are inside a table. if so, find out which one and which cell
1840 //once we do that, the next time we get a takefocus, check the parent tree.
1841 //if we are no longer inside same table ,cell then switch to table selection mode.
1842 // BUT only do this in an editor
1844 NS_ENSURE_STATE(mShell);
1845 PRInt16 displaySelection = mShell->GetSelectionFlags();
1847 // Editor has DISPLAY_ALL selection type
1848 if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
1850 mCellParent = GetCellParent(aNewFocus);
1851 #ifdef DEBUG_TABLE_SELECTION
1852 if (mCellParent)
1853 printf(" * TakeFocus - Collapsing into new cell\n");
1854 #endif
1857 else {
1858 // Now update the range list:
1859 if (aContinueSelection && aNewFocus)
1861 PRInt32 offset;
1862 nsINode *cellparent = GetCellParent(aNewFocus);
1863 if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode
1865 #ifdef DEBUG_TABLE_SELECTION
1866 printf(" * TakeFocus - moving into new cell\n");
1867 #endif
1868 nsMouseEvent event(PR_FALSE, 0, nsnull, nsMouseEvent::eReal);
1870 // Start selecting in the cell we were in before
1871 nsINode* parent = ParentOffset(mCellParent, &offset);
1872 if (parent)
1873 HandleTableSelection(parent, offset,
1874 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1876 // Find the parent of this new cell and extend selection to it
1877 parent = ParentOffset(cellparent, &offset);
1879 // XXXX We need to REALLY get the current key shift state
1880 // (we'd need to add event listener -- let's not bother for now)
1881 event.isShift = PR_FALSE; //aContinueSelection;
1882 if (parent)
1884 mCellParent = cellparent;
1885 // Continue selection into next cell
1886 HandleTableSelection(parent, offset,
1887 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1890 else
1892 // XXXX Problem: Shift+click in browser is appending text selection to selected table!!!
1893 // is this the place to erase seleced cells ?????
1894 if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough
1896 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff
1898 else
1899 mDomSelections[index]->Extend(aNewFocus, aContentOffset);
1904 // Don't notify selection listeners if batching is on:
1905 if (GetBatching())
1906 return NS_OK;
1907 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
1912 SelectionDetails*
1913 nsFrameSelection::LookUpSelection(nsIContent *aContent,
1914 PRInt32 aContentOffset,
1915 PRInt32 aContentLength,
1916 PRBool aSlowCheck) const
1918 if (!aContent || !mShell)
1919 return nsnull;
1921 SelectionDetails* details = nsnull;
1923 for (PRInt32 j = 0; j < nsISelectionController::NUM_SELECTIONTYPES; j++) {
1924 if (mDomSelections[j]) {
1925 mDomSelections[j]->LookUpSelection(aContent, aContentOffset,
1926 aContentLength, &details, (SelectionType)(1<<j), aSlowCheck);
1930 return details;
1933 void
1934 nsFrameSelection::SetMouseDownState(PRBool aState)
1936 if (mMouseDownState == aState)
1937 return;
1939 mMouseDownState = aState;
1941 if (!mMouseDownState)
1943 mDragSelectingCells = PR_FALSE;
1944 PostReason(nsISelectionListener::MOUSEUP_REASON);
1945 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); //notify that reason is mouse up please.
1949 nsISelection*
1950 nsFrameSelection::GetSelection(SelectionType aType) const
1952 PRInt8 index = GetIndexFromSelectionType(aType);
1953 if (index < 0)
1954 return nsnull;
1956 return static_cast<nsISelection*>(mDomSelections[index]);
1959 nsresult
1960 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType,
1961 SelectionRegion aRegion,
1962 PRBool aIsSynchronous) const
1964 PRInt8 index = GetIndexFromSelectionType(aType);
1965 if (index < 0)
1966 return NS_ERROR_INVALID_ARG;
1968 if (!mDomSelections[index])
1969 return NS_ERROR_NULL_POINTER;
1971 // After ScrollSelectionIntoView(), the pending notifications might be
1972 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
1973 return mDomSelections[index]->ScrollIntoView(aRegion, aIsSynchronous,
1974 PR_TRUE);
1977 nsresult
1978 nsFrameSelection::RepaintSelection(SelectionType aType) const
1980 PRInt8 index = GetIndexFromSelectionType(aType);
1981 if (index < 0)
1982 return NS_ERROR_INVALID_ARG;
1983 if (!mDomSelections[index])
1984 return NS_ERROR_NULL_POINTER;
1985 NS_ENSURE_STATE(mShell);
1986 return mDomSelections[index]->Repaint(mShell->GetPresContext());
1989 nsIFrame*
1990 nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode,
1991 PRInt32 aOffset,
1992 HINT aHint,
1993 PRInt32 *aReturnOffset) const
1995 if (!aNode || !aReturnOffset || !mShell)
1996 return nsnull;
1998 if (aOffset < 0)
1999 return nsnull;
2001 *aReturnOffset = aOffset;
2003 nsCOMPtr<nsIContent> theNode = aNode;
2005 if (aNode->IsElement())
2007 PRInt32 childIndex = 0;
2008 PRInt32 numChildren = theNode->GetChildCount();
2010 if (aHint == HINTLEFT)
2012 if (aOffset > 0)
2013 childIndex = aOffset - 1;
2014 else
2015 childIndex = aOffset;
2017 else // HINTRIGHT
2019 if (aOffset >= numChildren)
2021 if (numChildren > 0)
2022 childIndex = numChildren - 1;
2023 else
2024 childIndex = 0;
2026 else
2027 childIndex = aOffset;
2030 if (childIndex > 0 || numChildren > 0) {
2031 nsCOMPtr<nsIContent> childNode = theNode->GetChildAt(childIndex);
2033 if (!childNode)
2034 return nsnull;
2036 theNode = childNode;
2039 #ifdef DONT_DO_THIS_YET
2040 // XXX: We can't use this code yet because the hinting
2041 // can cause us to attach to the wrong line frame.
2043 // Now that we have the child node, check if it too
2044 // can contain children. If so, call this method again!
2046 if (theNode->IsElement())
2048 PRInt32 newOffset = 0;
2050 if (aOffset > childIndex)
2052 numChildren = theNode->GetChildCount();
2054 newOffset = numChildren;
2057 return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset);
2059 else
2060 #endif // DONT_DO_THIS_YET
2062 // Check to see if theNode is a text node. If it is, translate
2063 // aOffset into an offset into the text node.
2065 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(theNode);
2067 if (textNode)
2069 if (aOffset > childIndex)
2071 PRUint32 textLength = 0;
2073 nsresult rv = textNode->GetLength(&textLength);
2074 if (NS_FAILED(rv))
2075 return nsnull;
2077 *aReturnOffset = (PRInt32)textLength;
2079 else
2080 *aReturnOffset = 0;
2085 nsIFrame* returnFrame = theNode->GetPrimaryFrame();
2086 if (!returnFrame)
2087 return nsnull;
2089 // find the child frame containing the offset we want
2090 returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint,
2091 &aOffset, &returnFrame);
2092 return returnFrame;
2095 void
2096 nsFrameSelection::CommonPageMove(PRBool aForward,
2097 PRBool aExtend,
2098 nsIScrollableFrame* aScrollableFrame)
2100 // expected behavior for PageMove is to scroll AND move the caret
2101 // and remain relative position of the caret in view. see Bug 4302.
2103 //get the frame from the scrollable view
2105 nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
2106 if (!scrolledFrame)
2107 return;
2109 // find out where the caret is.
2110 // we should know mDesiredX value of nsFrameSelection, but I havent seen that behavior in other windows applications yet.
2111 nsISelection* domSel = GetSelection(nsISelectionController::SELECTION_NORMAL);
2112 if (!domSel)
2113 return;
2115 nsRefPtr<nsCaret> caret = mShell->GetCaret();
2117 nsRect caretPos;
2118 nsIFrame* caretFrame = caret->GetGeometry(domSel, &caretPos);
2119 if (!caretFrame)
2120 return;
2122 //need to adjust caret jump by percentage scroll
2123 nsSize scrollDelta = aScrollableFrame->GetPageScrollAmount();
2125 if (aForward)
2126 caretPos.y += scrollDelta.height;
2127 else
2128 caretPos.y -= scrollDelta.height;
2130 caretPos += caretFrame->GetOffsetTo(scrolledFrame);
2132 // get a content at desired location
2133 nsPoint desiredPoint;
2134 desiredPoint.x = caretPos.x;
2135 desiredPoint.y = caretPos.y + caretPos.height/2;
2136 nsIFrame::ContentOffsets offsets =
2137 scrolledFrame->GetContentOffsetsFromPoint(desiredPoint);
2139 if (!offsets.content)
2140 return;
2142 // scroll one page
2143 aScrollableFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
2144 nsIScrollableFrame::PAGES,
2145 nsIScrollableFrame::SMOOTH);
2147 // place the caret
2148 HandleClick(offsets.content, offsets.offset,
2149 offsets.offset, aExtend, PR_FALSE, PR_TRUE);
2152 nsresult
2153 nsFrameSelection::CharacterMove(PRBool aForward, PRBool aExtend)
2155 if (aForward)
2156 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectCharacter);
2157 else
2158 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectCharacter);
2161 nsresult
2162 nsFrameSelection::CharacterExtendForDelete()
2164 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectCharacter);
2167 nsresult
2168 nsFrameSelection::CharacterExtendForBackspace()
2170 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, PR_TRUE, eSelectCharacter);
2173 nsresult
2174 nsFrameSelection::WordMove(PRBool aForward, PRBool aExtend)
2176 if (aForward)
2177 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectWord);
2178 else
2179 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectWord);
2182 nsresult
2183 nsFrameSelection::WordExtendForDelete(PRBool aForward)
2185 if (aForward)
2186 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectWord);
2187 else
2188 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, PR_TRUE, eSelectWord);
2191 nsresult
2192 nsFrameSelection::LineMove(PRBool aForward, PRBool aExtend)
2194 if (aForward)
2195 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DOWN,aExtend,eSelectLine);
2196 else
2197 return MoveCaret(nsIDOMKeyEvent::DOM_VK_UP,aExtend,eSelectLine);
2200 nsresult
2201 nsFrameSelection::IntraLineMove(PRBool aForward, PRBool aExtend)
2203 if (aForward)
2204 return MoveCaret(nsIDOMKeyEvent::DOM_VK_END,aExtend,eSelectLine);
2205 else
2206 return MoveCaret(nsIDOMKeyEvent::DOM_VK_HOME,aExtend,eSelectLine);
2209 nsresult
2210 nsFrameSelection::SelectAll()
2212 nsCOMPtr<nsIContent> rootContent;
2213 if (mLimiter)
2215 rootContent = mLimiter;//addrefit
2217 else if (mAncestorLimiter) {
2218 rootContent = mAncestorLimiter;
2220 else
2222 NS_ENSURE_STATE(mShell);
2223 nsIDocument *doc = mShell->GetDocument();
2224 if (!doc)
2225 return NS_ERROR_FAILURE;
2226 rootContent = doc->GetRootElement();
2227 if (!rootContent)
2228 return NS_ERROR_FAILURE;
2230 PRInt32 numChildren = rootContent->GetChildCount();
2231 PostReason(nsISelectionListener::NO_REASON);
2232 return TakeFocus(rootContent, 0, numChildren, HINTLEFT, PR_FALSE, PR_FALSE);
2235 //////////END FRAMESELECTION
2237 void
2238 nsFrameSelection::StartBatchChanges()
2240 mBatching++;
2243 void
2244 nsFrameSelection::EndBatchChanges()
2246 mBatching--;
2247 NS_ASSERTION(mBatching >=0,"Bad mBatching");
2248 if (mBatching == 0 && mChangesDuringBatching){
2249 mChangesDuringBatching = PR_FALSE;
2250 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
2255 nsresult
2256 nsFrameSelection::NotifySelectionListeners(SelectionType aType)
2258 PRInt8 index = GetIndexFromSelectionType(aType);
2259 if (index >=0 && mDomSelections[index])
2261 return mDomSelections[index]->NotifySelectionListeners();
2263 return NS_ERROR_FAILURE;
2266 // Start of Table Selection methods
2268 static PRBool IsCell(nsIContent *aContent)
2270 return ((aContent->Tag() == nsGkAtoms::td ||
2271 aContent->Tag() == nsGkAtoms::th) &&
2272 aContent->IsHTML());
2275 nsITableCellLayout*
2276 nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const
2278 NS_ENSURE_TRUE(mShell, nsnull);
2279 nsITableCellLayout *cellLayoutObject =
2280 do_QueryFrame(aCellContent->GetPrimaryFrame());
2281 return cellLayoutObject;
2284 nsITableLayout*
2285 nsFrameSelection::GetTableLayout(nsIContent *aTableContent) const
2287 NS_ENSURE_TRUE(mShell, nsnull);
2288 nsITableLayout *tableLayoutObject =
2289 do_QueryFrame(aTableContent->GetPrimaryFrame());
2290 return tableLayoutObject;
2293 nsresult
2294 nsFrameSelection::ClearNormalSelection()
2296 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2297 if (!mDomSelections[index])
2298 return NS_ERROR_NULL_POINTER;
2300 return mDomSelections[index]->RemoveAllRanges();
2303 static nsIContent*
2304 GetFirstSelectedContent(nsIRange* aRange)
2306 if (!aRange) {
2307 return nsnull;
2310 NS_PRECONDITION(aRange->GetStartParent(), "Must have start parent!");
2311 NS_PRECONDITION(aRange->GetStartParent()->IsElement(),
2312 "Unexpected parent");
2314 return aRange->GetStartParent()->GetChildAt(aRange->StartOffset());
2317 // Table selection support.
2318 // TODO: Separate table methods into a separate nsITableSelection interface
2319 nsresult
2320 nsFrameSelection::HandleTableSelection(nsINode *aParentContent,
2321 PRInt32 aContentOffset,
2322 PRInt32 aTarget,
2323 nsMouseEvent *aMouseEvent)
2325 NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
2326 NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
2328 if (mMouseDownState && mDragSelectingCells && (aTarget & nsISelectionPrivate::TABLESELECTION_TABLE))
2330 // We were selecting cells and user drags mouse in table border or inbetween cells,
2331 // just do nothing
2332 return NS_OK;
2335 nsresult result = NS_OK;
2337 nsIContent *childContent = aParentContent->GetChildAt(aContentOffset);
2339 // When doing table selection, always set the direction to next so
2340 // we can be sure that anchorNode's offset always points to the
2341 // selected cell
2342 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2343 if (!mDomSelections[index])
2344 return NS_ERROR_NULL_POINTER;
2346 mDomSelections[index]->SetDirection(eDirNext);
2348 // Stack-class to wrap all table selection changes in
2349 // BeginBatchChanges() / EndBatchChanges()
2350 nsSelectionBatcher selectionBatcher(mDomSelections[index]);
2352 PRInt32 startRowIndex, startColIndex, curRowIndex, curColIndex;
2353 if (mMouseDownState && mDragSelectingCells)
2355 // We are drag-selecting
2356 if (aTarget != nsISelectionPrivate::TABLESELECTION_TABLE)
2358 // If dragging in the same cell as last event, do nothing
2359 if (mEndSelectedCell == childContent)
2360 return NS_OK;
2362 #ifdef DEBUG_TABLE_SELECTION
2363 printf(" mStartSelectedCell = %x, mEndSelectedCell = %x, childContent = %x \n", mStartSelectedCell, mEndSelectedCell, childContent);
2364 #endif
2365 // aTarget can be any "cell mode",
2366 // so we can easily drag-select rows and columns
2367 // Once we are in row or column mode,
2368 // we can drift into any cell to stay in that mode
2369 // even if aTarget = TABLESELECTION_CELL
2371 if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW ||
2372 mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN)
2374 if (mEndSelectedCell)
2376 // Also check if cell is in same row/col
2377 result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex);
2378 if (NS_FAILED(result)) return result;
2379 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2380 if (NS_FAILED(result)) return result;
2382 #ifdef DEBUG_TABLE_SELECTION
2383 printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex);
2384 #endif
2385 if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) ||
2386 (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex))
2387 return NS_OK;
2389 #ifdef DEBUG_TABLE_SELECTION
2390 printf(" Dragged into a new column or row\n");
2391 #endif
2392 // Continue dragging row or column selection
2393 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2395 else if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_CELL)
2397 #ifdef DEBUG_TABLE_SELECTION
2398 printf("HandleTableSelection: Dragged into a new cell\n");
2399 #endif
2400 // Trick for quick selection of rows and columns
2401 // Hold down shift, then start selecting in one direction
2402 // If next cell dragged into is in same row, select entire row,
2403 // if next cell is in same column, select entire column
2404 if (mStartSelectedCell && aMouseEvent->isShift)
2406 result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex);
2407 if (NS_FAILED(result)) return result;
2408 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2409 if (NS_FAILED(result)) return result;
2411 if (startRowIndex == curRowIndex ||
2412 startColIndex == curColIndex)
2414 // Force new selection block
2415 mStartSelectedCell = nsnull;
2416 mDomSelections[index]->RemoveAllRanges();
2418 if (startRowIndex == curRowIndex)
2419 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_ROW;
2420 else
2421 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_COLUMN;
2423 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2427 // Reselect block of cells to new end location
2428 return SelectBlockOfCells(mStartSelectedCell, childContent);
2431 // Do nothing if dragging in table, but outside a cell
2432 return NS_OK;
2434 else
2436 // Not dragging -- mouse event is down or up
2437 if (mMouseDownState)
2439 #ifdef DEBUG_TABLE_SELECTION
2440 printf("HandleTableSelection: Mouse down event\n");
2441 #endif
2442 // Clear cell we stored in mouse-down
2443 mUnselectCellOnMouseUp = nsnull;
2445 if (aTarget == nsISelectionPrivate::TABLESELECTION_CELL)
2447 PRBool isSelected = PR_FALSE;
2449 // Check if we have other selected cells
2450 nsIContent* previousCellNode =
2451 GetFirstSelectedContent(GetFirstCellRange());
2452 if (previousCellNode)
2454 // We have at least 1 other selected cell
2456 // Check if new cell is already selected
2457 nsIFrame *cellFrame = childContent->GetPrimaryFrame();
2458 if (!cellFrame) return NS_ERROR_NULL_POINTER;
2459 result = cellFrame->GetSelected(&isSelected);
2460 if (NS_FAILED(result)) return result;
2462 else
2464 // No cells selected -- remove non-cell selection
2465 mDomSelections[index]->RemoveAllRanges();
2467 mDragSelectingCells = PR_TRUE; // Signal to start drag-cell-selection
2468 mSelectingTableCellMode = aTarget;
2469 // Set start for new drag-selection block (not appended)
2470 mStartSelectedCell = childContent;
2471 // The initial block end is same as the start
2472 mEndSelectedCell = childContent;
2474 if (isSelected)
2476 // Remember this cell to (possibly) unselect it on mouseup
2477 mUnselectCellOnMouseUp = childContent;
2478 #ifdef DEBUG_TABLE_SELECTION
2479 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2480 #endif
2482 else
2484 // Select an unselected cell
2485 // but first remove existing selection if not in same table
2486 if (previousCellNode &&
2487 !IsInSameTable(previousCellNode, childContent))
2489 mDomSelections[index]->RemoveAllRanges();
2490 // Reset selection mode that is cleared in RemoveAllRanges
2491 mSelectingTableCellMode = aTarget;
2494 return SelectCellElement(childContent);
2497 return NS_OK;
2499 else if (aTarget == nsISelectionPrivate::TABLESELECTION_TABLE)
2501 //TODO: We currently select entire table when clicked between cells,
2502 // should we restrict to only around border?
2503 // *** How do we get location data for cell and click?
2504 mDragSelectingCells = PR_FALSE;
2505 mStartSelectedCell = nsnull;
2506 mEndSelectedCell = nsnull;
2508 // Remove existing selection and select the table
2509 mDomSelections[index]->RemoveAllRanges();
2510 return CreateAndAddRange(aParentContent, aContentOffset);
2512 else if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW || aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2514 #ifdef DEBUG_TABLE_SELECTION
2515 printf("aTarget == %d\n", aTarget);
2516 #endif
2518 // Start drag-selecting mode so multiple rows/cols can be selected
2519 // Note: Currently, nsFrame::GetDataForTableSelection
2520 // will never call us for row or column selection on mouse down
2521 mDragSelectingCells = PR_TRUE;
2523 // Force new selection block
2524 mStartSelectedCell = nsnull;
2525 mDomSelections[index]->RemoveAllRanges();
2526 // Always do this AFTER RemoveAllRanges
2527 mSelectingTableCellMode = aTarget;
2528 return SelectRowOrColumn(childContent, aTarget);
2531 else
2533 #ifdef DEBUG_TABLE_SELECTION
2534 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%d\n", mDragSelectingCells, mStartSelectedCell);
2535 #endif
2536 // First check if we are extending a block selection
2537 PRInt32 rangeCount;
2538 result = mDomSelections[index]->GetRangeCount(&rangeCount);
2539 if (NS_FAILED(result))
2540 return result;
2542 if (rangeCount > 0 && aMouseEvent->isShift &&
2543 mAppendStartSelectedCell && mAppendStartSelectedCell != childContent)
2545 // Shift key is down: append a block selection
2546 mDragSelectingCells = PR_FALSE;
2547 return SelectBlockOfCells(mAppendStartSelectedCell, childContent);
2550 if (mDragSelectingCells)
2551 mAppendStartSelectedCell = mStartSelectedCell;
2553 mDragSelectingCells = PR_FALSE;
2554 mStartSelectedCell = nsnull;
2555 mEndSelectedCell = nsnull;
2557 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2558 // else stop table selection mode
2559 PRBool doMouseUpAction = PR_FALSE;
2560 #ifdef XP_MACOSX
2561 doMouseUpAction = aMouseEvent->isMeta;
2562 #else
2563 doMouseUpAction = aMouseEvent->isControl;
2564 #endif
2565 if (!doMouseUpAction)
2567 #ifdef DEBUG_TABLE_SELECTION
2568 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%d\n", mAppendStartSelectedCell);
2569 #endif
2570 return NS_OK;
2572 // Unselect a cell only if it wasn't
2573 // just selected on mousedown
2574 if( childContent == mUnselectCellOnMouseUp)
2576 // Scan ranges to find the cell to unselect (the selection range to remove)
2577 // XXXbz it's really weird that this lives outside the loop, so once we
2578 // find one we keep looking at it even if we find no more cells...
2579 nsINode* previousCellParent = nsnull;
2580 #ifdef DEBUG_TABLE_SELECTION
2581 printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount);
2582 #endif
2583 for( PRInt32 i = 0; i < rangeCount; i++)
2585 // Strong reference, because sometimes we want to remove
2586 // this range, and then we might be the only owner.
2587 nsCOMPtr<nsIRange> range = mDomSelections[index]->GetRangeAt(i);
2588 if (!range) return NS_ERROR_NULL_POINTER;
2590 nsINode* parent = range->GetStartParent();
2591 if (!parent) return NS_ERROR_NULL_POINTER;
2593 PRInt32 offset = range->StartOffset();
2594 // Be sure previous selection is a table cell
2595 nsIContent* child = parent->GetChildAt(offset);
2596 if (child && IsCell(child))
2597 previousCellParent = parent;
2599 // We're done if we didn't find parent of a previously-selected cell
2600 if (!previousCellParent) break;
2602 if (previousCellParent == aParentContent && offset == aContentOffset)
2604 // Cell is already selected
2605 if (rangeCount == 1)
2607 #ifdef DEBUG_TABLE_SELECTION
2608 printf("HandleTableSelection: Unselecting single selected cell\n");
2609 #endif
2610 // This was the only cell selected.
2611 // Collapse to "normal" selection inside the cell
2612 mStartSelectedCell = nsnull;
2613 mEndSelectedCell = nsnull;
2614 mAppendStartSelectedCell = nsnull;
2615 //TODO: We need a "Collapse to just before deepest child" routine
2616 // Even better, should we collapse to just after the LAST deepest child
2617 // (i.e., at the end of the cell's contents)?
2618 return mDomSelections[index]->Collapse(childContent, 0);
2620 #ifdef DEBUG_TABLE_SELECTION
2621 printf("HandleTableSelection: Removing cell from multi-cell selection\n");
2622 #endif
2623 // Unselecting the start of previous block
2624 // XXX What do we use now!
2625 if (childContent == mAppendStartSelectedCell)
2626 mAppendStartSelectedCell = nsnull;
2628 // Deselect cell by removing its range from selection
2629 return mDomSelections[index]->RemoveRange(range);
2632 mUnselectCellOnMouseUp = nsnull;
2636 return result;
2639 nsresult
2640 nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell)
2642 NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
2643 NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
2644 mEndSelectedCell = aEndCell;
2646 nsCOMPtr<nsIContent> startCell;
2647 nsresult result = NS_OK;
2649 // If new end cell is in a different table, do nothing
2650 nsIContent* table = IsInSameTable(aStartCell, aEndCell);
2651 if (!table) {
2652 return NS_OK;
2655 // Get starting and ending cells' location in the cellmap
2656 PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex;
2657 result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
2658 if(NS_FAILED(result)) return result;
2659 result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
2660 if(NS_FAILED(result)) return result;
2662 if (mDragSelectingCells)
2664 // Drag selecting: remove selected cells outside of new block limits
2665 UnselectCells(table, startRowIndex, startColIndex, endRowIndex, endColIndex,
2666 PR_TRUE);
2669 // Note that we select block in the direction of user's mouse dragging,
2670 // which means start cell may be after the end cell in either row or column
2671 return AddCellsToSelection(table, startRowIndex, startColIndex,
2672 endRowIndex, endColIndex);
2675 nsresult
2676 nsFrameSelection::UnselectCells(nsIContent *aTableContent,
2677 PRInt32 aStartRowIndex,
2678 PRInt32 aStartColumnIndex,
2679 PRInt32 aEndRowIndex,
2680 PRInt32 aEndColumnIndex,
2681 PRBool aRemoveOutsideOfCellRange)
2683 PRInt8 index =
2684 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2685 if (!mDomSelections[index])
2686 return NS_ERROR_NULL_POINTER;
2688 nsITableLayout *tableLayout = GetTableLayout(aTableContent);
2689 if (!tableLayout)
2690 return NS_ERROR_FAILURE;
2692 PRInt32 minRowIndex = NS_MIN(aStartRowIndex, aEndRowIndex);
2693 PRInt32 maxRowIndex = NS_MAX(aStartRowIndex, aEndRowIndex);
2694 PRInt32 minColIndex = NS_MIN(aStartColumnIndex, aEndColumnIndex);
2695 PRInt32 maxColIndex = NS_MAX(aStartColumnIndex, aEndColumnIndex);
2697 // Strong reference because we sometimes remove the range
2698 nsCOMPtr<nsIRange> range = GetFirstCellRange();
2699 nsIContent* cellNode = GetFirstSelectedContent(range);
2700 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2702 PRInt32 curRowIndex, curColIndex;
2703 while (cellNode)
2705 nsresult result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2706 if (NS_FAILED(result))
2707 return result;
2709 #ifdef DEBUG_TABLE_SELECTION
2710 if (!range)
2711 printf("RemoveCellsToSelection -- range is null\n");
2712 #endif
2714 if (range) {
2715 if (aRemoveOutsideOfCellRange) {
2716 if (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
2717 curColIndex < minColIndex || curColIndex > maxColIndex) {
2719 mDomSelections[index]->RemoveRange(range);
2720 // Since we've removed the range, decrement pointer to next range
2721 mSelectedCellIndex--;
2724 } else {
2725 // Remove cell from selection if it belongs to the given cells range or
2726 // it is spanned onto the cells range.
2727 nsCOMPtr<nsIDOMElement> cellElement;
2728 PRInt32 origRowIndex, origColIndex, rowSpan, colSpan,
2729 actualRowSpan, actualColSpan;
2730 PRBool isSelected;
2732 result = tableLayout->GetCellDataAt(curRowIndex, curColIndex,
2733 *getter_AddRefs(cellElement),
2734 origRowIndex, origColIndex,
2735 rowSpan, colSpan,
2736 actualRowSpan, actualColSpan,
2737 isSelected);
2738 if (NS_FAILED(result))
2739 return result;
2741 if (origRowIndex <= maxRowIndex &&
2742 origRowIndex + actualRowSpan - 1 >= minRowIndex &&
2743 origColIndex <= maxColIndex &&
2744 origColIndex + actualColSpan - 1 >= minColIndex) {
2746 mDomSelections[index]->RemoveRange(range);
2747 // Since we've removed the range, decrement pointer to next range
2748 mSelectedCellIndex--;
2753 range = GetNextCellRange();
2754 cellNode = GetFirstSelectedContent(range);
2755 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2758 return NS_OK;
2761 nsresult
2762 nsFrameSelection::AddCellsToSelection(nsIContent *aTableContent,
2763 PRInt32 aStartRowIndex,
2764 PRInt32 aStartColumnIndex,
2765 PRInt32 aEndRowIndex,
2766 PRInt32 aEndColumnIndex)
2768 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2769 if (!mDomSelections[index])
2770 return NS_ERROR_NULL_POINTER;
2772 // Get TableLayout interface to access cell data based on cellmap location
2773 // frames are not ref counted, so don't use an nsCOMPtr
2774 nsITableLayout *tableLayoutObject = GetTableLayout(aTableContent);
2775 if (!tableLayoutObject) // Check that |table| is a table.
2776 return NS_ERROR_FAILURE;
2778 nsCOMPtr<nsIDOMElement> cellElement;
2779 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan,
2780 curRowIndex, curColIndex;
2781 PRBool isSelected;
2782 nsresult result = NS_OK;
2784 PRInt32 row = aStartRowIndex;
2785 while(PR_TRUE)
2787 PRInt32 col = aStartColumnIndex;
2788 while(PR_TRUE)
2790 result = tableLayoutObject->GetCellDataAt(row, col, *getter_AddRefs(cellElement),
2791 curRowIndex, curColIndex, rowSpan, colSpan,
2792 actualRowSpan, actualColSpan, isSelected);
2793 if (NS_FAILED(result)) return result;
2795 NS_ASSERTION(actualColSpan, "!actualColSpan is 0!");
2797 // Skip cells that are spanned from previous locations or are already selected
2798 if (!isSelected && cellElement && row == curRowIndex && col == curColIndex)
2800 nsCOMPtr<nsIContent> cellContent = do_QueryInterface(cellElement);
2801 result = SelectCellElement(cellContent);
2802 if (NS_FAILED(result)) return result;
2804 // Done when we reach end column
2805 if (col == aEndColumnIndex) break;
2807 if (aStartColumnIndex < aEndColumnIndex)
2808 col ++;
2809 else
2810 col--;
2812 if (row == aEndRowIndex) break;
2814 if (aStartRowIndex < aEndRowIndex)
2815 row++;
2816 else
2817 row--;
2819 return result;
2822 nsresult
2823 nsFrameSelection::RemoveCellsFromSelection(nsIContent *aTable,
2824 PRInt32 aStartRowIndex,
2825 PRInt32 aStartColumnIndex,
2826 PRInt32 aEndRowIndex,
2827 PRInt32 aEndColumnIndex)
2829 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2830 aEndRowIndex, aEndColumnIndex, PR_FALSE);
2833 nsresult
2834 nsFrameSelection::RestrictCellsToSelection(nsIContent *aTable,
2835 PRInt32 aStartRowIndex,
2836 PRInt32 aStartColumnIndex,
2837 PRInt32 aEndRowIndex,
2838 PRInt32 aEndColumnIndex)
2840 return UnselectCells(aTable, aStartRowIndex, aStartColumnIndex,
2841 aEndRowIndex, aEndColumnIndex, PR_TRUE);
2844 nsresult
2845 nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, PRUint32 aTarget)
2847 if (!aCellContent) return NS_ERROR_NULL_POINTER;
2849 nsIContent* table = GetParentTable(aCellContent);
2850 if (!table) return NS_ERROR_NULL_POINTER;
2852 // Get table and cell layout interfaces to access
2853 // cell data based on cellmap location
2854 // Frames are not ref counted, so don't use an nsCOMPtr
2855 nsITableLayout *tableLayout = GetTableLayout(table);
2856 if (!tableLayout) return NS_ERROR_FAILURE;
2857 nsITableCellLayout *cellLayout = GetCellLayout(aCellContent);
2858 if (!cellLayout) return NS_ERROR_FAILURE;
2860 // Get location of target cell:
2861 PRInt32 rowIndex, colIndex, curRowIndex, curColIndex;
2862 nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
2863 if (NS_FAILED(result)) return result;
2865 // Be sure we start at proper beginning
2866 // (This allows us to select row or col given ANY cell!)
2867 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2868 colIndex = 0;
2869 if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2870 rowIndex = 0;
2872 nsCOMPtr<nsIDOMElement> cellElement;
2873 nsCOMPtr<nsIContent> firstCell;
2874 nsCOMPtr<nsIDOMElement> lastCell;
2875 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan;
2876 PRBool isSelected;
2878 do {
2879 // Loop through all cells in column or row to find first and last
2880 result = tableLayout->GetCellDataAt(rowIndex, colIndex, *getter_AddRefs(cellElement),
2881 curRowIndex, curColIndex, rowSpan, colSpan,
2882 actualRowSpan, actualColSpan, isSelected);
2883 if (NS_FAILED(result)) return result;
2884 if (cellElement)
2886 NS_ASSERTION(actualRowSpan > 0 && actualColSpan> 0, "SelectRowOrColumn: Bad rowspan or colspan\n");
2887 if (!firstCell)
2888 firstCell = do_QueryInterface(cellElement);
2890 lastCell = cellElement;
2892 // Move to next cell in cellmap, skipping spanned locations
2893 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2894 colIndex += actualColSpan;
2895 else
2896 rowIndex += actualRowSpan;
2899 while (cellElement);
2901 // Use SelectBlockOfCells:
2902 // This will replace existing selection,
2903 // but allow unselecting by dragging out of selected region
2904 if (firstCell && lastCell)
2906 if (!mStartSelectedCell)
2908 // We are starting a new block, so select the first cell
2909 result = SelectCellElement(firstCell);
2910 if (NS_FAILED(result)) return result;
2911 mStartSelectedCell = firstCell;
2913 nsCOMPtr<nsIContent> lastCellContent = do_QueryInterface(lastCell);
2914 result = SelectBlockOfCells(mStartSelectedCell, lastCellContent);
2916 // This gets set to the cell at end of row/col,
2917 // but we need it to be the cell under cursor
2918 mEndSelectedCell = aCellContent;
2919 return result;
2922 #if 0
2923 // This is a more efficient strategy that appends row to current selection,
2924 // but doesn't allow dragging OFF of an existing selection to unselect!
2925 do {
2926 // Loop through all cells in column or row
2927 result = tableLayout->GetCellDataAt(rowIndex, colIndex,
2928 getter_AddRefs(cellElement),
2929 curRowIndex, curColIndex,
2930 rowSpan, colSpan,
2931 actualRowSpan, actualColSpan,
2932 isSelected);
2933 if (NS_FAILED(result)) return result;
2934 // We're done when cell is not found
2935 if (!cellElement) break;
2938 // Check spans else we infinitely loop
2939 NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
2940 NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
2942 // Skip cells that are already selected or span from outside our region
2943 if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
2945 result = SelectCellElement(cellElement);
2946 if (NS_FAILED(result)) return result;
2948 // Move to next row or column in cellmap, skipping spanned locations
2949 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2950 colIndex += actualColSpan;
2951 else
2952 rowIndex += actualRowSpan;
2954 while (cellElement);
2955 #endif
2957 return NS_OK;
2960 nsIContent*
2961 nsFrameSelection::GetFirstCellNodeInRange(nsIRange *aRange) const
2963 if (!aRange) return nsnull;
2965 nsINode* startParent = aRange->GetStartParent();
2966 if (!startParent)
2967 return nsnull;
2969 PRInt32 offset = aRange->StartOffset();
2971 nsIContent* childContent = startParent->GetChildAt(offset);
2972 if (!childContent)
2973 return nsnull;
2974 // Don't return node if not a cell
2975 if (!IsCell(childContent))
2976 return nsnull;
2978 return childContent;
2981 nsIRange*
2982 nsFrameSelection::GetFirstCellRange()
2984 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2985 if (!mDomSelections[index])
2986 return nsnull;
2988 nsIRange* firstRange = mDomSelections[index]->GetRangeAt(0);
2989 if (!GetFirstCellNodeInRange(firstRange)) {
2990 return nsnull;
2993 // Setup for next cell
2994 mSelectedCellIndex = 1;
2996 return firstRange;
2999 nsIRange*
3000 nsFrameSelection::GetNextCellRange()
3002 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3003 if (!mDomSelections[index])
3004 return nsnull;
3006 nsIRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex);
3008 // Get first node in next range of selection - test if it's a cell
3009 if (!GetFirstCellNodeInRange(range)) {
3010 return nsnull;
3013 // Setup for next cell
3014 mSelectedCellIndex++;
3016 return range;
3019 nsresult
3020 nsFrameSelection::GetCellIndexes(nsIContent *aCell,
3021 PRInt32 &aRowIndex,
3022 PRInt32 &aColIndex)
3024 if (!aCell) return NS_ERROR_NULL_POINTER;
3026 aColIndex=0; // initialize out params
3027 aRowIndex=0;
3029 nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell);
3030 if (!cellLayoutObject) return NS_ERROR_FAILURE;
3031 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
3034 nsIContent*
3035 nsFrameSelection::IsInSameTable(nsIContent *aContent1,
3036 nsIContent *aContent2) const
3038 if (!aContent1 || !aContent2) return PR_FALSE;
3040 nsIContent* tableNode1 = GetParentTable(aContent1);
3041 nsIContent* tableNode2 = GetParentTable(aContent2);
3043 // Must be in the same table. Note that we want to return false for
3044 // the test if both tables are null.
3045 return (tableNode1 == tableNode2) ? tableNode1 : nsnull;
3048 nsIContent*
3049 nsFrameSelection::GetParentTable(nsIContent *aCell) const
3051 if (!aCell) {
3052 return nsnull;
3055 for (nsIContent* parent = aCell->GetParent(); parent;
3056 parent = parent->GetParent()) {
3057 if (parent->Tag() == nsGkAtoms::table &&
3058 parent->IsHTML()) {
3059 return parent;
3063 return nsnull;
3066 nsresult
3067 nsFrameSelection::SelectCellElement(nsIContent *aCellElement)
3069 nsIContent *parent = aCellElement->GetParent();
3071 // Get child offset
3072 PRInt32 offset = parent->IndexOf(aCellElement);
3074 return CreateAndAddRange(parent, offset);
3077 nsresult
3078 nsTypedSelection::getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol)
3080 if (!aRange || !aSelectionType || !aRow || !aCol)
3081 return NS_ERROR_NULL_POINTER;
3083 *aSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
3084 *aRow = 0;
3085 *aCol = 0;
3087 // Must have access to frame selection to get cell info
3088 if (!mFrameSelection) return NS_OK;
3090 nsresult result = GetTableSelectionType(aRange, aSelectionType);
3091 if (NS_FAILED(result)) return result;
3093 // Don't fail if range does not point to a single table cell,
3094 // let aSelectionType tell user if we don't have a cell
3095 if (*aSelectionType != nsISelectionPrivate::TABLESELECTION_CELL)
3096 return NS_OK;
3098 // Get the child content (the cell) pointed to by starting node of range
3099 // We do minimal checking since GetTableSelectionType assures
3100 // us that this really is a table cell
3101 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
3102 if (!content)
3103 return NS_ERROR_FAILURE;
3105 nsIContent *child = content->GetChildAt(aRange->StartOffset());
3106 if (!child)
3107 return NS_ERROR_FAILURE;
3109 //Note: This is a non-ref-counted pointer to the frame
3110 nsITableCellLayout *cellLayout = mFrameSelection->GetCellLayout(child);
3111 if (NS_FAILED(result))
3112 return result;
3113 if (!cellLayout)
3114 return NS_ERROR_FAILURE;
3116 return cellLayout->GetCellIndexes(*aRow, *aCol);
3119 nsresult
3120 nsTypedSelection::addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange,
3121 PRInt32 *aOutIndex)
3123 if (!aDidAddRange || !aOutIndex)
3124 return NS_ERROR_NULL_POINTER;
3126 *aDidAddRange = PR_FALSE;
3127 *aOutIndex = -1;
3129 if (!mFrameSelection)
3130 return NS_OK;
3132 if (!aRange)
3133 return NS_ERROR_NULL_POINTER;
3135 nsresult result;
3137 // Get if we are adding a cell selection and the row, col of cell if we are
3138 PRInt32 newRow, newCol, tableMode;
3139 result = getTableCellLocationFromRange(aRange, &tableMode, &newRow, &newCol);
3140 if (NS_FAILED(result)) return result;
3142 // If not adding a cell range, we are done here
3143 if (tableMode != nsISelectionPrivate::TABLESELECTION_CELL)
3145 mFrameSelection->mSelectingTableCellMode = tableMode;
3146 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed
3147 return NS_OK;
3150 // Set frame selection mode only if not already set to a table mode
3151 // so we don't lose the select row and column flags (not detected by getTableCellLocation)
3152 if (mFrameSelection->mSelectingTableCellMode == TABLESELECTION_NONE)
3153 mFrameSelection->mSelectingTableCellMode = tableMode;
3155 *aDidAddRange = PR_TRUE;
3156 return AddItem(aRange, aOutIndex);
3159 //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS
3160 NS_IMETHODIMP
3161 nsTypedSelection::GetTableSelectionType(nsIDOMRange* aRange,
3162 PRInt32* aTableSelectionType)
3164 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
3165 return GetTableSelectionType(range, aTableSelectionType);
3168 nsresult
3169 nsTypedSelection::GetTableSelectionType(nsIRange* aRange,
3170 PRInt32* aTableSelectionType)
3172 if (!aRange || !aTableSelectionType)
3173 return NS_ERROR_NULL_POINTER;
3175 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
3177 // Must have access to frame selection to get cell info
3178 if(!mFrameSelection) return NS_OK;
3180 nsINode* startNode = aRange->GetStartParent();
3181 if (!startNode) return NS_ERROR_FAILURE;
3183 nsINode* endNode = aRange->GetEndParent();
3184 if (!endNode) return NS_ERROR_FAILURE;
3186 // Not a single selected node
3187 if (startNode != endNode) return NS_OK;
3189 PRInt32 startOffset = aRange->StartOffset();
3190 PRInt32 endOffset = aRange->EndOffset();
3192 // Not a single selected node
3193 if ((endOffset - startOffset) != 1)
3194 return NS_OK;
3196 nsIContent* startContent = static_cast<nsIContent*>(startNode);
3197 if (!(startNode->IsElement() && startContent->IsHTML())) {
3198 // Implies a check for being an element; if we ever make this work
3199 // for non-HTML, need to keep checking for elements.
3200 return NS_OK;
3203 nsIAtom *tag = startContent->Tag();
3205 if (tag == nsGkAtoms::tr)
3207 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
3209 else //check to see if we are selecting a table or row (column and all cells not done yet)
3211 nsIContent *child = startNode->GetChildAt(startOffset);
3212 if (!child)
3213 return NS_ERROR_FAILURE;
3215 tag = child->Tag();
3217 if (tag == nsGkAtoms::table)
3218 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE;
3219 else if (tag == nsGkAtoms::tr)
3220 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
3223 return NS_OK;
3226 nsresult
3227 nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, PRInt32 aOffset)
3229 if (!aParentNode) return NS_ERROR_NULL_POINTER;
3231 nsCOMPtr<nsIRange> range = new nsRange();
3232 if (!range) return NS_ERROR_OUT_OF_MEMORY;
3234 // Set range around child at given offset
3235 nsresult result = range->SetStart(aParentNode, aOffset);
3236 if (NS_FAILED(result)) return result;
3237 result = range->SetEnd(aParentNode, aOffset+1);
3238 if (NS_FAILED(result)) return result;
3240 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3241 if (!mDomSelections[index])
3242 return NS_ERROR_NULL_POINTER;
3244 return mDomSelections[index]->AddRange(range);
3247 // End of Table Selection
3249 void
3250 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
3252 if (mAncestorLimiter != aLimiter) {
3253 mAncestorLimiter = aLimiter;
3254 PRInt8 index =
3255 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3256 if (!mDomSelections[index])
3257 return;
3259 if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) {
3260 ClearNormalSelection();
3261 if (mAncestorLimiter) {
3262 PostReason(nsISelectionListener::NO_REASON);
3263 TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, PR_FALSE, PR_FALSE);
3269 //END nsFrameSelection methods
3272 //BEGIN nsISelection interface implementations
3276 nsresult
3277 nsFrameSelection::DeleteFromDocument()
3279 nsresult res;
3281 // If we're already collapsed, then set ourselves to include the
3282 // last item BEFORE the current range, rather than the range itself,
3283 // before we do the delete.
3284 PRBool isCollapsed;
3285 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3286 if (!mDomSelections[index])
3287 return NS_ERROR_NULL_POINTER;
3289 mDomSelections[index]->GetIsCollapsed( &isCollapsed);
3290 if (isCollapsed)
3292 // If the offset is positive, then it's easy:
3293 if (mDomSelections[index]->GetFocusOffset() > 0)
3295 mDomSelections[index]->Extend(mDomSelections[index]->GetFocusNode(), mDomSelections[index]->GetFocusOffset() - 1);
3297 else
3299 // Otherwise it's harder, have to find the previous node
3300 printf("Sorry, don't know how to delete across frame boundaries yet\n");
3301 return NS_ERROR_NOT_IMPLEMENTED;
3305 // Get an iterator
3306 nsSelectionIterator iter(mDomSelections[index]);
3307 res = iter.First();
3308 if (NS_FAILED(res))
3309 return res;
3311 while (iter.IsDone())
3313 nsCOMPtr<nsIRange> range = iter.CurrentItem();
3314 res = range->DeleteContents();
3315 if (NS_FAILED(res))
3316 return res;
3317 iter.Next();
3320 // Collapse to the new location.
3321 // If we deleted one character, then we move back one element.
3322 // FIXME We don't know how to do this past frame boundaries yet.
3323 if (isCollapsed)
3324 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->GetAnchorOffset()-1);
3325 else if (mDomSelections[index]->GetAnchorOffset() > 0)
3326 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->GetAnchorOffset());
3327 #ifdef DEBUG
3328 else
3329 printf("Don't know how to set selection back past frame boundary\n");
3330 #endif
3332 return NS_OK;
3335 void
3336 nsFrameSelection::SetDelayedCaretData(nsMouseEvent *aMouseEvent)
3338 if (aMouseEvent)
3340 mDelayedMouseEventValid = PR_TRUE;
3341 mDelayedMouseEvent = *aMouseEvent;
3343 // Don't cache the widget. We don't need it and it could go away.
3344 mDelayedMouseEvent.widget = nsnull;
3346 else
3347 mDelayedMouseEventValid = PR_FALSE;
3350 nsMouseEvent*
3351 nsFrameSelection::GetDelayedCaretData()
3353 if (mDelayedMouseEventValid)
3354 return &mDelayedMouseEvent;
3356 return nsnull;
3359 //END nsISelection interface implementations
3361 #if 0
3362 #pragma mark -
3363 #endif
3365 // nsTypedSelection implementation
3367 // note: this can return a nil anchor node
3369 nsTypedSelection::nsTypedSelection()
3370 : mCachedOffsetForFrame(nsnull)
3371 , mDirection(eDirNext)
3372 , mType(nsISelectionController::SELECTION_NORMAL)
3376 nsTypedSelection::nsTypedSelection(nsFrameSelection *aList)
3377 : mFrameSelection(aList)
3378 , mCachedOffsetForFrame(nsnull)
3379 , mDirection(eDirNext)
3380 , mType(nsISelectionController::SELECTION_NORMAL)
3384 nsTypedSelection::~nsTypedSelection()
3386 setAnchorFocusRange(-1);
3388 if (mAutoScrollTimer) {
3389 mAutoScrollTimer->Stop();
3390 mAutoScrollTimer = nsnull;
3393 mScrollEvent.Revoke();
3395 if (mCachedOffsetForFrame) {
3396 delete mCachedOffsetForFrame;
3397 mCachedOffsetForFrame = nsnull;
3402 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTypedSelection)
3403 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTypedSelection)
3404 // Unlink the selection listeners *before* we do RemoveAllRanges since
3405 // we don't want to notify the listeners during JS GC (they could be
3406 // in JS!).
3407 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mSelectionListeners)
3408 tmp->RemoveAllRanges();
3409 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFrameSelection)
3410 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3411 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTypedSelection)
3413 PRUint32 i, count = tmp->mRanges.Length();
3414 for (i = 0; i < count; ++i) {
3415 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRanges[i].mRange)
3418 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAnchorFocusRange)
3419 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFrameSelection)
3420 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mSelectionListeners)
3421 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3423 DOMCI_DATA(Selection, nsTypedSelection)
3425 // QueryInterface implementation for nsTypedSelection
3426 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypedSelection)
3427 NS_INTERFACE_MAP_ENTRY(nsISelection)
3428 NS_INTERFACE_MAP_ENTRY(nsISelection2)
3429 NS_INTERFACE_MAP_ENTRY(nsISelection3)
3430 NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate)
3431 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
3432 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection)
3433 NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Selection)
3434 NS_INTERFACE_MAP_END
3436 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypedSelection)
3437 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypedSelection)
3439 NS_IMETHODIMP
3440 nsTypedSelection::SetPresShell(nsIPresShell *aPresShell)
3442 mPresShellWeak = do_GetWeakReference(aPresShell);
3443 return NS_OK;
3448 NS_IMETHODIMP
3449 nsTypedSelection::GetAnchorNode(nsIDOMNode** aAnchorNode)
3451 nsINode* anchorNode = GetAnchorNode();
3452 if (anchorNode) {
3453 return CallQueryInterface(anchorNode, aAnchorNode);
3456 *aAnchorNode = nsnull;
3457 return NS_OK;
3460 nsINode*
3461 nsTypedSelection::GetAnchorNode()
3463 if (!mAnchorFocusRange)
3464 return nsnull;
3466 if (GetDirection() == eDirNext) {
3467 return mAnchorFocusRange->GetStartParent();
3470 return mAnchorFocusRange->GetEndParent();
3473 NS_IMETHODIMP
3474 nsTypedSelection::GetAnchorOffset(PRInt32* aAnchorOffset)
3476 *aAnchorOffset = GetAnchorOffset();
3477 return NS_OK;
3480 // note: this can return a nil focus node
3481 NS_IMETHODIMP
3482 nsTypedSelection::GetFocusNode(nsIDOMNode** aFocusNode)
3484 nsINode* focusNode = GetFocusNode();
3485 if (focusNode) {
3486 return CallQueryInterface(focusNode, aFocusNode);
3489 *aFocusNode = nsnull;
3490 return NS_OK;
3493 nsINode*
3494 nsTypedSelection::GetFocusNode()
3496 if (!mAnchorFocusRange)
3497 return nsnull;
3499 if (GetDirection() == eDirNext){
3500 return mAnchorFocusRange->GetEndParent();
3503 return mAnchorFocusRange->GetStartParent();
3506 NS_IMETHODIMP nsTypedSelection::GetFocusOffset(PRInt32* aFocusOffset)
3508 *aFocusOffset = GetFocusOffset();
3509 return NS_OK;
3512 void nsTypedSelection::setAnchorFocusRange(PRInt32 indx)
3514 if (indx >= (PRInt32)mRanges.Length())
3515 return;
3516 if (indx < 0) //release all
3518 mAnchorFocusRange = nsnull;
3520 else{
3521 mAnchorFocusRange = mRanges[indx].mRange;
3525 PRInt32
3526 nsTypedSelection::GetAnchorOffset()
3528 if (!mAnchorFocusRange)
3529 return 0;
3531 if (GetDirection() == eDirNext){
3532 return mAnchorFocusRange->StartOffset();
3535 return mAnchorFocusRange->EndOffset();
3538 PRInt32
3539 nsTypedSelection::GetFocusOffset()
3541 if (!mAnchorFocusRange)
3542 return 0;
3544 if (GetDirection() == eDirNext){
3545 return mAnchorFocusRange->EndOffset();
3548 return mAnchorFocusRange->StartOffset();
3551 static nsresult
3552 CompareToRangeStart(nsINode* aCompareNode, PRInt32 aCompareOffset,
3553 nsIRange* aRange, PRInt32* aCmp)
3555 nsINode* start = aRange->GetStartParent();
3556 NS_ENSURE_STATE(aCompareNode && start);
3557 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3558 start, aRange->StartOffset());
3559 return NS_OK;
3562 static nsresult
3563 CompareToRangeEnd(nsINode* aCompareNode, PRInt32 aCompareOffset,
3564 nsIRange* aRange, PRInt32* aCmp)
3566 nsINode* end = aRange->GetEndParent();
3567 NS_ENSURE_STATE(aCompareNode && end);
3568 *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3569 end, aRange->EndOffset());
3570 return NS_OK;
3573 // nsTypedSelection::FindInsertionPoint
3575 // Binary searches the given sorted array of ranges for the insertion point
3576 // for the given node/offset. The given comparator is used, and the index
3577 // where the point should appear in the array is placed in *aInsertionPoint.
3579 // If there is an item in the array equal to the input point, we will return
3580 // the index of this item.
3582 nsresult
3583 nsTypedSelection::FindInsertionPoint(
3584 nsTArray<RangeData>* aElementArray,
3585 nsINode* aPointNode, PRInt32 aPointOffset,
3586 nsresult (*aComparator)(nsINode*,PRInt32,nsIRange*,PRInt32*),
3587 PRInt32* aPoint)
3589 *aPoint = 0;
3590 PRInt32 beginSearch = 0;
3591 PRInt32 endSearch = aElementArray->Length(); // one beyond what to check
3592 while (endSearch - beginSearch > 0) {
3593 PRInt32 center = (endSearch - beginSearch) / 2 + beginSearch;
3595 nsIRange* range = (*aElementArray)[center].mRange;
3597 PRInt32 cmp;
3598 nsresult rv = aComparator(aPointNode, aPointOffset, range, &cmp);
3599 NS_ENSURE_SUCCESS(rv, rv);
3601 if (cmp < 0) { // point < cur
3602 endSearch = center;
3603 } else if (cmp > 0) { // point > cur
3604 beginSearch = center + 1;
3605 } else { // found match, done
3606 beginSearch = center;
3607 break;
3610 *aPoint = beginSearch;
3611 return NS_OK;
3614 // nsTypedSelection::SubtractRange
3616 // A helper function that subtracts aSubtract from aRange, and adds
3617 // 1 or 2 RangeData objects representing the remaining non-overlapping
3618 // difference to aOutput. It is assumed that the caller has checked that
3619 // aRange and aSubtract do indeed overlap
3621 nsresult
3622 nsTypedSelection::SubtractRange(RangeData* aRange, nsIRange* aSubtract,
3623 nsTArray<RangeData>* aOutput)
3625 nsIRange* range = aRange->mRange;
3627 // First we want to compare to the range start
3628 PRInt32 cmp;
3629 nsresult rv = CompareToRangeStart(range->GetStartParent(),
3630 range->StartOffset(),
3631 aSubtract, &cmp);
3632 NS_ENSURE_SUCCESS(rv, rv);
3634 // Also, make a comparison to the range end
3635 PRInt32 cmp2;
3636 rv = CompareToRangeEnd(range->GetEndParent(),
3637 range->EndOffset(),
3638 aSubtract, &cmp2);
3639 NS_ENSURE_SUCCESS(rv, rv);
3641 // If the existing range left overlaps the new range (aSubtract) then
3642 // cmp < 0, and cmp2 < 0
3643 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
3644 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
3646 if (cmp2 > 0) {
3647 // We need to add a new RangeData to the output, running from
3648 // the end of aSubtract to the end of range
3649 nsIRange* postOverlap = new nsRange();
3650 if (!postOverlap)
3651 return NS_ERROR_OUT_OF_MEMORY;
3653 rv =
3654 postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
3655 NS_ENSURE_SUCCESS(rv, rv);
3656 rv =
3657 postOverlap->SetEnd(range->GetEndParent(), range->EndOffset());
3658 NS_ENSURE_SUCCESS(rv, rv);
3659 if (!postOverlap->Collapsed()) {
3660 if (!aOutput->InsertElementAt(0, RangeData(postOverlap)))
3661 return NS_ERROR_OUT_OF_MEMORY;
3662 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3666 if (cmp < 0) {
3667 // We need to add a new RangeData to the output, running from
3668 // the start of the range to the start of aSubtract
3669 nsIRange* preOverlap = new nsRange();
3670 if (!preOverlap)
3671 return NS_ERROR_OUT_OF_MEMORY;
3673 nsresult rv =
3674 preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
3675 NS_ENSURE_SUCCESS(rv, rv);
3676 rv =
3677 preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset());
3678 NS_ENSURE_SUCCESS(rv, rv);
3680 if (!preOverlap->Collapsed()) {
3681 if (!aOutput->InsertElementAt(0, RangeData(preOverlap)))
3682 return NS_ERROR_OUT_OF_MEMORY;
3683 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3687 return NS_OK;
3690 nsresult
3691 nsTypedSelection::AddItem(nsIRange *aItem, PRInt32 *aOutIndex)
3693 if (!aItem)
3694 return NS_ERROR_NULL_POINTER;
3695 if (!aItem->IsPositioned())
3696 return NS_ERROR_UNEXPECTED;
3697 if (aOutIndex)
3698 *aOutIndex = -1;
3700 // a common case is that we have no ranges yet
3701 if (mRanges.Length() == 0) {
3702 if (!mRanges.AppendElement(RangeData(aItem)))
3703 return NS_ERROR_OUT_OF_MEMORY;
3704 if (aOutIndex)
3705 *aOutIndex = 0;
3706 return NS_OK;
3709 PRInt32 startIndex, endIndex;
3710 GetIndicesForInterval(aItem->GetStartParent(), aItem->StartOffset(),
3711 aItem->GetEndParent(), aItem->EndOffset(),
3712 PR_FALSE, &startIndex, &endIndex);
3714 if (endIndex == -1) {
3715 // All ranges start after the given range. We can insert our range at
3716 // position 0, knowing there are no overlaps (handled below)
3717 startIndex = 0;
3718 endIndex = 0;
3719 } else if (startIndex == -1) {
3720 // All ranges end before the given range. We can insert our range at
3721 // the end of the array, knowing there are no overlaps (handled below)
3722 startIndex = mRanges.Length();
3723 endIndex = startIndex;
3726 // If the range is already contained in mRanges, silently succeed
3727 PRBool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(),
3728 aItem->StartOffset(),
3729 aItem->GetEndParent(),
3730 aItem->EndOffset(), startIndex);
3731 if (sameRange) {
3732 if (aOutIndex)
3733 *aOutIndex = startIndex;
3734 return NS_OK;
3737 if (startIndex == endIndex) {
3738 // The new range doesn't overlap any existing ranges
3739 if (!mRanges.InsertElementAt(startIndex, RangeData(aItem)))
3740 return NS_ERROR_OUT_OF_MEMORY;
3741 if (aOutIndex)
3742 *aOutIndex = startIndex;
3743 return NS_OK;
3746 // We now know that at least 1 existing range overlaps with the range that
3747 // we are trying to add. In fact, the only ranges of interest are those at
3748 // the two end points, startIndex and endIndex - 1 (which may point to the
3749 // same range) as these may partially overlap the new range. Any ranges
3750 // between these indices are fully overlapped by the new range, and so can be
3751 // removed
3752 nsTArray<RangeData> overlaps;
3753 if (!overlaps.InsertElementAt(0, mRanges[startIndex]))
3754 return NS_ERROR_OUT_OF_MEMORY;
3756 if (endIndex - 1 != startIndex) {
3757 if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1]))
3758 return NS_ERROR_OUT_OF_MEMORY;
3761 // Remove all the overlapping ranges
3762 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
3764 nsTArray<RangeData> temp;
3765 for (PRInt32 i = overlaps.Length() - 1; i >= 0; i--) {
3766 nsresult rv = SubtractRange(&overlaps[i], aItem, &temp);
3767 NS_ENSURE_SUCCESS(rv, rv);
3770 // Insert the new element into our "leftovers" array
3771 PRInt32 insertionPoint;
3772 nsresult rv = FindInsertionPoint(&temp, aItem->GetStartParent(),
3773 aItem->StartOffset(),
3774 CompareToRangeStart,
3775 &insertionPoint);
3776 NS_ENSURE_SUCCESS(rv, rv);
3778 if (!temp.InsertElementAt(insertionPoint, RangeData(aItem)))
3779 return NS_ERROR_OUT_OF_MEMORY;
3781 // Merge the leftovers back in to mRanges
3782 if (!mRanges.InsertElementsAt(startIndex, temp))
3783 return NS_ERROR_OUT_OF_MEMORY;
3785 *aOutIndex = startIndex + insertionPoint;
3786 return NS_OK;
3789 nsresult
3790 nsTypedSelection::RemoveItem(nsIRange *aItem)
3792 if (!aItem)
3793 return NS_ERROR_NULL_POINTER;
3795 // Find the range's index & remove it. We could use FindInsertionPoint to
3796 // get O(log n) time, but that requires many expensive DOM comparisons.
3797 // For even several thousand items, this is probably faster because the
3798 // comparisons are so fast.
3799 PRInt32 idx = -1;
3800 PRUint32 i;
3801 for (i = 0; i < mRanges.Length(); i ++) {
3802 if (mRanges[i].mRange == aItem) {
3803 idx = (PRInt32)i;
3804 break;
3807 if (idx < 0)
3808 return NS_ERROR_INVALID_ARG;
3810 mRanges.RemoveElementAt(idx);
3811 return NS_OK;
3814 nsresult
3815 nsTypedSelection::RemoveCollapsedRanges()
3817 PRUint32 i = 0;
3818 while (i < mRanges.Length()) {
3819 if (mRanges[i].mRange->Collapsed()) {
3820 nsresult rv = RemoveItem(mRanges[i].mRange);
3821 NS_ENSURE_SUCCESS(rv, rv);
3822 } else {
3823 ++i;
3826 return NS_OK;
3829 nsresult
3830 nsTypedSelection::Clear(nsPresContext* aPresContext)
3832 setAnchorFocusRange(-1);
3834 for (PRInt32 i = 0; i < (PRInt32)mRanges.Length(); i ++) {
3835 selectFrames(aPresContext, mRanges[i].mRange, 0);
3837 mRanges.Clear();
3839 // Reset direction so for more dependable table selection range handling
3840 SetDirection(eDirNext);
3842 // If this was an ATTENTION selection, change it back to normal now
3843 if (mFrameSelection &&
3844 mFrameSelection->GetDisplaySelection() ==
3845 nsISelectionController::SELECTION_ATTENTION) {
3846 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
3849 return NS_OK;
3852 NS_IMETHODIMP
3853 nsTypedSelection::GetType(PRInt16 *aType)
3855 NS_ENSURE_ARG_POINTER(aType);
3856 *aType = mType;
3858 return NS_OK;
3861 // RangeMatches*Point
3863 // Compares the range beginning or ending point, and returns true if it
3864 // exactly matches the given DOM point.
3866 static inline PRBool
3867 RangeMatchesBeginPoint(nsIRange* aRange, nsINode* aNode, PRInt32 aOffset)
3869 return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset;
3872 static inline PRBool
3873 RangeMatchesEndPoint(nsIRange* aRange, nsINode* aNode, PRInt32 aOffset)
3875 return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset;
3878 // nsTypedSelection::EqualsRangeAtPoint
3880 // Utility method for checking equivalence of two ranges.
3882 PRBool
3883 nsTypedSelection::EqualsRangeAtPoint(
3884 nsINode* aBeginNode, PRInt32 aBeginOffset,
3885 nsINode* aEndNode, PRInt32 aEndOffset,
3886 PRInt32 aRangeIndex)
3888 if (aRangeIndex >=0 && aRangeIndex < (PRInt32) mRanges.Length()) {
3889 nsIRange* range = mRanges[aRangeIndex].mRange;
3890 if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset) &&
3891 RangeMatchesEndPoint(range, aEndNode, aEndOffset))
3892 return PR_TRUE;
3894 return PR_FALSE;
3897 // nsTypedSelection::GetRangesForInterval
3899 // XPCOM wrapper for the COMArray version
3901 NS_IMETHODIMP
3902 nsTypedSelection::GetRangesForInterval(nsIDOMNode* aBeginNode, PRInt32 aBeginOffset,
3903 nsIDOMNode* aEndNode, PRInt32 aEndOffset,
3904 PRBool aAllowAdjacent,
3905 PRUint32 *aResultCount,
3906 nsIDOMRange ***aResults)
3908 if (!aBeginNode || ! aEndNode || ! aResultCount || ! aResults)
3909 return NS_ERROR_NULL_POINTER;
3911 *aResultCount = 0;
3912 *aResults = nsnull;
3914 nsCOMArray<nsIDOMRange> results;
3915 nsresult rv = GetRangesForIntervalCOMArray(aBeginNode, aBeginOffset,
3916 aEndNode, aEndOffset,
3917 aAllowAdjacent,
3918 &results);
3919 NS_ENSURE_SUCCESS(rv, rv);
3920 if (results.Count() == 0)
3921 return NS_OK;
3923 *aResults = static_cast<nsIDOMRange**>
3924 (nsMemory::Alloc(sizeof(nsIDOMRange*) * results.Count()));
3925 NS_ENSURE_TRUE(*aResults, NS_ERROR_OUT_OF_MEMORY);
3927 *aResultCount = results.Count();
3928 for (PRInt32 i = 0; i < results.Count(); i ++)
3929 NS_ADDREF((*aResults)[i] = results[i]);
3930 return NS_OK;
3933 // nsTypedSelection::GetRangesForIntervalCOMArray
3935 // Fills a COM array with the ranges overlapping the range specified by
3936 // the given endpoints. Ranges in the selection exactly adjacent to the
3937 // input range are not returned unless aAllowAdjacent is set.
3939 NS_IMETHODIMP
3940 nsTypedSelection::GetRangesForIntervalCOMArray(nsIDOMNode* aBeginNode, PRInt32 aBeginOffset,
3941 nsIDOMNode* aEndNode, PRInt32 aEndOffset,
3942 PRBool aAllowAdjacent,
3943 nsCOMArray<nsIDOMRange>* aRanges)
3945 nsCOMPtr<nsINode> begin = do_QueryInterface(aBeginNode);
3946 nsCOMPtr<nsINode> end = do_QueryInterface(aEndNode);
3947 nsCOMArray<nsIRange> ranges;
3948 nsresult rv = GetRangesForIntervalCOMArray(begin, aBeginOffset,
3949 end, aEndOffset,
3950 aAllowAdjacent, &ranges);
3951 NS_ENSURE_SUCCESS(rv, rv);
3952 for (PRInt32 i = 0; i < ranges.Count(); ++i) {
3953 nsCOMPtr<nsIDOMRange> r = do_QueryInterface(ranges[i]);
3954 if (!aRanges->AppendObject(r)) {
3955 return NS_ERROR_OUT_OF_MEMORY;
3959 return NS_OK;
3962 // nsTypedSelection::GetRangesForIntervalCOMArray
3964 // Fills a COM array with the ranges overlapping the range specified by
3965 // the given endpoints. Ranges in the selection exactly adjacent to the
3966 // input range are not returned unless aAllowAdjacent is set.
3968 // For example, if the following ranges were in the selection
3969 // (assume everything is within the same node)
3971 // Start Offset: 0 2 7 9
3972 // End Offset: 2 5 9 10
3974 // and passed aBeginOffset of 2 and aEndOffset of 9, then with
3975 // aAllowAdjacent set, all the ranges should be returned. If
3976 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
3977 // should be returned
3979 // Now that overlapping ranges are disallowed, there can be a maximum of
3980 // 2 adjacent ranges
3982 nsresult
3983 nsTypedSelection::GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
3984 nsINode* aEndNode, PRInt32 aEndOffset,
3985 PRBool aAllowAdjacent,
3986 nsCOMArray<nsIRange>* aRanges)
3988 aRanges->Clear();
3989 PRInt32 startIndex, endIndex;
3990 GetIndicesForInterval(aBeginNode, aBeginOffset, aEndNode, aEndOffset,
3991 aAllowAdjacent, &startIndex, &endIndex);
3992 if (startIndex == -1 || endIndex == -1)
3993 return NS_OK;
3995 for (PRInt32 i = startIndex; i < endIndex; i++) {
3996 if (!aRanges->AppendObject(mRanges[i].mRange))
3997 return NS_ERROR_OUT_OF_MEMORY;
4000 return NS_OK;
4003 // nsTypedSelection::GetIndicesForInterval
4005 // Works on the same principle as GetRangesForIntervalCOMArray above, however
4006 // instead this returns the indices into mRanges between which the
4007 // overlapping ranges lie.
4009 void
4010 nsTypedSelection::GetIndicesForInterval(nsINode* aBeginNode,
4011 PRInt32 aBeginOffset,
4012 nsINode* aEndNode, PRInt32 aEndOffset,
4013 PRBool aAllowAdjacent,
4014 PRInt32 *aStartIndex,
4015 PRInt32 *aEndIndex)
4017 if (aStartIndex)
4018 *aStartIndex = -1;
4019 if (aEndIndex)
4020 *aEndIndex = -1;
4022 if (mRanges.Length() == 0)
4023 return;
4025 PRBool intervalIsCollapsed = aBeginNode == aEndNode &&
4026 aBeginOffset == aEndOffset;
4028 // Ranges that end before the given interval and begin after the given
4029 // interval can be discarded
4030 PRInt32 endsBeforeIndex;
4031 if (NS_FAILED(FindInsertionPoint(&mRanges, aEndNode, aEndOffset,
4032 &CompareToRangeStart,
4033 &endsBeforeIndex))) {
4034 return;
4037 if (endsBeforeIndex == 0) {
4038 nsIRange* endRange = mRanges[endsBeforeIndex].mRange;
4040 // If the interval is strictly before the range at index 0, we can optimize
4041 // by returning now - all ranges start after the given interval
4042 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset))
4043 return;
4045 // We now know that the start point of mRanges[0].mRange equals the end of
4046 // the interval. Thus, when aAllowadjacent is true, the caller is always
4047 // interested in this range. However, when excluding adjacencies, we must
4048 // remember to include the range when both it and the given interval are
4049 // collapsed to the same point
4050 if (!aAllowAdjacent && !(endRange->Collapsed() && intervalIsCollapsed))
4051 return;
4053 *aEndIndex = endsBeforeIndex;
4055 PRInt32 beginsAfterIndex;
4056 if (NS_FAILED(FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset,
4057 &CompareToRangeEnd,
4058 &beginsAfterIndex))) {
4059 return;
4061 if (beginsAfterIndex == (PRInt32) mRanges.Length())
4062 return; // optimization: all ranges are strictly before us
4064 if (aAllowAdjacent) {
4065 // At this point, one of the following holds:
4066 // endsBeforeIndex == mRanges.Length(),
4067 // endsBeforeIndex points to a range whose start point does not equal the
4068 // given interval's start point
4069 // endsBeforeIndex points to a range whose start point equals the given
4070 // interval's start point
4071 // In the final case, there can be two such ranges, a collapsed range, and
4072 // an adjacent range (they will appear in mRanges in that order). For this
4073 // final case, we need to increment endsBeforeIndex, until one of the
4074 // first two possibilites hold
4075 while (endsBeforeIndex < (PRInt32) mRanges.Length()) {
4076 nsIRange* endRange = mRanges[endsBeforeIndex].mRange;
4077 if (!RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset))
4078 break;
4079 endsBeforeIndex++;
4082 // Likewise, one of the following holds:
4083 // beginsAfterIndex == 0,
4084 // beginsAfterIndex points to a range whose end point does not equal
4085 // the given interval's end point
4086 // beginsOnOrAfter points to a range whose end point equals the given
4087 // interval's end point
4088 // In the final case, there can be two such ranges, an adjacent range, and
4089 // a collapsed range (they will appear in mRanges in that order). For this
4090 // final case, we only need to take action if both those ranges exist, and
4091 // we are pointing to the collapsed range - we need to point to the
4092 // adjacent range
4093 nsIRange* beginRange = mRanges[beginsAfterIndex].mRange;
4094 if (beginsAfterIndex > 0 && beginRange->Collapsed() &&
4095 RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset)) {
4096 beginRange = mRanges[beginsAfterIndex - 1].mRange;
4097 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset))
4098 beginsAfterIndex--;
4100 } else {
4101 // See above for the possibilities at this point. The only case where we
4102 // need to take action is when the range at beginsAfterIndex ends on
4103 // the given interval's start point, but that range isn't collapsed (a
4104 // collapsed range should be included in the returned results).
4105 nsIRange* beginRange = mRanges[beginsAfterIndex].mRange;
4106 if (RangeMatchesEndPoint(beginRange, aBeginNode, aBeginOffset) &&
4107 !beginRange->Collapsed())
4108 beginsAfterIndex++;
4110 // Again, see above for the meaning of endsBeforeIndex at this point.
4111 // In particular, endsBeforeIndex may point to a collaped range which
4112 // represents the point at the end of the interval - this range should be
4113 // included
4114 if (endsBeforeIndex < (PRInt32) mRanges.Length()) {
4115 nsIRange* endRange = mRanges[endsBeforeIndex].mRange;
4116 if (RangeMatchesBeginPoint(endRange, aEndNode, aEndOffset) &&
4117 endRange->Collapsed())
4118 endsBeforeIndex++;
4122 *aStartIndex = beginsAfterIndex;
4123 *aEndIndex = endsBeforeIndex;
4124 return;
4127 //utility method to get the primary frame of node or use the offset to get frame of child node
4129 #if 0
4130 NS_IMETHODIMP
4131 nsTypedSelection::GetPrimaryFrameForRangeEndpoint(nsIDOMNode *aNode, PRInt32 aOffset, PRBool aIsEndNode, nsIFrame **aReturnFrame)
4133 if (!aNode || !aReturnFrame || !mFrameSelection)
4134 return NS_ERROR_NULL_POINTER;
4136 if (aOffset < 0)
4137 return NS_ERROR_FAILURE;
4139 *aReturnFrame = 0;
4141 nsresult result = NS_OK;
4143 nsCOMPtr<nsIDOMNode> node = aNode;
4145 if (!node)
4146 return NS_ERROR_NULL_POINTER;
4148 nsCOMPtr<nsIContent> content = do_QueryInterface(node, &result);
4150 if (NS_FAILED(result))
4151 return result;
4153 if (!content)
4154 return NS_ERROR_NULL_POINTER;
4156 if (content->IsElement())
4158 if (aIsEndNode)
4159 aOffset--;
4161 if (aOffset >= 0)
4163 nsIContent *child = content->GetChildAt(aOffset);
4164 if (!child) //out of bounds?
4165 return NS_ERROR_FAILURE;
4167 content = child; // releases the focusnode
4170 *aReturnFrame = content->GetPrimaryFrame();
4171 return NS_OK;
4173 #endif
4176 NS_IMETHODIMP
4177 nsTypedSelection::GetPrimaryFrameForAnchorNode(nsIFrame **aReturnFrame)
4179 if (!aReturnFrame)
4180 return NS_ERROR_NULL_POINTER;
4182 PRInt32 frameOffset = 0;
4183 *aReturnFrame = 0;
4184 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
4185 if (content && mFrameSelection)
4187 *aReturnFrame = mFrameSelection->
4188 GetFrameForNodeOffset(content, GetAnchorOffset(),
4189 mFrameSelection->GetHint(), &frameOffset);
4190 if (*aReturnFrame)
4191 return NS_OK;
4193 return NS_ERROR_FAILURE;
4196 NS_IMETHODIMP
4197 nsTypedSelection::GetPrimaryFrameForFocusNode(nsIFrame **aReturnFrame, PRInt32 *aOffsetUsed,
4198 PRBool aVisual)
4200 if (!aReturnFrame)
4201 return NS_ERROR_NULL_POINTER;
4203 nsCOMPtr<nsIContent> content = do_QueryInterface(GetFocusNode());
4204 if (!content || !mFrameSelection)
4205 return NS_ERROR_FAILURE;
4207 nsIPresShell *presShell = mFrameSelection->GetShell();
4209 PRInt32 frameOffset = 0;
4210 *aReturnFrame = 0;
4211 if (!aOffsetUsed)
4212 aOffsetUsed = &frameOffset;
4214 nsFrameSelection::HINT hint = mFrameSelection->GetHint();
4216 if (aVisual) {
4217 nsRefPtr<nsCaret> caret = presShell->GetCaret();
4218 if (!caret)
4219 return NS_ERROR_FAILURE;
4221 PRUint8 caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
4223 return caret->GetCaretFrameForNodeOffset(content, GetFocusOffset(),
4224 hint, caretBidiLevel, aReturnFrame, aOffsetUsed);
4227 *aReturnFrame = mFrameSelection->
4228 GetFrameForNodeOffset(content, GetFocusOffset(),
4229 hint, aOffsetUsed);
4230 if (!*aReturnFrame)
4231 return NS_ERROR_FAILURE;
4233 return NS_OK;
4236 //select all content children of aContent
4237 nsresult
4238 nsTypedSelection::SelectAllFramesForContent(nsIContentIterator *aInnerIter,
4239 nsIContent *aContent,
4240 PRBool aSelected)
4242 if (!mFrameSelection)
4243 return NS_OK; // nothing to do
4244 nsIPresShell* shell = mFrameSelection->GetShell();
4245 if (!shell)
4246 return NS_OK;
4247 nsresult result;
4248 if (!aInnerIter)
4249 return NS_ERROR_NULL_POINTER;
4250 result = aInnerIter->Init(aContent);
4251 nsIFrame *frame;
4252 if (NS_SUCCEEDED(result))
4254 // First select frame of content passed in
4255 frame = aContent->GetPrimaryFrame();
4256 if (frame)
4258 frame->SetSelected(aSelected, mType);
4259 if (mFrameSelection->GetTableCellSelection())
4261 nsITableCellLayout *tcl = do_QueryFrame(frame);
4262 if (tcl)
4264 return NS_OK;
4268 // Now iterated through the child frames and set them
4269 while (!aInnerIter->IsDone())
4271 nsCOMPtr<nsIContent> innercontent =
4272 do_QueryInterface(aInnerIter->GetCurrentNode());
4274 frame = innercontent->GetPrimaryFrame();
4275 if (frame)
4277 frame->SetSelected(aSelected, mType);
4280 aInnerIter->Next();
4283 return NS_OK;
4286 return NS_ERROR_FAILURE;
4291 //the idea of this helper method is to select, deselect "top to bottom" traversing through the frames
4292 nsresult
4293 nsTypedSelection::selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PRBool aFlags)
4295 if (!mFrameSelection || !aPresContext)
4296 return NS_OK; // nothing to do
4297 nsIPresShell *presShell = aPresContext->GetPresShell();
4298 if (!presShell)
4299 return NS_OK;
4301 // Re-get shell because the flush might have destroyed it
4302 presShell = aPresContext->GetPresShell();
4303 if (!presShell)
4304 return NS_OK;
4306 nsCOMPtr<nsIDOMRange> domRange = do_QueryInterface(aRange);
4307 if (!domRange || !aPresContext)
4308 return NS_ERROR_NULL_POINTER;
4310 nsresult result;
4311 nsCOMPtr<nsIContentIterator> iter = do_CreateInstance(
4312 kCSubtreeIteratorCID,
4313 &result);
4314 if (NS_FAILED(result))
4315 return result;
4317 nsCOMPtr<nsIContentIterator> inneriter = do_CreateInstance(
4318 kCContentIteratorCID,
4319 &result);
4321 if ((NS_SUCCEEDED(result)) && iter && inneriter)
4323 result = iter->Init(aRange);
4325 // loop through the content iterator for each content node
4326 // for each text node, call SetSelected on it:
4327 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
4329 // we must call first one explicitly
4330 if (!content)
4331 return NS_ERROR_UNEXPECTED;
4333 if (content->IsNodeOfType(nsINode::eTEXT))
4335 nsIFrame* frame = content->GetPrimaryFrame();
4336 // The frame could be an SVG text frame, in which case we'll ignore
4337 // it.
4338 if (frame && frame->GetType() == nsGkAtoms::textFrame)
4340 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4341 PRUint32 startOffset = aRange->StartOffset();
4342 PRUint32 endOffset;
4343 if (aRange->GetEndParent() == content) {
4344 endOffset = aRange->EndOffset();
4345 } else {
4346 endOffset = content->GetText()->GetLength();
4348 textFrame->SetSelectedRange(startOffset, endOffset, aFlags, mType);
4352 iter->First();
4354 while (!iter->IsDone())
4356 content = do_QueryInterface(iter->GetCurrentNode());
4358 SelectAllFramesForContent(inneriter, content, aFlags);
4360 iter->Next();
4363 //we must now do the last one if it is not the same as the first
4364 if (aRange->GetEndParent() != aRange->GetStartParent())
4366 content = do_QueryInterface(aRange->GetEndParent(), &result);
4367 if (NS_FAILED(result) || !content)
4368 return result;
4370 if (content->IsNodeOfType(nsINode::eTEXT))
4372 nsIFrame* frame = content->GetPrimaryFrame();
4373 // The frame could be an SVG text frame, in which case we'll
4374 // ignore it.
4375 if (frame && frame->GetType() == nsGkAtoms::textFrame)
4377 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4378 textFrame->SetSelectedRange(0, aRange->EndOffset(), aFlags, mType);
4383 return result;
4386 // nsTypedSelection::LookUpSelection
4388 // This function is called when a node wants to know where the selection is
4389 // over itself.
4391 // Usually, this is called when we already know there is a selection over
4392 // the node in question, and we only need to find the boundaries of it on
4393 // that node. This is when slowCheck is false--a strict test is not needed.
4394 // Other times, the caller has no idea, and wants us to test everything,
4395 // so we are supposed to determine whether there is a selection over the
4396 // node at all.
4398 // A previous version of this code used this flag to do less work when
4399 // inclusion was already known (slowCheck=false). However, our tree
4400 // structure allows us to quickly determine ranges overlapping the node,
4401 // so we just ignore the slowCheck flag and do the full test every time.
4403 // PERFORMANCE: a common case is that we are doing a fast check with exactly
4404 // one range in the selection. In this case, this function is slower than
4405 // brute force because of the overhead of checking the tree. We can optimize
4406 // this case to make it faster by doing the same thing the previous version
4407 // of this function did in the case of 1 range. This would also mean that
4408 // the aSlowCheck flag would have meaning again.
4410 NS_IMETHODIMP
4411 nsTypedSelection::LookUpSelection(nsIContent *aContent, PRInt32 aContentOffset,
4412 PRInt32 aContentLength,
4413 SelectionDetails **aReturnDetails,
4414 SelectionType aType, PRBool aSlowCheck)
4416 nsresult rv;
4417 if (!aContent || ! aReturnDetails)
4418 return NS_ERROR_NULL_POINTER;
4420 // it is common to have no ranges, to optimize that
4421 if (mRanges.Length() == 0)
4422 return NS_OK;
4424 nsCOMArray<nsIRange> overlappingRanges;
4425 rv = GetRangesForIntervalCOMArray(aContent, aContentOffset,
4426 aContent, aContentOffset + aContentLength,
4427 PR_FALSE,
4428 &overlappingRanges);
4429 NS_ENSURE_SUCCESS(rv, rv);
4430 if (overlappingRanges.Count() == 0)
4431 return NS_OK;
4433 for (PRInt32 i = 0; i < overlappingRanges.Count(); i ++) {
4434 nsIRange* range = overlappingRanges[i];
4435 nsINode* startNode = range->GetStartParent();
4436 nsINode* endNode = range->GetEndParent();
4437 PRInt32 startOffset = range->StartOffset();
4438 PRInt32 endOffset = range->EndOffset();
4440 PRInt32 start = -1, end = -1;
4441 if (startNode == aContent && endNode == aContent) {
4442 if (startOffset < (aContentOffset + aContentLength) &&
4443 endOffset > aContentOffset) {
4444 // this range is totally inside the requested content range
4445 start = NS_MAX(0, startOffset - aContentOffset);
4446 end = NS_MIN(aContentLength, endOffset - aContentOffset);
4448 // otherwise, range is inside the requested node, but does not intersect
4449 // the requested content range, so ignore it
4450 } else if (startNode == aContent) {
4451 if (startOffset < (aContentOffset + aContentLength)) {
4452 // the beginning of the range is inside the requested node, but the
4453 // end is outside, select everything from there to the end
4454 start = NS_MAX(0, startOffset - aContentOffset);
4455 end = aContentLength;
4457 } else if (endNode == aContent) {
4458 if (endOffset > aContentOffset) {
4459 // the end of the range is inside the requested node, but the beginning
4460 // is outside, select everything from the beginning to there
4461 start = 0;
4462 end = NS_MIN(aContentLength, endOffset - aContentOffset);
4464 } else {
4465 // this range does not begin or end in the requested node, but since
4466 // GetRangesForInterval returned this range, we know it overlaps.
4467 // Therefore, this node is enclosed in the range, and we select all
4468 // of it.
4469 start = 0;
4470 end = aContentLength;
4472 if (start < 0)
4473 continue; // the ranges do not overlap the input range
4475 SelectionDetails* details = new SelectionDetails;
4476 if (!details)
4477 return NS_ERROR_OUT_OF_MEMORY;
4479 details->mNext = *aReturnDetails;
4480 details->mStart = start;
4481 details->mEnd = end;
4482 details->mType = aType;
4483 RangeData *rd = FindRangeData(range);
4484 if (rd) {
4485 details->mTextRangeStyle = rd->mTextRangeStyle;
4487 *aReturnDetails = details;
4489 return NS_OK;
4492 NS_IMETHODIMP
4493 nsTypedSelection::Repaint(nsPresContext* aPresContext)
4495 PRInt32 arrCount = (PRInt32)mRanges.Length();
4497 if (arrCount < 1)
4498 return NS_OK;
4500 PRInt32 i;
4502 for (i = 0; i < arrCount; i++)
4504 nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, PR_TRUE);
4506 if (NS_FAILED(rv)) {
4507 return rv;
4511 return NS_OK;
4514 NS_IMETHODIMP
4515 nsTypedSelection::GetCanCacheFrameOffset(PRBool *aCanCacheFrameOffset)
4517 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset);
4519 if (mCachedOffsetForFrame)
4520 *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset;
4521 else
4522 *aCanCacheFrameOffset = PR_FALSE;
4524 return NS_OK;
4527 NS_IMETHODIMP
4528 nsTypedSelection::SetCanCacheFrameOffset(PRBool aCanCacheFrameOffset)
4530 if (!mCachedOffsetForFrame) {
4531 mCachedOffsetForFrame = new CachedOffsetForFrame;
4534 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
4536 // clean up cached frame when turn off cache
4537 // fix bug 207936
4538 if (!aCanCacheFrameOffset) {
4539 mCachedOffsetForFrame->mLastCaretFrame = nsnull;
4542 return NS_OK;
4545 NS_IMETHODIMP
4546 nsTypedSelection::GetCachedFrameOffset(nsIFrame *aFrame, PRInt32 inOffset, nsPoint& aPoint)
4548 if (!mCachedOffsetForFrame) {
4549 mCachedOffsetForFrame = new CachedOffsetForFrame;
4552 nsresult rv = NS_OK;
4553 if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
4554 mCachedOffsetForFrame->mLastCaretFrame &&
4555 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
4556 (inOffset == mCachedOffsetForFrame->mLastContentOffset))
4558 // get cached frame offset
4559 aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
4561 else
4563 // Recalculate frame offset and cache it. Don't cache a frame offset if
4564 // GetPointFromOffset fails, though.
4565 rv = aFrame->GetPointFromOffset(inOffset, &aPoint);
4566 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
4567 mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
4568 mCachedOffsetForFrame->mLastCaretFrame = aFrame;
4569 mCachedOffsetForFrame->mLastContentOffset = inOffset;
4573 return rv;
4576 NS_IMETHODIMP
4577 nsTypedSelection::GetFrameSelection(nsFrameSelection **aFrameSelection) {
4578 NS_ENSURE_ARG_POINTER(aFrameSelection);
4579 *aFrameSelection = mFrameSelection;
4580 NS_IF_ADDREF(*aFrameSelection);
4581 return NS_OK;
4584 NS_IMETHODIMP
4585 nsTypedSelection::SetAncestorLimiter(nsIContent *aContent)
4587 if (mFrameSelection)
4588 mFrameSelection->SetAncestorLimiter(aContent);
4589 return NS_OK;
4592 RangeData*
4593 nsTypedSelection::FindRangeData(nsIDOMRange* aRange)
4595 NS_ENSURE_TRUE(aRange, nsnull);
4596 for (PRUint32 i = 0; i < mRanges.Length(); i++) {
4597 if (mRanges[i].mRange == aRange)
4598 return &mRanges[i];
4600 return nsnull;
4603 NS_IMETHODIMP
4604 nsTypedSelection::SetTextRangeStyle(nsIDOMRange *aRange,
4605 const nsTextRangeStyle &aTextRangeStyle)
4607 NS_ENSURE_ARG_POINTER(aRange);
4608 RangeData *rd = FindRangeData(aRange);
4609 if (rd) {
4610 rd->mTextRangeStyle = aTextRangeStyle;
4612 return NS_OK;
4615 nsresult
4616 nsTypedSelection::StartAutoScrollTimer(nsIFrame *aFrame,
4617 nsPoint& aPoint,
4618 PRUint32 aDelay)
4620 NS_PRECONDITION(aFrame, "Need a frame");
4622 nsresult result;
4623 if (!mFrameSelection)
4624 return NS_OK;//nothing to do
4626 if (!mAutoScrollTimer)
4628 mAutoScrollTimer = new nsAutoScrollTimer();
4630 if (!mAutoScrollTimer)
4631 return NS_ERROR_OUT_OF_MEMORY;
4633 result = mAutoScrollTimer->Init(mFrameSelection, this);
4635 if (NS_FAILED(result))
4636 return result;
4639 result = mAutoScrollTimer->SetDelay(aDelay);
4641 if (NS_FAILED(result))
4642 return result;
4644 return DoAutoScroll(aFrame, aPoint);
4647 nsresult
4648 nsTypedSelection::StopAutoScrollTimer()
4650 if (mAutoScrollTimer)
4651 return mAutoScrollTimer->Stop();
4653 return NS_OK;
4656 nsresult
4657 nsTypedSelection::DoAutoScroll(nsIFrame *aFrame, nsPoint& aPoint)
4659 NS_PRECONDITION(aFrame, "Need a frame");
4661 nsresult result = NS_OK;
4663 if (mAutoScrollTimer)
4664 result = mAutoScrollTimer->Stop();
4666 nsPresContext* presContext = aFrame->PresContext();
4667 nsRootPresContext* rootPC = presContext->GetRootPresContext();
4668 if (!rootPC)
4669 return NS_OK;
4670 nsIFrame* rootmostFrame = rootPC->PresShell()->FrameManager()->GetRootFrame();
4671 // Get the point relative to the root most frame because the scroll we are
4672 // about to do will change the coordinates of aFrame.
4673 nsPoint globalPoint = aPoint + aFrame->GetOffsetToCrossDoc(rootmostFrame);
4675 PRBool didScroll = presContext->PresShell()->
4676 ScrollFrameRectIntoView(aFrame, nsRect(aPoint, nsSize(1,1)),
4677 NS_PRESSHELL_SCROLL_ANYWHERE,
4678 NS_PRESSHELL_SCROLL_ANYWHERE, 0);
4681 // Start the AutoScroll timer if necessary.
4684 if (didScroll && mAutoScrollTimer)
4686 nsPoint presContextPoint = globalPoint -
4687 presContext->PresShell()->FrameManager()->GetRootFrame()->GetOffsetToCrossDoc(rootmostFrame);
4688 mAutoScrollTimer->Start(presContext, presContextPoint);
4691 return NS_OK;
4694 NS_IMETHODIMP
4695 nsTypedSelection::GetEnumerator(nsIEnumerator **aIterator)
4697 nsresult status = NS_ERROR_OUT_OF_MEMORY;
4698 nsSelectionIterator *iterator = new nsSelectionIterator(this);
4699 if (iterator && NS_FAILED(status = CallQueryInterface(iterator, aIterator)) )
4700 delete iterator;
4701 return status;
4706 /** RemoveAllRanges zeroes the selection
4708 NS_IMETHODIMP
4709 nsTypedSelection::RemoveAllRanges()
4711 if (!mFrameSelection)
4712 return NS_OK;//nothing to do
4713 nsRefPtr<nsPresContext> presContext;
4714 GetPresContext(getter_AddRefs(presContext));
4717 nsresult result = Clear(presContext);
4718 if (NS_FAILED(result))
4719 return result;
4721 // Turn off signal for table selection
4722 mFrameSelection->ClearTableCellSelection();
4724 return mFrameSelection->NotifySelectionListeners(GetType());
4725 // Also need to notify the frames!
4726 // PresShell::CharacterDataChanged should do that on DocumentChanged
4729 /** AddRange adds the specified range to the selection
4730 * @param aRange is the range to be added
4732 NS_IMETHODIMP
4733 nsTypedSelection::AddRange(nsIDOMRange* aRange)
4735 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
4736 return AddRange(range);
4739 nsresult
4740 nsTypedSelection::AddRange(nsIRange* aRange)
4742 if (!aRange) return NS_ERROR_NULL_POINTER;
4744 // This inserts a table cell range in proper document order
4745 // and returns NS_OK if range doesn't contain just one table cell
4746 PRBool didAddRange;
4747 PRInt32 rangeIndex;
4748 nsresult result = addTableCellRange(aRange, &didAddRange, &rangeIndex);
4749 if (NS_FAILED(result)) return result;
4751 if (!didAddRange)
4753 result = AddItem(aRange, &rangeIndex);
4754 if (NS_FAILED(result)) return result;
4757 NS_ASSERTION(rangeIndex >= 0, "Range index not returned");
4758 setAnchorFocusRange(rangeIndex);
4760 // Make sure the caret appears on the next line, if at a newline
4761 if (mType == nsISelectionController::SELECTION_NORMAL)
4762 SetInterlinePosition(PR_TRUE);
4764 nsRefPtr<nsPresContext> presContext;
4765 GetPresContext(getter_AddRefs(presContext));
4766 selectFrames(presContext, aRange, PR_TRUE);
4768 //ScrollIntoView(); this should not happen automatically
4769 if (!mFrameSelection)
4770 return NS_OK;//nothing to do
4772 return mFrameSelection->NotifySelectionListeners(GetType());
4775 // nsTypedSelection::RemoveRange
4777 // Removes the given range from the selection. The tricky part is updating
4778 // the flags on the frames that indicate whether they have a selection or
4779 // not. There could be several selection ranges on the frame, and clearing
4780 // the bit would cause the selection to not be drawn, even when there is
4781 // another range on the frame (bug 346185).
4783 // We therefore find any ranges that intersect the same nodes as the range
4784 // being removed, and cause them to set the selected bits back on their
4785 // selected frames after we've cleared the bit from ours.
4787 NS_IMETHODIMP
4788 nsTypedSelection::RemoveRange(nsIDOMRange* aRange)
4790 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
4791 return RemoveRange(range);
4794 nsresult
4795 nsTypedSelection::RemoveRange(nsIRange* aRange)
4797 if (!aRange)
4798 return NS_ERROR_INVALID_ARG;
4799 nsresult rv = RemoveItem(aRange);
4800 if (NS_FAILED(rv))
4801 return rv;
4803 nsINode* beginNode = aRange->GetStartParent();
4804 nsINode* endNode = aRange->GetEndParent();
4806 // find out the length of the end node, so we can select all of it
4807 PRInt32 beginOffset, endOffset;
4808 if (endNode->IsNodeOfType(nsINode::eTEXT)) {
4809 // Get the length of the text. We can't just use the offset because
4810 // another range could be touching this text node but not intersect our
4811 // range.
4812 beginOffset = 0;
4813 endOffset = static_cast<nsIContent*>(endNode)->TextLength();
4814 } else {
4815 // For non-text nodes, the given offsets should be sufficient.
4816 beginOffset = aRange->StartOffset();
4817 endOffset = aRange->EndOffset();
4820 // clear the selected bit from the removed range's frames
4821 nsRefPtr<nsPresContext> presContext;
4822 GetPresContext(getter_AddRefs(presContext));
4823 selectFrames(presContext, aRange, PR_FALSE);
4825 // add back the selected bit for each range touching our nodes
4826 nsCOMArray<nsIRange> affectedRanges;
4827 rv = GetRangesForIntervalCOMArray(beginNode, beginOffset,
4828 endNode, endOffset,
4829 PR_TRUE, &affectedRanges);
4830 NS_ENSURE_SUCCESS(rv, rv);
4831 for (PRInt32 i = 0; i < affectedRanges.Count(); i ++) {
4832 selectFrames(presContext, affectedRanges[i], PR_TRUE);
4835 PRInt32 cnt = mRanges.Length();
4836 if (aRange == mAnchorFocusRange) {
4837 // Reset anchor to LAST range or clear it if there are no ranges.
4838 setAnchorFocusRange(cnt - 1);
4840 // When the selection is user-created it makes sense to scroll the range
4841 // into view. The spell-check selection, however, is created and destroyed
4842 // in the background. We don't want to scroll in this case or the view
4843 // might appear to be moving randomly (bug 337871).
4844 if (mType != nsISelectionController::SELECTION_SPELLCHECK && cnt > 0)
4845 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE,
4846 PR_FALSE);
4849 if (!mFrameSelection)
4850 return NS_OK;//nothing to do
4851 return mFrameSelection->NotifySelectionListeners(GetType());
4857 * Collapse sets the whole selection to be one point.
4859 NS_IMETHODIMP
4860 nsTypedSelection::Collapse(nsIDOMNode* aParentNode, PRInt32 aOffset)
4862 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
4863 return Collapse(parentNode, aOffset);
4866 nsresult
4867 nsTypedSelection::Collapse(nsINode* aParentNode, PRInt32 aOffset)
4869 if (!aParentNode)
4870 return NS_ERROR_INVALID_ARG;
4871 if (!mFrameSelection)
4872 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
4873 mFrameSelection->InvalidateDesiredX();
4874 if (!IsValidSelectionPoint(mFrameSelection, aParentNode))
4875 return NS_ERROR_FAILURE;
4876 nsresult result;
4877 // Delete all of the current ranges
4878 nsRefPtr<nsPresContext> presContext;
4879 GetPresContext(getter_AddRefs(presContext));
4880 Clear(presContext);
4882 // Turn off signal for table selection
4883 mFrameSelection->ClearTableCellSelection();
4885 nsCOMPtr<nsIRange> range = new nsRange();
4886 if (!range) {
4887 NS_ASSERTION(PR_FALSE,"Couldn't make a range - nsFrameSelection::Collapse");
4888 return NS_ERROR_UNEXPECTED;
4890 result = range->SetEnd(aParentNode, aOffset);
4891 if (NS_FAILED(result))
4892 return result;
4893 result = range->SetStart(aParentNode, aOffset);
4894 if (NS_FAILED(result))
4895 return result;
4897 #ifdef DEBUG_SELECTION
4898 if (aParentNode)
4900 nsCOMPtr<nsIContent>content;
4901 content = do_QueryInterface(aParentNode);
4902 if (!content)
4903 return NS_ERROR_FAILURE;
4905 printf ("Sel. Collapse to %p %s %d\n", content.get(),
4906 nsAtomCString(content->Tag()).get(), aOffset);
4908 else {
4909 printf ("Sel. Collapse set to null parent.\n");
4911 #endif
4914 result = AddItem(range);
4915 setAnchorFocusRange(0);
4916 selectFrames(presContext, range, PR_TRUE);
4917 if (NS_FAILED(result))
4918 return result;
4919 return mFrameSelection->NotifySelectionListeners(GetType());
4923 * Sets the whole selection to be one point
4924 * at the start of the current selection
4926 NS_IMETHODIMP
4927 nsTypedSelection::CollapseToStart()
4929 PRInt32 cnt;
4930 nsresult rv = GetRangeCount(&cnt);
4931 if (NS_FAILED(rv) || cnt <= 0)
4932 return NS_ERROR_FAILURE;
4934 // Get the first range
4935 nsIRange* firstRange = mRanges[0].mRange;
4936 if (!firstRange)
4937 return NS_ERROR_FAILURE;
4939 return Collapse(firstRange->GetStartParent(), firstRange->StartOffset());
4943 * Sets the whole selection to be one point
4944 * at the end of the current selection
4946 NS_IMETHODIMP
4947 nsTypedSelection::CollapseToEnd()
4949 PRInt32 cnt;
4950 nsresult rv = GetRangeCount(&cnt);
4951 if (NS_FAILED(rv) || cnt <= 0)
4952 return NS_ERROR_FAILURE;
4954 // Get the last range
4955 nsIRange* lastRange = mRanges[cnt-1].mRange;
4956 if (!lastRange)
4957 return NS_ERROR_FAILURE;
4959 return Collapse(lastRange->GetEndParent(), lastRange->EndOffset());
4963 * IsCollapsed -- is the whole selection just one point, or unset?
4965 NS_IMETHODIMP
4966 nsTypedSelection::GetIsCollapsed(PRBool* aIsCollapsed)
4968 if (!aIsCollapsed)
4969 return NS_ERROR_NULL_POINTER;
4971 PRInt32 cnt = (PRInt32)mRanges.Length();;
4972 if (cnt == 0)
4974 *aIsCollapsed = PR_TRUE;
4975 return NS_OK;
4978 if (cnt != 1)
4980 *aIsCollapsed = PR_FALSE;
4981 return NS_OK;
4984 *aIsCollapsed = mRanges[0].mRange->Collapsed();
4985 return NS_OK;
4988 NS_IMETHODIMP
4989 nsTypedSelection::GetRangeCount(PRInt32* aRangeCount)
4991 *aRangeCount = (PRInt32)mRanges.Length();
4993 return NS_OK;
4996 NS_IMETHODIMP
4997 nsTypedSelection::GetRangeAt(PRInt32 aIndex, nsIDOMRange** aReturn)
4999 *aReturn = mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
5000 if (!*aReturn) {
5001 return NS_ERROR_INVALID_ARG;
5004 NS_ADDREF(*aReturn);
5006 return NS_OK;
5009 nsIRange*
5010 nsTypedSelection::GetRangeAt(PRInt32 aIndex)
5012 return mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
5016 utility function
5018 nsresult
5019 nsTypedSelection::CopyRangeToAnchorFocus(nsIRange *aRange)
5021 // XXXbz could we just clone into mAnchorFocusRange, or do consumers
5022 // expect that pointer to not change across this call?
5023 NS_ENSURE_STATE(mAnchorFocusRange);
5025 nsINode* startNode = aRange->GetStartParent();
5026 nsINode* endNode = aRange->GetEndParent();
5027 PRInt32 startOffset = aRange->StartOffset();
5028 PRInt32 endOffset = aRange->EndOffset();;
5029 if (NS_FAILED(mAnchorFocusRange->SetStart(startNode,startOffset)))
5031 // XXXbz what is this doing exactly?
5032 if (NS_FAILED(mAnchorFocusRange->SetEnd(endNode,endOffset)))
5033 return NS_ERROR_FAILURE;//???
5034 if (NS_FAILED(mAnchorFocusRange->SetStart(startNode,startOffset)))
5035 return NS_ERROR_FAILURE;//???
5037 else if (NS_FAILED(mAnchorFocusRange->SetEnd(endNode,endOffset)))
5038 return NS_ERROR_FAILURE;//???
5039 return NS_OK;
5042 void
5043 nsTypedSelection::ReplaceAnchorFocusRange(nsIRange *aRange)
5045 nsRefPtr<nsPresContext> presContext;
5046 GetPresContext(getter_AddRefs(presContext));
5047 if (presContext) {
5048 selectFrames(presContext, mAnchorFocusRange, PR_FALSE);
5049 CopyRangeToAnchorFocus(aRange);
5050 selectFrames(presContext, mAnchorFocusRange, PR_TRUE);
5055 Notes which might come in handy for extend:
5057 We can tell the direction of the selection by asking for the anchors selection
5058 if the begin is less than the end then we know the selection is to the "right".
5059 else it is a backwards selection.
5060 a = anchor
5061 1 = old cursor
5062 2 = new cursor
5064 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
5065 if (a < 2 && 1 > 2) a,2,1
5066 if (1 < a && a <2) 1,a,2
5067 if (a > 2 && 2 >1) 1,2,a
5068 if (2 < a && a <1) 2,a,1
5069 if (a > 1 && 1 >2) 2,1,a
5070 then execute
5071 a 1 2 select from 1 to 2
5072 a 2 1 deselect from 2 to 1
5073 1 a 2 deselect from 1 to a select from a to 2
5074 1 2 a deselect from 1 to 2
5075 2 1 a = continue selection from 2 to 1
5080 * Extend extends the selection away from the anchor.
5081 * We don't need to know the direction, because we always change the focus.
5083 NS_IMETHODIMP
5084 nsTypedSelection::Extend(nsIDOMNode* aParentNode, PRInt32 aOffset)
5086 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
5087 return Extend(parentNode, aOffset);
5090 nsresult
5091 nsTypedSelection::Extend(nsINode* aParentNode, PRInt32 aOffset)
5093 if (!aParentNode)
5094 return NS_ERROR_INVALID_ARG;
5096 // First, find the range containing the old focus point:
5097 if (!mAnchorFocusRange)
5098 return NS_ERROR_NOT_INITIALIZED;
5100 if (!mFrameSelection)
5101 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
5103 nsresult res;
5104 if (!IsValidSelectionPoint(mFrameSelection, aParentNode))
5105 return NS_ERROR_FAILURE;
5107 //mFrameSelection->InvalidateDesiredX();
5108 nsCOMPtr<nsIRange> difRange = new nsRange();
5109 nsCOMPtr<nsIRange> range;
5111 nsINode* anchorNode = GetAnchorNode();
5112 nsINode* focusNode = GetFocusNode();
5113 PRInt32 anchorOffset = GetAnchorOffset();
5114 PRInt32 focusOffset = GetFocusOffset();
5116 if (focusNode == aParentNode && focusOffset == aOffset)
5117 return NS_OK; //same node nothing to do!
5119 res = mAnchorFocusRange->CloneRange(getter_AddRefs(range));
5120 if (NS_FAILED(res))
5121 return res;
5122 //range = mAnchorFocusRange;
5124 nsINode* startNode = range->GetStartParent();
5125 nsINode* endNode = range->GetEndParent();
5126 PRInt32 startOffset = range->StartOffset();
5127 PRInt32 endOffset = range->EndOffset();;
5129 nsDirection dir = GetDirection();
5131 //compare anchor to old cursor.
5133 if (NS_FAILED(res))
5134 return res;
5135 PRInt32 result1 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
5136 focusNode, focusOffset);
5137 //compare old cursor to new cursor
5138 PRInt32 result2 = nsContentUtils::ComparePoints(focusNode, focusOffset,
5139 aParentNode, aOffset);
5140 //compare anchor to new cursor
5141 PRInt32 result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
5142 aParentNode, aOffset);
5144 if (result2 == 0) //not selecting anywhere
5145 return NS_OK;
5147 nsRefPtr<nsPresContext> presContext;
5148 GetPresContext(getter_AddRefs(presContext));
5149 if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2 a,1,2
5150 //select from 1 to 2 unless they are collapsed
5151 res = range->SetEnd(aParentNode, aOffset);
5152 if (NS_FAILED(res))
5153 return res;
5154 dir = eDirNext;
5155 res = difRange->SetEnd(range->GetEndParent(), range->EndOffset());
5156 res |= difRange->SetStart(focusNode, focusOffset);
5157 if (NS_FAILED(res))
5158 return res;
5159 selectFrames(presContext, difRange , PR_TRUE);
5160 res = CopyRangeToAnchorFocus(range);
5161 if (NS_FAILED(res))
5162 return res;
5164 else if (result1 == 0 && result3 > 0){//2, a1
5165 //select from 2 to 1a
5166 dir = eDirPrevious;
5167 res = range->SetStart(aParentNode, aOffset);
5168 if (NS_FAILED(res))
5169 return res;
5170 selectFrames(presContext, range, PR_TRUE);
5171 res = CopyRangeToAnchorFocus(range);
5172 if (NS_FAILED(res))
5173 return res;
5175 else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21
5176 //deselect from 2 to 1
5177 res = difRange->SetEnd(focusNode, focusOffset);
5178 res |= difRange->SetStart(aParentNode, aOffset);
5179 if (NS_FAILED(res))
5180 return res;
5182 res = range->SetEnd(aParentNode, aOffset);
5183 if (NS_FAILED(res))
5184 return res;
5185 res = CopyRangeToAnchorFocus(range);
5186 if (NS_FAILED(res))
5187 return res;
5188 selectFrames(presContext, difRange, PR_FALSE); // deselect now
5189 difRange->SetEnd(range->GetEndParent(), range->EndOffset());
5190 selectFrames(presContext, difRange, PR_TRUE); // must reselect last node maybe more
5192 else if (result1 >= 0 && result3 <= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2
5193 if (GetDirection() == eDirPrevious){
5194 res = range->SetStart(endNode, endOffset);
5195 if (NS_FAILED(res))
5196 return res;
5198 dir = eDirNext;
5199 res = range->SetEnd(aParentNode, aOffset);
5200 if (NS_FAILED(res))
5201 return res;
5202 if (focusNode != anchorNode || focusOffset != anchorOffset) {//if collapsed diff dont do anything
5203 res = difRange->SetStart(focusNode, focusOffset);
5204 res |= difRange->SetEnd(anchorNode, anchorOffset);
5205 if (NS_FAILED(res))
5206 return res;
5207 res = CopyRangeToAnchorFocus(range);
5208 if (NS_FAILED(res))
5209 return res;
5210 //deselect from 1 to a
5211 selectFrames(presContext, difRange , PR_FALSE);
5213 else
5215 res = CopyRangeToAnchorFocus(range);
5216 if (NS_FAILED(res))
5217 return res;
5219 //select from a to 2
5220 selectFrames(presContext, range , PR_TRUE);
5222 else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a
5223 //deselect from 1 to 2
5224 res = difRange->SetEnd(aParentNode, aOffset);
5225 res |= difRange->SetStart(focusNode, focusOffset);
5226 if (NS_FAILED(res))
5227 return res;
5228 dir = eDirPrevious;
5229 res = range->SetStart(aParentNode, aOffset);
5230 if (NS_FAILED(res))
5231 return res;
5233 res = CopyRangeToAnchorFocus(range);
5234 if (NS_FAILED(res))
5235 return res;
5236 selectFrames(presContext, difRange , PR_FALSE);
5237 difRange->SetStart(range->GetStartParent(), range->StartOffset());
5238 selectFrames(presContext, difRange, PR_TRUE);//must reselect last node
5240 else if (result3 >= 0 && result1 <= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1
5241 if (GetDirection() == eDirNext){
5242 range->SetEnd(startNode, startOffset);
5244 dir = eDirPrevious;
5245 res = range->SetStart(aParentNode, aOffset);
5246 if (NS_FAILED(res))
5247 return res;
5248 //deselect from a to 1
5249 if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything
5250 res = difRange->SetStart(anchorNode, anchorOffset);
5251 res |= difRange->SetEnd(focusNode, focusOffset);
5252 res |= CopyRangeToAnchorFocus(range);
5253 if (NS_FAILED(res))
5254 return res;
5255 selectFrames(presContext, difRange, PR_FALSE);
5257 else
5259 res = CopyRangeToAnchorFocus(range);
5260 if (NS_FAILED(res))
5261 return res;
5263 //select from 2 to a
5264 selectFrames(presContext, range , PR_TRUE);
5266 else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a
5267 //select from 2 to 1
5268 res = range->SetStart(aParentNode, aOffset);
5269 if (NS_FAILED(res))
5270 return res;
5271 dir = eDirPrevious;
5272 res = difRange->SetEnd(focusNode, focusOffset);
5273 res |= difRange->SetStart(range->GetStartParent(), range->StartOffset());
5274 if (NS_FAILED(res))
5275 return res;
5277 selectFrames(presContext, difRange, PR_TRUE);
5278 res = CopyRangeToAnchorFocus(range);
5279 if (NS_FAILED(res))
5280 return res;
5283 DEBUG_OUT_RANGE(range);
5284 #ifdef DEBUG_SELECTION
5285 if (eDirNext == mDirection)
5286 printf(" direction = 1 LEFT TO RIGHT\n");
5287 else
5288 printf(" direction = 0 RIGHT TO LEFT\n");
5289 #endif
5290 SetDirection(dir);
5291 #ifdef DEBUG_SELECTION
5292 if (aParentNode)
5294 nsCOMPtr<nsIContent>content;
5295 content = do_QueryInterface(aParentNode);
5297 printf ("Sel. Extend to %p %s %d\n", content.get(),
5298 nsAtomCString(content->Tag()).get(), aOffset);
5300 else {
5301 printf ("Sel. Extend set to null parent.\n");
5303 #endif
5304 return mFrameSelection->NotifySelectionListeners(GetType());
5307 static nsresult
5308 GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, PRInt32 &aOffset)
5310 NS_ASSERTION((aChild && aParent), "bad args");
5311 nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);
5312 nsCOMPtr<nsIContent> cChild = do_QueryInterface(aChild);
5314 if (!cChild || !content)
5315 return NS_ERROR_NULL_POINTER;
5317 aOffset = content->IndexOf(cChild);
5319 return NS_OK;
5322 NS_IMETHODIMP
5323 nsTypedSelection::SelectAllChildren(nsIDOMNode* aParentNode)
5325 NS_ENSURE_ARG_POINTER(aParentNode);
5327 if (mFrameSelection)
5329 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5331 nsresult result = Collapse(aParentNode, 0);
5332 if (NS_SUCCEEDED(result))
5334 nsCOMPtr<nsIDOMNode>lastChild;
5335 result = aParentNode->GetLastChild(getter_AddRefs(lastChild));
5336 if ((NS_SUCCEEDED(result)) && lastChild)
5338 PRInt32 numBodyChildren=0;
5339 GetChildOffset(lastChild, aParentNode, numBodyChildren);
5340 if (mFrameSelection)
5342 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5344 result = Extend(aParentNode, numBodyChildren+1);
5347 return result;
5350 NS_IMETHODIMP
5351 nsTypedSelection::ContainsNode(nsIDOMNode* aNode, PRBool aAllowPartial,
5352 PRBool* aYes)
5354 nsresult rv;
5355 if (!aYes)
5356 return NS_ERROR_NULL_POINTER;
5357 *aYes = PR_FALSE;
5359 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
5360 if (mRanges.Length() == 0 || !node)
5361 return NS_OK;
5363 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
5364 PRUint32 nodeLength;
5365 PRBool isData = node->IsNodeOfType(nsINode::eDATA_NODE);
5366 if (isData) {
5367 nodeLength = static_cast<nsIContent*>(node.get())->TextLength();
5368 } else {
5369 nodeLength = node->GetChildCount();
5372 nsCOMArray<nsIRange> overlappingRanges;
5373 rv = GetRangesForIntervalCOMArray(node, 0, node, nodeLength,
5374 PR_FALSE, &overlappingRanges);
5375 NS_ENSURE_SUCCESS(rv, rv);
5376 if (overlappingRanges.Count() == 0)
5377 return NS_OK; // no ranges overlap
5379 // if the caller said partial intersections are OK, we're done
5380 if (aAllowPartial) {
5381 *aYes = PR_TRUE;
5382 return NS_OK;
5385 // text nodes always count as inside
5386 if (isData) {
5387 *aYes = PR_TRUE;
5388 return NS_OK;
5391 // The caller wants to know if the node is entirely within the given range,
5392 // so we have to check all intersecting ranges.
5393 for (PRInt32 i = 0; i < overlappingRanges.Count(); i ++) {
5394 PRBool nodeStartsBeforeRange, nodeEndsAfterRange;
5395 if (NS_SUCCEEDED(nsRange::CompareNodeToRange(node, overlappingRanges[i],
5396 &nodeStartsBeforeRange,
5397 &nodeEndsAfterRange))) {
5398 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
5399 *aYes = PR_TRUE;
5400 return NS_OK;
5404 return NS_OK;
5408 nsresult
5409 nsTypedSelection::GetPresContext(nsPresContext **aPresContext)
5411 if (!mFrameSelection)
5412 return NS_ERROR_FAILURE;//nothing to do
5413 nsIPresShell *shell = mFrameSelection->GetShell();
5415 if (!shell)
5416 return NS_ERROR_NULL_POINTER;
5418 NS_IF_ADDREF(*aPresContext = shell->GetPresContext());
5419 return NS_OK;
5422 nsresult
5423 nsTypedSelection::GetPresShell(nsIPresShell **aPresShell)
5425 if (mPresShellWeak)
5427 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShellWeak);
5428 if (presShell)
5429 NS_ADDREF(*aPresShell = presShell);
5430 return NS_OK;
5432 nsresult rv = NS_OK;
5433 if (!mFrameSelection)
5434 return NS_ERROR_FAILURE;//nothing to do
5436 nsIPresShell *shell = mFrameSelection->GetShell();
5438 mPresShellWeak = do_GetWeakReference(shell); // the presshell owns us, so no addref
5439 if (mPresShellWeak)
5440 NS_ADDREF(*aPresShell = shell);
5441 return rv;
5444 nsIFrame *
5445 nsTypedSelection::GetSelectionAnchorGeometry(SelectionRegion aRegion,
5446 nsRect *aRect)
5448 if (!mFrameSelection)
5449 return nsnull; // nothing to do
5451 NS_ENSURE_TRUE(aRect, nsnull);
5453 aRect->SetRect(0, 0, 0, 0);
5455 nsINode *node = nsnull;
5456 PRInt32 nodeOffset = 0;
5457 nsIFrame *frame = nsnull;
5459 switch (aRegion) {
5460 case nsISelectionController::SELECTION_ANCHOR_REGION:
5461 node = GetAnchorNode();
5462 nodeOffset = GetAnchorOffset();
5463 break;
5464 case nsISelectionController::SELECTION_FOCUS_REGION:
5465 node = GetFocusNode();
5466 nodeOffset = GetFocusOffset();
5467 break;
5468 default:
5469 return nsnull;
5472 if (!node)
5473 return nsnull;
5475 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
5476 NS_ENSURE_TRUE(content.get(), nsnull);
5477 PRInt32 frameOffset = 0;
5478 frame = mFrameSelection->GetFrameForNodeOffset(content, nodeOffset,
5479 mFrameSelection->GetHint(),
5480 &frameOffset);
5481 if (!frame)
5482 return nsnull;
5484 // Figure out what node type we have, then get the
5485 // appropriate rect for it's nodeOffset.
5486 PRBool isText = node->IsNodeOfType(nsINode::eTEXT);
5488 nsPoint pt(0, 0);
5489 if (isText) {
5490 nsIFrame* childFrame = nsnull;
5491 frameOffset = 0;
5492 nsresult rv =
5493 frame->GetChildFrameContainingOffset(nodeOffset,
5494 mFrameSelection->GetHint(),
5495 &frameOffset, &childFrame);
5496 if (NS_FAILED(rv))
5497 return nsnull;
5498 if (!childFrame)
5499 return nsnull;
5501 frame = childFrame;
5503 // Get the x coordinate of the offset into the text frame.
5504 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
5505 if (NS_FAILED(rv))
5506 return nsnull;
5509 // Return the rect relative to the frame, with zero width.
5510 if (isText) {
5511 aRect->x = pt.x;
5512 } else if (mFrameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
5513 // It's the frame's right edge we're interested in.
5514 aRect->x = frame->GetRect().width;
5516 aRect->height = frame->GetRect().height;
5518 return frame;
5521 NS_IMETHODIMP
5522 nsTypedSelection::ScrollSelectionIntoViewEvent::Run()
5524 if (!mTypedSelection)
5525 return NS_OK; // event revoked
5527 mTypedSelection->mScrollEvent.Forget();
5528 mTypedSelection->ScrollIntoView(mRegion, PR_TRUE, PR_TRUE);
5529 return NS_OK;
5532 nsresult
5533 nsTypedSelection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion)
5535 // If we've already posted an event, revoke it and place a new one at the
5536 // end of the queue to make sure that any new pending reflow events are
5537 // processed before we scroll. This will insure that we scroll to the
5538 // correct place on screen.
5539 mScrollEvent.Revoke();
5541 nsRefPtr<ScrollSelectionIntoViewEvent> ev =
5542 new ScrollSelectionIntoViewEvent(this, aRegion);
5543 nsresult rv = NS_DispatchToCurrentThread(ev);
5544 NS_ENSURE_SUCCESS(rv, rv);
5546 mScrollEvent = ev;
5547 return NS_OK;
5550 NS_IMETHODIMP
5551 nsTypedSelection::ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous,
5552 PRInt16 aVPercent, PRInt16 aHPercent)
5554 return ScrollIntoView(aRegion, aIsSynchronous, PR_FALSE,
5555 aVPercent, aHPercent);
5558 nsresult
5559 nsTypedSelection::ScrollIntoView(SelectionRegion aRegion,
5560 PRBool aIsSynchronous, PRBool aDoFlush,
5561 PRInt16 aVPercent, PRInt16 aHPercent)
5563 nsresult result;
5564 if (!mFrameSelection)
5565 return NS_OK;//nothing to do
5567 if (mFrameSelection->GetBatching())
5568 return NS_OK;
5570 if (!aIsSynchronous)
5571 return PostScrollSelectionIntoViewEvent(aRegion);
5574 // Shut the caret off before scrolling to avoid
5575 // leaving caret turds on the screen!
5577 nsCOMPtr<nsIPresShell> presShell;
5578 result = GetPresShell(getter_AddRefs(presShell));
5579 if (NS_FAILED(result) || !presShell)
5580 return result;
5581 nsRefPtr<nsCaret> caret = presShell->GetCaret();
5582 if (caret)
5584 // Now that text frame character offsets are always valid (though not
5585 // necessarily correct), the worst that will happen if we don't flush here
5586 // is that some callers might scroll to the wrong place. Those should
5587 // either manually flush if they're in a safe position for it or use the
5588 // async version of this method.
5589 if (aDoFlush) {
5590 presShell->FlushPendingNotifications(Flush_Layout);
5592 // Reget the presshell, since it might have gone away.
5593 result = GetPresShell(getter_AddRefs(presShell));
5594 if (NS_FAILED(result) || !presShell)
5595 return result;
5598 StCaretHider caretHider(caret); // stack-based class hides and shows the caret
5601 // Scroll the selection region into view.
5604 nsRect rect;
5605 nsIFrame* frame = GetSelectionAnchorGeometry(aRegion, &rect);
5606 if (!frame)
5607 return NS_ERROR_FAILURE;
5609 presShell->ScrollFrameRectIntoView(frame, rect, aVPercent, aHPercent, 0);
5610 return NS_OK;
5612 return result;
5617 NS_IMETHODIMP
5618 nsTypedSelection::AddSelectionListener(nsISelectionListener* aNewListener)
5620 if (!aNewListener)
5621 return NS_ERROR_NULL_POINTER;
5622 return mSelectionListeners.AppendObject(aNewListener) ? NS_OK : NS_ERROR_FAILURE; // addrefs
5627 NS_IMETHODIMP
5628 nsTypedSelection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove)
5630 if (!aListenerToRemove )
5631 return NS_ERROR_NULL_POINTER;
5632 return mSelectionListeners.RemoveObject(aListenerToRemove) ? NS_OK : NS_ERROR_FAILURE; // releases
5636 nsresult
5637 nsTypedSelection::NotifySelectionListeners()
5639 if (!mFrameSelection)
5640 return NS_OK;//nothing to do
5642 if (mFrameSelection->GetBatching()){
5643 mFrameSelection->SetDirty();
5644 return NS_OK;
5646 PRInt32 cnt = mSelectionListeners.Count();
5647 nsCOMArray<nsISelectionListener> selectionListeners(mSelectionListeners);
5649 nsCOMPtr<nsIDOMDocument> domdoc;
5650 nsCOMPtr<nsIPresShell> shell;
5651 nsresult rv = GetPresShell(getter_AddRefs(shell));
5652 if (NS_SUCCEEDED(rv) && shell)
5653 domdoc = do_QueryInterface(shell->GetDocument());
5654 short reason = mFrameSelection->PopReason();
5655 for (PRInt32 i = 0; i < cnt; i++)
5657 nsISelectionListener* thisListener = selectionListeners[i];
5658 if (thisListener)
5659 thisListener->NotifySelectionChanged(domdoc, this, reason);
5661 return NS_OK;
5664 NS_IMETHODIMP
5665 nsTypedSelection::StartBatchChanges()
5667 if (mFrameSelection)
5668 mFrameSelection->StartBatchChanges();
5670 return NS_OK;
5675 NS_IMETHODIMP
5676 nsTypedSelection::EndBatchChanges()
5678 if (mFrameSelection)
5679 mFrameSelection->EndBatchChanges();
5681 return NS_OK;
5686 NS_IMETHODIMP
5687 nsTypedSelection::DeleteFromDocument()
5689 if (!mFrameSelection)
5690 return NS_OK;//nothing to do
5691 return mFrameSelection->DeleteFromDocument();
5694 NS_IMETHODIMP
5695 nsTypedSelection::Modify(const nsAString& aAlter, const nsAString& aDirection,
5696 const nsAString& aGranularity)
5698 // Silently exit if there's no selection or no focus node.
5699 if (!mFrameSelection || !GetAnchorFocusRange() || !GetFocusNode()) {
5700 return NS_OK;
5703 if (!aAlter.LowerCaseEqualsLiteral("move") &&
5704 !aAlter.LowerCaseEqualsLiteral("extend")) {
5705 return NS_ERROR_INVALID_ARG;
5708 if (!aDirection.LowerCaseEqualsLiteral("forward") &&
5709 !aDirection.LowerCaseEqualsLiteral("backward") &&
5710 !aDirection.LowerCaseEqualsLiteral("left") &&
5711 !aDirection.LowerCaseEqualsLiteral("right")) {
5712 return NS_ERROR_INVALID_ARG;
5715 // Line moves are always visual.
5716 PRBool visual = aDirection.LowerCaseEqualsLiteral("left") ||
5717 aDirection.LowerCaseEqualsLiteral("right") ||
5718 aGranularity.LowerCaseEqualsLiteral("line");
5720 PRBool forward = aDirection.LowerCaseEqualsLiteral("forward") ||
5721 aDirection.LowerCaseEqualsLiteral("right");
5723 PRBool extend = aAlter.LowerCaseEqualsLiteral("extend");
5725 // The PRUint32 casts below prevent an enum mismatch warning.
5726 nsSelectionAmount amount;
5727 PRUint32 keycode;
5728 if (aGranularity.LowerCaseEqualsLiteral("character")) {
5729 amount = eSelectCharacter;
5730 keycode = forward ? (PRUint32) nsIDOMKeyEvent::DOM_VK_RIGHT :
5731 (PRUint32) nsIDOMKeyEvent::DOM_VK_LEFT;
5733 else if (aGranularity.LowerCaseEqualsLiteral("word")) {
5734 amount = eSelectWord;
5735 keycode = forward ? (PRUint32) nsIDOMKeyEvent::DOM_VK_RIGHT :
5736 (PRUint32) nsIDOMKeyEvent::DOM_VK_LEFT;
5738 else if (aGranularity.LowerCaseEqualsLiteral("line")) {
5739 amount = eSelectLine;
5740 keycode = forward ? (PRUint32) nsIDOMKeyEvent::DOM_VK_DOWN :
5741 (PRUint32) nsIDOMKeyEvent::DOM_VK_UP;
5743 else if (aGranularity.LowerCaseEqualsLiteral("lineboundary")) {
5744 amount = eSelectLine;
5745 keycode = forward ? (PRUint32) nsIDOMKeyEvent::DOM_VK_END :
5746 (PRUint32) nsIDOMKeyEvent::DOM_VK_HOME;
5748 else if (aGranularity.LowerCaseEqualsLiteral("sentence") ||
5749 aGranularity.LowerCaseEqualsLiteral("sentenceboundary") ||
5750 aGranularity.LowerCaseEqualsLiteral("paragraph") ||
5751 aGranularity.LowerCaseEqualsLiteral("paragraphboundary") ||
5752 aGranularity.LowerCaseEqualsLiteral("documentboundary")) {
5753 return NS_ERROR_NOT_IMPLEMENTED;
5755 else {
5756 return NS_ERROR_INVALID_ARG;
5759 // If the anchor doesn't equal the focus and we try to move without first
5760 // collapsing the selection, MoveCaret will collapse the selection and quit.
5761 // To avoid this, we need to collapse the selection first.
5762 nsresult rv = NS_OK;
5763 if (!extend) {
5764 nsINode* focusNode = GetFocusNode();
5765 // We should have checked earlier that there was a focus node.
5766 NS_ENSURE_TRUE(focusNode, NS_ERROR_UNEXPECTED);
5767 PRInt32 focusOffset = GetFocusOffset();
5768 Collapse(focusNode, focusOffset);
5771 // If the base level of the focused frame is odd, we may have to swap the
5772 // direction of the keycode.
5773 nsIFrame *frame;
5774 PRInt32 offset;
5775 rv = GetPrimaryFrameForFocusNode(&frame, &offset, visual);
5776 if (NS_SUCCEEDED(rv) && frame) {
5777 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame);
5779 if (baseLevel & 1) {
5780 if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_RIGHT) {
5781 keycode = nsIDOMKeyEvent::DOM_VK_LEFT;
5783 else if (!visual && keycode == nsIDOMKeyEvent::DOM_VK_LEFT) {
5784 keycode = nsIDOMKeyEvent::DOM_VK_RIGHT;
5786 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_HOME) {
5787 keycode = nsIDOMKeyEvent::DOM_VK_END;
5789 else if (visual && keycode == nsIDOMKeyEvent::DOM_VK_END) {
5790 keycode = nsIDOMKeyEvent::DOM_VK_HOME;
5795 // MoveCaret will return an error if it can't move in the specified
5796 // direction, but we just ignore this error unless it's a line move, in which
5797 // case we call nsISelectionController::CompleteMove to move the cursor to
5798 // the beginning/end of the line.
5799 rv = mFrameSelection->MoveCaret(keycode, extend, amount, visual);
5801 if (aGranularity.LowerCaseEqualsLiteral("line") && NS_FAILED(rv)) {
5802 nsCOMPtr<nsISelectionController> shell =
5803 do_QueryInterface(mFrameSelection->GetShell());
5804 if (!shell)
5805 return NS_OK;
5806 shell->CompleteMove(forward, extend);
5808 return NS_OK;
5811 /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction
5812 * @param aLangRTL is PR_TRUE if the new language is right-to-left or PR_FALSE if the new language is left-to-right
5814 NS_IMETHODIMP
5815 nsTypedSelection::SelectionLanguageChange(PRBool aLangRTL)
5817 if (!mFrameSelection)
5818 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
5819 nsresult result;
5820 nsIFrame *focusFrame = 0;
5822 result = GetPrimaryFrameForFocusNode(&focusFrame, nsnull, PR_FALSE);
5823 if (NS_FAILED(result)) {
5824 return result;
5826 if (!focusFrame) {
5827 return NS_ERROR_FAILURE;
5830 PRInt32 frameStart, frameEnd;
5831 focusFrame->GetOffsets(frameStart, frameEnd);
5832 nsRefPtr<nsPresContext> context;
5833 PRUint8 levelBefore, levelAfter;
5834 result = GetPresContext(getter_AddRefs(context));
5835 if (NS_FAILED(result) || !context)
5836 return result?result:NS_ERROR_FAILURE;
5838 PRUint8 level = NS_GET_EMBEDDING_LEVEL(focusFrame);
5839 PRInt32 focusOffset = GetFocusOffset();
5840 if ((focusOffset != frameStart) && (focusOffset != frameEnd))
5841 // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor
5842 // is equal to the frame level
5843 levelBefore = levelAfter = level;
5844 else {
5845 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters
5846 // before and after the cursor
5847 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
5849 nsFrameSelection::HINT hint;
5851 if ((focusOffset == frameStart && level) // beginning of an RTL frame
5852 || (focusOffset == frameEnd && !level)) { // end of an LTR frame
5853 hint = nsFrameSelection::HINTRIGHT;
5855 else { // end of an RTL frame or beginning of an LTR frame
5856 hint = nsFrameSelection::HINTLEFT;
5858 mFrameSelection->SetHint(hint);
5860 nsPrevNextBidiLevels levels = mFrameSelection->
5861 GetPrevNextBidiLevels(focusContent, focusOffset, PR_FALSE);
5863 levelBefore = levels.mLevelBefore;
5864 levelAfter = levels.mLevelAfter;
5867 if ((levelBefore & 1) == (levelAfter & 1)) {
5868 // if cursor is between two characters with the same orientation, changing the keyboard language
5869 // must toggle the cursor level between the level of the character with the lowest level
5870 // (if the new language corresponds to the orientation of that character) and this level plus 1
5871 // (if the new language corresponds to the opposite orientation)
5872 if ((level != levelBefore) && (level != levelAfter))
5873 level = NS_MIN(levelBefore, levelAfter);
5874 if ((level & 1) == aLangRTL)
5875 mFrameSelection->SetCaretBidiLevel(level);
5876 else
5877 mFrameSelection->SetCaretBidiLevel(level + 1);
5879 else {
5880 // if cursor is between characters with opposite orientations, changing the keyboard language must change
5881 // the cursor level to that of the adjacent character with the orientation corresponding to the new language.
5882 if ((levelBefore & 1) == aLangRTL)
5883 mFrameSelection->SetCaretBidiLevel(levelBefore);
5884 else
5885 mFrameSelection->SetCaretBidiLevel(levelAfter);
5888 // The caret might have moved, so invalidate the desired X position
5889 // for future usages of up-arrow or down-arrow
5890 mFrameSelection->InvalidateDesiredX();
5892 return NS_OK;
5896 // nsAutoCopyListener
5898 nsAutoCopyListener* nsAutoCopyListener::sInstance = nsnull;
5900 NS_IMPL_ISUPPORTS1(nsAutoCopyListener, nsISelectionListener)
5903 * What we do now:
5904 * On every selection change, we copy to the clipboard anew, creating a
5905 * HTML buffer, a transferable, an nsISupportsString and
5906 * a huge mess every time. This is basically what nsPresShell::DoCopy does
5907 * to move the selection into the clipboard for Edit->Copy.
5909 * What we should do, to make our end of the deal faster:
5910 * Create a singleton transferable with our own magic converter. When selection
5911 * changes (use a quick cache to detect ``real'' changes), we put the new
5912 * nsISelection in the transferable. Our magic converter will take care of
5913 * transferable->whatever-other-format when the time comes to actually
5914 * hand over the clipboard contents.
5916 * Other issues:
5917 * - which X clipboard should we populate?
5918 * - should we use a different one than Edit->Copy, so that inadvertant
5919 * selections (or simple clicks, which currently cause a selection
5920 * notification, regardless of if they're in the document which currently has
5921 * selection!) don't lose the contents of the ``application''? Or should we
5922 * just put some intelligence in the ``is this a real selection?'' code to
5923 * protect our selection against clicks in other documents that don't create
5924 * selections?
5925 * - maybe we should just never clear the X clipboard? That would make this
5926 * problem just go away, which is very tempting.
5929 NS_IMETHODIMP
5930 nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument *aDoc,
5931 nsISelection *aSel, PRInt16 aReason)
5933 if (!(aReason & nsISelectionListener::MOUSEUP_REASON ||
5934 aReason & nsISelectionListener::SELECTALL_REASON ||
5935 aReason & nsISelectionListener::KEYPRESS_REASON))
5936 return NS_OK; //dont care if we are still dragging
5938 PRBool collapsed;
5939 if (!aDoc || !aSel ||
5940 NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) {
5941 #ifdef DEBUG_CLIPBOARD
5942 fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
5943 #endif
5944 /* clear X clipboard? */
5945 return NS_OK;
5948 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
5949 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
5951 // call the copy code
5952 return nsCopySupport::HTMLCopy(aSel, doc, nsIClipboard::kSelectionClipboard);