Fixed regex LIR to be x64 compliant (bug 514548, r=lw).
[mozilla-central.git] / layout / generic / nsSelection.cpp
blobcfcac2186d686510bae9667a8f309376f52146c6
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 <mats.palmgren@bredband.net>
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 "nsISelectionPrivate.h"
54 #include "nsISelectionListener.h"
55 #include "nsIComponentManager.h"
56 #include "nsContentCID.h"
57 #include "nsIContent.h"
58 #include "nsIDOMElement.h"
59 #include "nsIDOMNode.h"
60 #include "nsRange.h"
61 #include "nsCOMArray.h"
62 #include "nsGUIEvent.h"
63 #include "nsIDOMKeyEvent.h"
64 #include "nsITableLayout.h"
65 #include "nsITableCellLayout.h"
66 #include "nsIDOMNodeList.h"
67 #include "nsTArray.h"
69 #include "nsISelectionListener.h"
70 #include "nsIContentIterator.h"
71 #include "nsIDocumentEncoder.h"
73 // for IBMBIDI
74 #include "nsFrameTraversal.h"
75 #include "nsILineIterator.h"
76 #include "nsGkAtoms.h"
77 #include "nsIFrameTraversal.h"
78 #include "nsLayoutUtils.h"
79 #include "nsLayoutCID.h"
80 #include "nsBidiPresUtils.h"
81 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
82 #include "nsTextFrame.h"
84 #include "nsIDOMText.h"
86 #include "nsContentUtils.h"
87 #include "nsThreadUtils.h"
89 //included for desired x position;
90 #include "nsPresContext.h"
91 #include "nsIPresShell.h"
92 #include "nsCaret.h"
95 // included for view scrolling
96 #include "nsIViewManager.h"
97 #include "nsIScrollableView.h"
98 #include "nsIDeviceContext.h"
99 #include "nsITimer.h"
100 #include "nsIServiceManager.h"
101 #include "nsFrameManager.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 nsISelectionPrivate,
181 public nsSupportsWeakReference
183 public:
184 nsTypedSelection();
185 nsTypedSelection(nsFrameSelection *aList);
186 virtual ~nsTypedSelection();
188 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
189 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTypedSelection, nsISelection)
190 NS_DECL_NSISELECTION
191 NS_DECL_NSISELECTION2
192 NS_DECL_NSISELECTIONPRIVATE
194 // utility methods for scrolling the selection into view
195 nsresult GetPresContext(nsPresContext **aPresContext);
196 nsresult GetPresShell(nsIPresShell **aPresShell);
197 nsresult GetRootScrollableView(nsIScrollableView **aScrollableView);
198 nsresult GetFrameToScrolledViewOffsets(nsIScrollableView *aScrollableView, nsIFrame *aFrame, nscoord *aXOffset, nscoord *aYOffset);
199 nsresult GetPointFromOffset(nsIFrame *aFrame, PRInt32 aContentOffset, nsPoint *aPoint);
200 nsresult GetSelectionRegionRectAndScrollableView(SelectionRegion aRegion, nsRect *aRect, nsIScrollableView **aScrollableView);
201 nsresult ScrollRectIntoView(nsIScrollableView *aScrollableView, nsRect& aRect, PRIntn aVPercent, PRIntn aHPercent, PRBool aScrollParentViews);
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, views etc.
252 nsresult StartAutoScrollTimer(nsPresContext *aPresContext,
253 nsIView *aView,
254 nsPoint& aPoint,
255 PRUint32 aDelay);
257 nsresult StopAutoScrollTimer();
260 private:
261 friend class nsAutoScrollTimer;
263 // Note: DoAutoScrollView might destroy arbitrary frames, views etc.
264 nsresult DoAutoScrollView(nsPresContext *aPresContext,
265 nsIView *aView,
266 nsPoint& aPoint,
267 PRBool aScrollParentViews);
269 // Note: ScrollPointIntoClipView might destroy arbitrary frames, views etc.
270 nsresult ScrollPointIntoClipView(nsPresContext *aPresContext, nsIView *aView, nsPoint& aPoint, PRBool *aDidScroll);
271 // Note: ScrollPointIntoView might destroy arbitrary frames, views etc.
272 nsresult ScrollPointIntoView(nsPresContext *aPresContext, nsIView *aView, nsPoint& aPoint, PRBool aScrollParentViews, PRBool *aDidScroll);
273 nsresult GetViewAncestorOffset(nsIView *aView, nsIView *aAncestorView, nscoord *aXOffset, nscoord *aYOffset);
275 public:
276 SelectionType GetType(){return mType;}
277 void SetType(SelectionType aType){mType = aType;}
279 nsresult NotifySelectionListeners();
281 private:
282 friend class nsSelectionIterator;
284 class ScrollSelectionIntoViewEvent;
285 friend class ScrollSelectionIntoViewEvent;
287 class ScrollSelectionIntoViewEvent : public nsRunnable {
288 public:
289 NS_DECL_NSIRUNNABLE
290 ScrollSelectionIntoViewEvent(nsTypedSelection *aTypedSelection,
291 SelectionRegion aRegion)
292 : mTypedSelection(aTypedSelection),
293 mRegion(aRegion) {
294 NS_ASSERTION(aTypedSelection, "null parameter");
296 void Revoke() { mTypedSelection = nsnull; }
297 private:
298 nsTypedSelection *mTypedSelection;
299 SelectionRegion mRegion;
302 void setAnchorFocusRange(PRInt32 aIndex); // pass in index into mRanges;
303 // negative value clears
304 // mAnchorFocusRange
305 nsresult SelectAllFramesForContent(nsIContentIterator *aInnerIter,
306 nsIContent *aContent,
307 PRBool aSelected);
308 nsresult selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PRBool aSelect);
309 nsresult getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol);
310 nsresult addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange, PRInt32 *aOutIndex);
312 PRInt32 FindInsertionPoint(
313 nsTArray<RangeData>* aElementArray,
314 nsINode* aPointNode, PRInt32 aPointOffset,
315 PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*));
316 PRBool EqualsRangeAtPoint(nsINode* aBeginNode, PRInt32 aBeginOffset,
317 nsINode* aEndNode, PRInt32 aEndOffset,
318 PRInt32 aRangeIndex);
319 nsresult GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
320 nsINode* aEndNode, PRInt32 aEndOffset,
321 PRBool aAllowAdjacent,
322 nsCOMArray<nsIRange>* aRanges);
323 void GetIndicesForInterval(nsINode* aBeginNode, PRInt32 aBeginOffset,
324 nsINode* aEndNode, PRInt32 aEndOffset,
325 PRBool aAllowAdjacent,
326 PRInt32 *aStartIndex, PRInt32 *aEndIndex);
327 RangeData* FindRangeData(nsIDOMRange* aRange);
329 // These are the ranges inside this selection. They are kept sorted in order
330 // of DOM start position.
332 // This data structure is sorted by the range beginnings. As the ranges are
333 // disjoint, it is also implicitly sorted by the range endings. This allows
334 // us to perform binary searches when searching for existence of a range,
335 // giving us O(log n) search time.
337 // Inserting a new range requires finding the overlapping interval, requiring
338 // two binary searches plus up to an additional 6 DOM comparisons. If this
339 // proves to be a performance concern, then an interval tree may be a
340 // possible solution, allowing the calculation of the overlap interval in
341 // O(log n) time, though this would require rebalancing and other overhead.
342 nsTArray<RangeData> mRanges;
344 nsCOMPtr<nsIRange> mAnchorFocusRange;
345 nsRefPtr<nsFrameSelection> mFrameSelection;
346 nsWeakPtr mPresShellWeak;
347 nsRefPtr<nsAutoScrollTimer> mAutoScrollTimer;
348 nsCOMArray<nsISelectionListener> mSelectionListeners;
349 nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
350 CachedOffsetForFrame *mCachedOffsetForFrame;
351 nsDirection mDirection;
352 SelectionType mType;
355 // Stack-class to turn on/off selection batching for table selection
356 class NS_STACK_CLASS NS_FINAL_CLASS nsSelectionBatcher
358 private:
359 nsCOMPtr<nsISelectionPrivate> mSelection;
360 public:
361 nsSelectionBatcher(nsISelectionPrivate *aSelection) : mSelection(aSelection)
363 if (mSelection) mSelection->StartBatchChanges();
365 ~nsSelectionBatcher()
367 if (mSelection) mSelection->EndBatchChanges();
371 class nsSelectionIterator : public nsIBidirectionalEnumerator
373 public:
374 /*BEGIN nsIEnumerator interfaces
375 see the nsIEnumerator for more details*/
377 NS_DECL_ISUPPORTS
379 NS_DECL_NSIENUMERATOR
381 NS_DECL_NSIBIDIRECTIONALENUMERATOR
383 /*END nsIEnumerator interfaces*/
384 /*BEGIN Helper Methods*/
385 nsIRange* CurrentItem();
386 /*END Helper Methods*/
387 private:
388 friend class nsTypedSelection;
390 //lame lame lame if delete from document goes away then get rid of this unless its debug
391 friend class nsFrameSelection;
393 nsSelectionIterator(nsTypedSelection *);
394 virtual ~nsSelectionIterator();
395 PRInt32 mIndex;
396 nsTypedSelection *mDomSelection;
397 SelectionType mType;
400 class nsAutoScrollTimer : public nsITimerCallback
402 public:
404 NS_DECL_ISUPPORTS
406 nsAutoScrollTimer()
407 : mFrameSelection(0), mSelection(0), mPresContext(0), mPoint(0,0), mDelay(30)
411 virtual ~nsAutoScrollTimer()
413 if (mTimer)
414 mTimer->Cancel();
417 nsresult Start(nsPresContext *aPresContext, nsIView *aView, nsPoint &aPoint)
419 mPoint = aPoint;
421 // Store the presentation context. The timer will be
422 // stopped by the selection if the prescontext is destroyed.
423 mPresContext = aPresContext;
425 // Store the content from the nearest capturing frame. If this returns null
426 // the capturing frame is the root.
427 nsIFrame* clientFrame = static_cast<nsIFrame*>(aView->GetClientData());
428 NS_ASSERTION(clientFrame, "Missing client frame");
430 nsIFrame* capturingFrame = nsFrame::GetNearestCapturingFrame(clientFrame);
431 NS_ASSERTION(!capturingFrame || capturingFrame->GetMouseCapturer(),
432 "Capturing frame should have a mouse capturer" );
434 NS_ASSERTION(!capturingFrame || mPresContext == capturingFrame->PresContext(),
435 "Shouldn't have different pres contexts");
437 NS_ASSERTION(capturingFrame != mPresContext->PresShell()->FrameManager()->GetRootFrame(),
438 "Capturing frame should not be the root frame");
440 if (capturingFrame)
442 mContent = capturingFrame->GetContent();
443 NS_ASSERTION(mContent, "Need content");
445 NS_ASSERTION(mContent != mPresContext->PresShell()->FrameManager()->GetRootFrame()->GetContent(),
446 "We didn't want the root content!");
448 NS_ASSERTION(capturingFrame == nsFrame::GetNearestCapturingFrame(
449 mPresContext->PresShell()->GetPrimaryFrameFor(mContent)),
450 "Mapping of frame to content failed.");
453 // Check that if there was no capturing frame the content is null.
454 NS_ASSERTION(capturingFrame || !mContent, "Content not cleared correctly.");
456 if (!mTimer)
458 nsresult result;
459 mTimer = do_CreateInstance("@mozilla.org/timer;1", &result);
461 if (NS_FAILED(result))
462 return result;
465 return mTimer->InitWithCallback(this, mDelay, nsITimer::TYPE_ONE_SHOT);
468 nsresult Stop()
470 if (mTimer)
472 mTimer->Cancel();
473 mTimer = 0;
476 mContent = nsnull;
477 return NS_OK;
480 nsresult Init(nsFrameSelection *aFrameSelection, nsTypedSelection *aSelection)
482 mFrameSelection = aFrameSelection;
483 mSelection = aSelection;
484 return NS_OK;
487 nsresult SetDelay(PRUint32 aDelay)
489 mDelay = aDelay;
490 return NS_OK;
493 NS_IMETHOD Notify(nsITimer *timer)
495 if (mSelection && mPresContext)
497 // If the content is null the capturing frame must be the root frame.
498 nsIFrame* capturingFrame;
499 if (mContent)
501 nsIFrame* contentFrame = mPresContext->PresShell()->GetPrimaryFrameFor(mContent);
502 if (contentFrame)
504 capturingFrame = nsFrame::GetNearestCapturingFrame(contentFrame);
506 else
508 capturingFrame = nsnull;
510 NS_ASSERTION(!capturingFrame || capturingFrame->GetMouseCapturer(),
511 "Capturing frame should have a mouse capturer" );
513 else
515 capturingFrame = mPresContext->PresShell()->FrameManager()->GetRootFrame();
518 // Clear the content reference now that the frame has been found.
519 mContent = nsnull;
521 // This could happen for a frame with style changed to display:none or a frame
522 // that was destroyed.
523 if (!capturingFrame) {
524 NS_WARNING("Frame destroyed or set to display:none before scroll timer fired.");
525 return NS_OK;
528 nsIView* captureView = capturingFrame->GetMouseCapturer();
530 nsWeakFrame viewFrame = static_cast<nsIFrame*>(captureView->GetClientData());
531 NS_ASSERTION(viewFrame.GetFrame(), "View must have a client frame");
533 mFrameSelection->HandleDrag(viewFrame, mPoint);
535 mSelection->DoAutoScrollView(mPresContext,
536 viewFrame.IsAlive() ? captureView : nsnull,
537 mPoint, PR_TRUE);
539 return NS_OK;
541 private:
542 nsFrameSelection *mFrameSelection;
543 nsTypedSelection *mSelection;
544 nsPresContext *mPresContext;
545 nsPoint mPoint;
546 nsCOMPtr<nsITimer> mTimer;
547 nsCOMPtr<nsIContent> mContent;
548 PRUint32 mDelay;
551 NS_IMPL_ISUPPORTS1(nsAutoScrollTimer, nsITimerCallback)
553 nsresult NS_NewSelection(nsFrameSelection **aFrameSelection)
555 nsFrameSelection *rlist = new nsFrameSelection;
556 if (!rlist)
557 return NS_ERROR_OUT_OF_MEMORY;
558 *aFrameSelection = rlist;
559 NS_ADDREF(rlist);
560 return NS_OK;
563 nsresult NS_NewDomSelection(nsISelection **aDomSelection)
565 nsTypedSelection *rlist = new nsTypedSelection;
566 if (!rlist)
567 return NS_ERROR_OUT_OF_MEMORY;
568 *aDomSelection = (nsISelection *)rlist;
569 NS_ADDREF(rlist);
570 return NS_OK;
573 static PRInt8
574 GetIndexFromSelectionType(SelectionType aType)
576 switch (aType)
578 case nsISelectionController::SELECTION_NORMAL: return 0; break;
579 case nsISelectionController::SELECTION_SPELLCHECK: return 1; break;
580 case nsISelectionController::SELECTION_IME_RAWINPUT: return 2; break;
581 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: return 3; break;
582 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: return 4; break;
583 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: return 5; break;
584 case nsISelectionController::SELECTION_ACCESSIBILITY: return 6; break;
585 case nsISelectionController::SELECTION_FIND: return 7; break;
586 default:
587 return -1; break;
589 /* NOTREACHED */
590 return 0;
593 static SelectionType
594 GetSelectionTypeFromIndex(PRInt8 aIndex)
596 switch (aIndex)
598 case 0: return nsISelectionController::SELECTION_NORMAL; break;
599 case 1: return nsISelectionController::SELECTION_SPELLCHECK; break;
600 case 2: return nsISelectionController::SELECTION_IME_RAWINPUT; break;
601 case 3: return nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT; break;
602 case 4: return nsISelectionController::SELECTION_IME_CONVERTEDTEXT; break;
603 case 5: return nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; break;
604 case 6: return nsISelectionController::SELECTION_ACCESSIBILITY; break;
605 case 7: return nsISelectionController::SELECTION_FIND; break;
606 default:
607 return nsISelectionController::SELECTION_NORMAL; break;
609 /* NOTREACHED */
610 return 0;
614 The limiter is used specifically for the text areas and textfields
615 In that case it is the DIV tag that is anonymously created for the text
616 areas/fields. Text nodes and BR nodes fall beneath it. In the case of a
617 BR node the limiter will be the parent and the offset will point before or
618 after the BR node. In the case of the text node the parent content is
619 the text node itself and the offset will be the exact character position.
620 The offset is not important to check for validity. Simply look at the
621 passed in content. If it equals the limiter then the selection point is valid.
622 If its parent it the limiter then the point is also valid. In the case of
623 NO limiter all points are valid since you are in a topmost iframe. (browser
624 or composer)
626 PRBool
627 IsValidSelectionPoint(nsFrameSelection *aFrameSel, nsINode *aNode)
629 if (!aFrameSel || !aNode)
630 return PR_FALSE;
632 nsIContent *limiter = aFrameSel->GetLimiter();
633 if (limiter && limiter != aNode && limiter != aNode->GetParent()) {
634 //if newfocus == the limiter. that's ok. but if not there and not parent bad
635 return PR_FALSE; //not in the right content. tLimiter said so
638 limiter = aFrameSel->GetAncestorLimiter();
639 return !limiter || nsContentUtils::ContentIsDescendantOf(aNode, limiter);
643 NS_IMPL_ADDREF(nsSelectionIterator)
644 NS_IMPL_RELEASE(nsSelectionIterator)
646 NS_INTERFACE_MAP_BEGIN(nsSelectionIterator)
647 NS_INTERFACE_MAP_ENTRY(nsIEnumerator)
648 NS_INTERFACE_MAP_ENTRY(nsIBidirectionalEnumerator)
649 NS_INTERFACE_MAP_END_AGGREGATED(mDomSelection)
652 ///////////BEGIN nsSelectionIterator methods
654 nsSelectionIterator::nsSelectionIterator(nsTypedSelection *aList)
655 :mIndex(0)
657 if (!aList)
659 NS_NOTREACHED("nsFrameSelection");
660 return;
662 mDomSelection = aList;
667 nsSelectionIterator::~nsSelectionIterator()
673 ////////////END nsSelectionIterator methods
675 ////////////BEGIN nsSelectionIterator methods
679 NS_IMETHODIMP
680 nsSelectionIterator::Next()
682 mIndex++;
683 PRInt32 cnt = mDomSelection->mRanges.Length();
684 if (mIndex < cnt)
685 return NS_OK;
686 return NS_ERROR_FAILURE;
691 NS_IMETHODIMP
692 nsSelectionIterator::Prev()
694 mIndex--;
695 if (mIndex >= 0 )
696 return NS_OK;
697 return NS_ERROR_FAILURE;
702 NS_IMETHODIMP
703 nsSelectionIterator::First()
705 if (!mDomSelection)
706 return NS_ERROR_NULL_POINTER;
707 mIndex = 0;
708 return NS_OK;
713 NS_IMETHODIMP
714 nsSelectionIterator::Last()
716 if (!mDomSelection)
717 return NS_ERROR_NULL_POINTER;
718 mIndex = mDomSelection->mRanges.Length() - 1;
719 return NS_OK;
724 NS_IMETHODIMP
725 nsSelectionIterator::CurrentItem(nsISupports **aItem)
727 *aItem = CurrentItem();
728 if (!*aItem) {
729 return NS_ERROR_FAILURE;
732 NS_ADDREF(*aItem);
733 return NS_OK;
736 nsIRange*
737 nsSelectionIterator::CurrentItem()
739 return mDomSelection->mRanges.SafeElementAt(mIndex, sEmptyData).mRange;
744 NS_IMETHODIMP
745 nsSelectionIterator::IsDone()
747 PRInt32 cnt = mDomSelection->mRanges.Length();
748 if (mIndex >= 0 && mIndex < cnt) {
749 return NS_ENUMERATOR_FALSE;
751 return NS_OK;
755 ////////////END nsSelectionIterator methods
758 ////////////BEGIN nsFrameSelection methods
760 nsFrameSelection::nsFrameSelection()
761 : mScrollableViewProvider(nsnull),
762 mDelayedMouseEvent(PR_FALSE, 0, nsnull, nsMouseEvent::eReal)
764 PRInt32 i;
765 for (i = 0;i<nsISelectionController::NUM_SELECTIONTYPES;i++){
766 mDomSelections[i] = new nsTypedSelection(this);
767 if (!mDomSelections[i])
768 break;
769 mDomSelections[i]->SetType(GetSelectionTypeFromIndex(i));
771 mBatching = 0;
772 mChangesDuringBatching = PR_FALSE;
773 mNotifyFrames = PR_TRUE;
774 mLimiter = nsnull; //no default limiter.
775 mAncestorLimiter = nsnull;
777 mMouseDoubleDownState = PR_FALSE;
779 mHint = HINTLEFT;
780 #ifdef IBMBIDI
781 mCaretBidiLevel = BIDI_LEVEL_UNDEFINED;
782 #endif
783 mDragSelectingCells = PR_FALSE;
784 mSelectingTableCellMode = 0;
785 mSelectedCellIndex = 0;
787 // Check to see if the autocopy pref is enabled
788 // and add the autocopy listener if it is
789 if (nsContentUtils::GetBoolPref("clipboard.autocopy")) {
790 nsAutoCopyListener *autoCopy = nsAutoCopyListener::GetInstance();
792 if (autoCopy) {
793 PRInt8 index =
794 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
795 if (mDomSelections[index]) {
796 autoCopy->Listen(mDomSelections[index]);
801 mDisplaySelection = nsISelectionController::SELECTION_OFF;
803 mDelayedMouseEventValid = PR_FALSE;
804 mSelectionChangeReason = nsISelectionListener::NO_REASON;
808 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameSelection)
809 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameSelection)
810 PRInt32 i;
811 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
812 tmp->mDomSelections[i] = nsnull;
815 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCellParent)
816 tmp->mSelectingTableCellMode = 0;
817 tmp->mDragSelectingCells = PR_FALSE;
818 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStartSelectedCell)
819 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEndSelectedCell)
820 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mAppendStartSelectedCell)
821 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUnselectCellOnMouseUp)
822 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMaintainRange)
823 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
824 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameSelection)
825 PRInt32 i;
826 for (i = 0; i < nsISelectionController::NUM_SELECTIONTYPES; ++i) {
827 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mDomSelections[i],
828 nsISelection)
831 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCellParent)
832 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mStartSelectedCell)
833 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEndSelectedCell)
834 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAppendStartSelectedCell)
835 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mUnselectCellOnMouseUp)
836 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mMaintainRange)
837 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
839 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameSelection)
840 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameSelection)
841 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameSelection)
842 NS_INTERFACE_MAP_ENTRY(nsFrameSelection)
843 NS_INTERFACE_MAP_ENTRY(nsISupports)
844 NS_INTERFACE_MAP_END
847 nsresult
848 nsFrameSelection::FetchDesiredX(nscoord &aDesiredX) //the x position requested by the Key Handling for up down
850 if (!mShell)
852 NS_ERROR("fetch desired X failed\n");
853 return NS_ERROR_FAILURE;
855 if (mDesiredXSet)
857 aDesiredX = mDesiredX;
858 return NS_OK;
861 nsRefPtr<nsCaret> caret;
862 nsresult result = mShell->GetCaret(getter_AddRefs(caret));
863 if (NS_FAILED(result))
864 return result;
865 if (!caret)
866 return NS_ERROR_NULL_POINTER;
868 nsRect coord;
869 PRBool collapsed;
870 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
871 result = caret->SetCaretDOMSelection(mDomSelections[index]);
872 if (NS_FAILED(result))
873 return result;
875 result = caret->GetCaretCoordinates(nsCaret::eClosestViewCoordinates, mDomSelections[index], &coord, &collapsed, nsnull);
876 if (NS_FAILED(result))
877 return result;
879 aDesiredX = coord.x;
880 return NS_OK;
885 void
886 nsFrameSelection::InvalidateDesiredX() //do not listen to mDesiredX you must get another.
888 mDesiredXSet = PR_FALSE;
893 void
894 nsFrameSelection::SetDesiredX(nscoord aX) //set the mDesiredX
896 mDesiredX = aX;
897 mDesiredXSet = PR_TRUE;
900 nsresult
901 nsFrameSelection::GetRootForContentSubtree(nsIContent *aContent,
902 nsIContent **aParent)
904 // This method returns the root of the sub-tree containing aContent.
905 // We do this by searching up through the parent hierarchy, and stopping
906 // when there are no more parents, or we hit a situation where the
907 // parent/child relationship becomes invalid.
909 // An example of an invalid parent/child relationship is anonymous content.
910 // Anonymous content has a pointer to its parent, but it is not listed
911 // as a child of its parent. In this case, the anonymous content would
912 // be considered the root of the subtree.
914 if (!aContent || !aParent)
915 return NS_ERROR_NULL_POINTER;
917 *aParent = 0;
919 nsIContent* child = aContent;
921 while (child)
923 nsIContent* parent = child->GetParent();
925 if (!parent)
926 break;
928 PRUint32 childCount = parent->GetChildCount();
930 if (childCount < 1)
931 break;
933 PRInt32 childIndex = parent->IndexOf(child);
935 if (childIndex < 0 || ((PRUint32)childIndex) >= childCount)
936 break;
938 child = parent;
941 NS_IF_ADDREF(*aParent = child);
943 return NS_OK;
946 nsresult
947 nsFrameSelection::ConstrainFrameAndPointToAnchorSubtree(nsIFrame *aFrame,
948 nsPoint& aPoint,
949 nsIFrame **aRetFrame,
950 nsPoint& aRetPoint)
953 // The whole point of this method is to return a frame and point that
954 // that lie within the same valid subtree as the anchor node's frame,
955 // for use with the method GetContentAndOffsetsFromPoint().
957 // A valid subtree is defined to be one where all the content nodes in
958 // the tree have a valid parent-child relationship.
960 // If the anchor frame and aFrame are in the same subtree, aFrame will
961 // be returned in aRetFrame. If they are in different subtrees, we
962 // return the frame for the root of the subtree.
965 if (!aFrame || !aRetFrame)
966 return NS_ERROR_NULL_POINTER;
968 *aRetFrame = aFrame;
969 aRetPoint = aPoint;
972 // Get the frame and content for the selection's anchor point!
975 nsresult result;
976 nsCOMPtr<nsIDOMNode> anchorNode;
977 PRInt32 anchorOffset = 0;
979 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
980 if (!mDomSelections[index])
981 return NS_ERROR_NULL_POINTER;
983 result = mDomSelections[index]->GetAnchorNode(getter_AddRefs(anchorNode));
985 if (NS_FAILED(result))
986 return result;
988 if (!anchorNode)
989 return NS_OK;
991 result = mDomSelections[index]->GetAnchorOffset(&anchorOffset);
993 if (NS_FAILED(result))
994 return result;
996 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode);
998 if (!anchorContent)
999 return NS_ERROR_FAILURE;
1002 // Now find the root of the subtree containing the anchor's content.
1005 nsCOMPtr<nsIContent> anchorRoot;
1006 result = GetRootForContentSubtree(anchorContent, getter_AddRefs(anchorRoot));
1008 if (NS_FAILED(result))
1009 return result;
1012 // Now find the root of the subtree containing aFrame's content.
1015 nsIContent* content = aFrame->GetContent();
1017 if (content)
1019 nsCOMPtr<nsIContent> contentRoot;
1021 result = GetRootForContentSubtree(content, getter_AddRefs(contentRoot));
1023 if (anchorRoot == contentRoot)
1026 // The anchor and AFrame's root are the same. There
1027 // is no need to constrain, simply return aFrame.
1029 *aRetFrame = aFrame;
1030 return NS_OK;
1035 // aFrame's root does not match the anchor's root, or there is no
1036 // content associated with aFrame. Just return the primary frame
1037 // for the anchor's root. We'll let GetContentAndOffsetsFromPoint()
1038 // find the closest frame aPoint.
1041 NS_ENSURE_STATE(mShell);
1042 *aRetFrame = mShell->GetPrimaryFrameFor(anchorRoot);
1044 if (!*aRetFrame)
1045 return NS_ERROR_FAILURE;
1048 // Now make sure that aRetPoint is converted to the same coordinate
1049 // system used by aRetFrame.
1052 aRetPoint = aPoint + aFrame->GetOffsetTo(*aRetFrame);
1054 return NS_OK;
1057 #ifdef IBMBIDI
1058 void
1059 nsFrameSelection::SetCaretBidiLevel(PRUint8 aLevel)
1061 // If the current level is undefined, we have just inserted new text.
1062 // In this case, we don't want to reset the keyboard language
1063 PRBool afterInsert = !!(mCaretBidiLevel & BIDI_LEVEL_UNDEFINED);
1064 mCaretBidiLevel = aLevel;
1066 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
1067 if (bidiKeyboard && !afterInsert)
1068 bidiKeyboard->SetLangFromBidiLevel(aLevel);
1069 return;
1072 PRUint8
1073 nsFrameSelection::GetCaretBidiLevel() const
1075 return mCaretBidiLevel;
1078 void
1079 nsFrameSelection::UndefineCaretBidiLevel()
1081 mCaretBidiLevel |= BIDI_LEVEL_UNDEFINED;
1083 #endif
1086 #ifdef PRINT_RANGE
1087 void printRange(nsIRange *aDomRange)
1089 if (!aDomRange)
1091 printf("NULL nsIDOMRange\n");
1093 nsINode* startNode = aDomRange->GetStartParent();
1094 nsINode* endNode = aDomRange->GetEndParent();
1095 PRInt32 startOffset = aDomRange->StartOffset();
1096 PRInt32 endOffset = aDomRange->EndOffset();
1098 printf("range: 0x%lx\t start: 0x%lx %ld, \t end: 0x%lx,%ld\n",
1099 (unsigned long)aDomRange,
1100 (unsigned long)startNode, (long)startOffset,
1101 (unsigned long)endNode, (long)endOffset);
1104 #endif /* PRINT_RANGE */
1106 static
1107 nsIAtom *GetTag(nsINode *aNode)
1109 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
1110 if (!content)
1112 NS_NOTREACHED("bad node passed to GetTag()");
1113 return nsnull;
1116 return content->Tag();
1119 // Returns the parent
1120 nsINode*
1121 ParentOffset(nsINode *aNode, PRInt32 *aChildOffset)
1123 if (!aNode || !aChildOffset)
1124 return nsnull;
1126 nsIContent* parent = aNode->GetParent();
1127 if (parent)
1129 *aChildOffset = parent->IndexOf(aNode);
1131 return parent;
1134 return nsnull;
1137 static nsINode*
1138 GetCellParent(nsINode *aDomNode)
1140 if (!aDomNode)
1141 return nsnull;
1142 nsINode* current = aDomNode;
1143 // Start with current node and look for a table cell
1144 while (current)
1146 nsIAtom* tag = GetTag(current);
1147 if (tag == nsGkAtoms::td || tag == nsGkAtoms::th)
1148 return current;
1149 current = current->GetParent();
1151 return nsnull;
1155 void
1156 nsFrameSelection::Init(nsIPresShell *aShell, nsIContent *aLimiter)
1158 mShell = aShell;
1159 mMouseDownState = PR_FALSE;
1160 mDesiredXSet = PR_FALSE;
1161 mLimiter = aLimiter;
1162 mCaretMovementStyle = nsContentUtils::GetIntPref("bidi.edit.caret_movement_style", 2);
1165 nsresult
1166 nsFrameSelection::MoveCaret(PRUint32 aKeycode,
1167 PRBool aContinueSelection,
1168 nsSelectionAmount aAmount)
1170 NS_ENSURE_STATE(mShell);
1171 // Flush out layout, since we need it to be up to date to do caret
1172 // positioning.
1173 mShell->FlushPendingNotifications(Flush_Layout);
1175 if (!mShell) {
1176 return NS_OK;
1179 nsPresContext *context = mShell->GetPresContext();
1180 if (!context)
1181 return NS_ERROR_FAILURE;
1183 PRBool isCollapsed;
1184 nscoord desiredX = 0; //we must keep this around and revalidate it when its just UP/DOWN
1186 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1187 nsRefPtr<nsTypedSelection> sel = mDomSelections[index];
1188 if (!sel)
1189 return NS_ERROR_NULL_POINTER;
1191 nsresult result = sel->GetIsCollapsed(&isCollapsed);
1192 if (NS_FAILED(result))
1193 return result;
1194 if (aKeycode == nsIDOMKeyEvent::DOM_VK_UP ||
1195 aKeycode == nsIDOMKeyEvent::DOM_VK_DOWN)
1197 result = FetchDesiredX(desiredX);
1198 if (NS_FAILED(result))
1199 return result;
1200 SetDesiredX(desiredX);
1203 PRInt32 caretStyle = nsContentUtils::GetIntPref("layout.selection.caret_style", 0);
1204 #ifdef XP_MACOSX
1205 if (caretStyle == 0) {
1206 caretStyle = 2; // put caret at the selection edge in the |aKeycode| direction
1208 #endif
1210 if (!isCollapsed && !aContinueSelection && caretStyle == 2) {
1211 switch (aKeycode){
1212 case nsIDOMKeyEvent::DOM_VK_LEFT :
1213 case nsIDOMKeyEvent::DOM_VK_UP :
1215 const nsIRange* anchorFocusRange = sel->GetAnchorFocusRange();
1216 if (anchorFocusRange) {
1217 sel->Collapse(anchorFocusRange->GetStartParent(),
1218 anchorFocusRange->StartOffset());
1220 mHint = HINTRIGHT;
1221 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1222 PR_FALSE, PR_FALSE);
1223 return NS_OK;
1226 case nsIDOMKeyEvent::DOM_VK_RIGHT :
1227 case nsIDOMKeyEvent::DOM_VK_DOWN :
1229 const nsIRange* anchorFocusRange = sel->GetAnchorFocusRange();
1230 if (anchorFocusRange) {
1231 sel->Collapse(anchorFocusRange->GetEndParent(),
1232 anchorFocusRange->EndOffset());
1234 mHint = HINTLEFT;
1235 sel->ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1236 PR_FALSE, PR_FALSE);
1237 return NS_OK;
1242 PRBool visualMovement =
1243 (aKeycode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE ||
1244 aKeycode == nsIDOMKeyEvent::DOM_VK_DELETE ||
1245 aKeycode == nsIDOMKeyEvent::DOM_VK_HOME ||
1246 aKeycode == nsIDOMKeyEvent::DOM_VK_END) ?
1247 PR_FALSE : // Delete operations and home/end are always logical
1248 mCaretMovementStyle == 1 || (mCaretMovementStyle == 2 && !aContinueSelection);
1250 nsIFrame *frame;
1251 PRInt32 offsetused = 0;
1252 result = sel->GetPrimaryFrameForFocusNode(&frame, &offsetused, visualMovement);
1254 if (NS_FAILED(result) || !frame)
1255 return result?result:NS_ERROR_FAILURE;
1257 nsPeekOffsetStruct pos;
1258 //set data using mLimiter to stop on scroll views. If we have a limiter then we stop peeking
1259 //when we hit scrollable views. If no limiter then just let it go ahead
1260 pos.SetData(aAmount, eDirPrevious, offsetused, desiredX,
1261 PR_TRUE, mLimiter != nsnull, PR_TRUE, visualMovement);
1263 nsBidiLevel baseLevel = nsBidiPresUtils::GetFrameBaseLevel(frame);
1265 HINT tHint(mHint); //temporary variable so we dont set mHint until it is necessary
1266 switch (aKeycode){
1267 case nsIDOMKeyEvent::DOM_VK_RIGHT :
1268 InvalidateDesiredX();
1269 pos.mDirection = (baseLevel & 1) ? eDirPrevious : eDirNext;
1270 break;
1271 case nsIDOMKeyEvent::DOM_VK_LEFT :
1272 InvalidateDesiredX();
1273 pos.mDirection = (baseLevel & 1) ? eDirNext : eDirPrevious;
1274 break;
1275 case nsIDOMKeyEvent::DOM_VK_DELETE :
1276 InvalidateDesiredX();
1277 pos.mDirection = eDirNext;
1278 break;
1279 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE :
1280 InvalidateDesiredX();
1281 pos.mDirection = eDirPrevious;
1282 break;
1283 case nsIDOMKeyEvent::DOM_VK_DOWN :
1284 pos.mAmount = eSelectLine;
1285 pos.mDirection = eDirNext;
1286 break;
1287 case nsIDOMKeyEvent::DOM_VK_UP :
1288 pos.mAmount = eSelectLine;
1289 pos.mDirection = eDirPrevious;
1290 break;
1291 case nsIDOMKeyEvent::DOM_VK_HOME :
1292 InvalidateDesiredX();
1293 pos.mAmount = eSelectBeginLine;
1294 break;
1295 case nsIDOMKeyEvent::DOM_VK_END :
1296 InvalidateDesiredX();
1297 pos.mAmount = eSelectEndLine;
1298 break;
1299 default :return NS_ERROR_FAILURE;
1301 PostReason(nsISelectionListener::KEYPRESS_REASON);
1302 if (NS_SUCCEEDED(result = frame->PeekOffset(&pos)) && pos.mResultContent)
1304 nsIFrame *theFrame;
1305 PRInt32 currentOffset, frameStart, frameEnd;
1307 if (aAmount == eSelectCharacter || aAmount == eSelectWord)
1309 // For left/right, PeekOffset() sets pos.mResultFrame correctly, but does not set pos.mAttachForward,
1310 // so determine the hint here based on the result frame and offset:
1311 // If we're at the end of a text frame, set the hint to HINTLEFT to indicate that we
1312 // want the caret displayed at the end of this frame, not at the beginning of the next one.
1313 theFrame = pos.mResultFrame;
1314 theFrame->GetOffsets(frameStart, frameEnd);
1315 currentOffset = pos.mContentOffset;
1316 if (frameEnd == currentOffset && !(frameStart == 0 && frameEnd == 0))
1317 tHint = HINTLEFT;
1318 else
1319 tHint = HINTRIGHT;
1320 } else {
1321 // For up/down and home/end, pos.mResultFrame might not be set correctly, or not at all.
1322 // In these cases, get the frame based on the content and hint returned by PeekOffset().
1323 tHint = (HINT)pos.mAttachForward;
1324 theFrame = GetFrameForNodeOffset(pos.mResultContent, pos.mContentOffset,
1325 tHint, &currentOffset);
1326 if (!theFrame)
1327 return NS_ERROR_FAILURE;
1329 theFrame->GetOffsets(frameStart, frameEnd);
1332 if (context->BidiEnabled())
1334 switch (aKeycode) {
1335 case nsIDOMKeyEvent::DOM_VK_HOME:
1336 case nsIDOMKeyEvent::DOM_VK_END:
1337 // set the caret Bidi level to the paragraph embedding level
1338 SetCaretBidiLevel(NS_GET_BASE_LEVEL(theFrame));
1339 break;
1341 default:
1342 // If the current position is not a frame boundary, it's enough just to take the Bidi level of the current frame
1343 if ((pos.mContentOffset != frameStart && pos.mContentOffset != frameEnd)
1344 || (eSelectLine == aAmount))
1346 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
1348 else
1349 BidiLevelFromMove(mShell, pos.mResultContent, pos.mContentOffset, aKeycode, tHint);
1352 result = TakeFocus(pos.mResultContent, pos.mContentOffset, pos.mContentOffset,
1353 tHint, aContinueSelection, PR_FALSE);
1354 } else if (aKeycode == nsIDOMKeyEvent::DOM_VK_RIGHT && !aContinueSelection) {
1355 // Collapse selection if PeekOffset failed, we either
1356 // 1. bumped into the BRFrame, bug 207623
1357 // 2. had select-all in a text input (DIV range), bug 352759.
1358 PRBool isBRFrame = frame->GetType() == nsGkAtoms::brFrame;
1359 sel->Collapse(sel->GetFocusNode(), sel->GetFocusOffset());
1360 // Note: 'frame' might be dead here.
1361 if (!isBRFrame) {
1362 mHint = HINTLEFT; // We're now at the end of the frame to the left.
1364 result = NS_OK;
1366 if (NS_SUCCEEDED(result))
1368 result = mDomSelections[index]->
1369 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION,
1370 PR_FALSE, PR_FALSE);
1373 return result;
1376 //END nsFrameSelection methods
1379 //BEGIN nsFrameSelection methods
1381 NS_IMETHODIMP
1382 nsTypedSelection::ToString(PRUnichar **aReturn)
1384 return ToStringWithFormat("text/plain", 0, 0, aReturn);
1388 NS_IMETHODIMP
1389 nsTypedSelection::ToStringWithFormat(const char * aFormatType, PRUint32 aFlags,
1390 PRInt32 aWrapCol, PRUnichar **aReturn)
1392 nsresult rv = NS_OK;
1393 if (!aReturn)
1394 return NS_ERROR_NULL_POINTER;
1396 nsCAutoString formatType( NS_DOC_ENCODER_CONTRACTID_BASE );
1397 formatType.Append(aFormatType);
1398 nsCOMPtr<nsIDocumentEncoder> encoder =
1399 do_CreateInstance(formatType.get(), &rv);
1400 NS_ENSURE_SUCCESS(rv, rv);
1402 nsCOMPtr<nsIPresShell> shell;
1403 rv = GetPresShell(getter_AddRefs(shell));
1404 if (NS_FAILED(rv) || !shell) {
1405 return NS_ERROR_FAILURE;
1408 nsIDocument *doc = shell->GetDocument();
1409 NS_ENSURE_SUCCESS(rv, rv);
1411 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
1412 NS_ASSERTION(domDoc, "Need a document");
1414 // Flags should always include OutputSelectionOnly if we're coming from here:
1415 aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1416 nsAutoString readstring;
1417 readstring.AssignASCII(aFormatType);
1418 rv = encoder->Init(domDoc, readstring, aFlags);
1419 NS_ENSURE_SUCCESS(rv, rv);
1421 encoder->SetSelection(this);
1422 if (aWrapCol != 0)
1423 encoder->SetWrapColumn(aWrapCol);
1425 nsAutoString tmp;
1426 rv = encoder->EncodeToString(tmp);
1427 *aReturn = ToNewUnicode(tmp);//get the unicode pointer from it. this is temporary
1428 return rv;
1431 NS_IMETHODIMP
1432 nsTypedSelection::SetInterlinePosition(PRBool aHintRight)
1434 if (!mFrameSelection)
1435 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
1436 nsFrameSelection::HINT hint;
1437 if (aHintRight)
1438 hint = nsFrameSelection::HINTRIGHT;
1439 else
1440 hint = nsFrameSelection::HINTLEFT;
1441 mFrameSelection->SetHint(hint);
1443 return NS_OK;
1446 NS_IMETHODIMP
1447 nsTypedSelection::GetInterlinePosition(PRBool *aHintRight)
1449 if (!mFrameSelection)
1450 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
1451 *aHintRight = (mFrameSelection->GetHint() == nsFrameSelection::HINTRIGHT);
1452 return NS_OK;
1455 nsPrevNextBidiLevels
1456 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1457 PRUint32 aContentOffset,
1458 PRBool aJumpLines) const
1460 return GetPrevNextBidiLevels(aNode, aContentOffset, mHint, aJumpLines);
1463 nsPrevNextBidiLevels
1464 nsFrameSelection::GetPrevNextBidiLevels(nsIContent *aNode,
1465 PRUint32 aContentOffset,
1466 HINT aHint,
1467 PRBool aJumpLines) const
1469 // Get the level of the frames on each side
1470 nsIFrame *currentFrame;
1471 PRInt32 currentOffset;
1472 PRInt32 frameStart, frameEnd;
1473 nsDirection direction;
1475 nsPrevNextBidiLevels levels;
1476 levels.SetData(nsnull, nsnull, 0, 0);
1478 currentFrame = GetFrameForNodeOffset(aNode, aContentOffset,
1479 aHint, &currentOffset);
1480 if (!currentFrame)
1481 return levels;
1483 currentFrame->GetOffsets(frameStart, frameEnd);
1485 if (0 == frameStart && 0 == frameEnd)
1486 direction = eDirPrevious;
1487 else if (frameStart == currentOffset)
1488 direction = eDirPrevious;
1489 else if (frameEnd == currentOffset)
1490 direction = eDirNext;
1491 else {
1492 // we are neither at the beginning nor at the end of the frame, so we have no worries
1493 levels.SetData(currentFrame, currentFrame,
1494 NS_GET_EMBEDDING_LEVEL(currentFrame),
1495 NS_GET_EMBEDDING_LEVEL(currentFrame));
1496 return levels;
1499 nsIFrame *newFrame;
1500 PRInt32 offset;
1501 PRBool jumpedLine;
1502 nsresult rv = currentFrame->GetFrameFromDirection(direction, PR_FALSE,
1503 aJumpLines, PR_TRUE,
1504 &newFrame, &offset, &jumpedLine);
1505 if (NS_FAILED(rv))
1506 newFrame = nsnull;
1508 PRUint8 baseLevel = NS_GET_BASE_LEVEL(currentFrame);
1509 PRUint8 currentLevel = NS_GET_EMBEDDING_LEVEL(currentFrame);
1510 PRUint8 newLevel = newFrame ? NS_GET_EMBEDDING_LEVEL(newFrame) : baseLevel;
1512 // If not jumping lines, disregard br frames, since they might be positioned incorrectly.
1513 // XXX This could be removed once bug 339786 is fixed.
1514 if (!aJumpLines) {
1515 if (currentFrame->GetType() == nsGkAtoms::brFrame) {
1516 currentFrame = nsnull;
1517 currentLevel = baseLevel;
1519 if (newFrame && newFrame->GetType() == nsGkAtoms::brFrame) {
1520 newFrame = nsnull;
1521 newLevel = baseLevel;
1525 if (direction == eDirNext)
1526 levels.SetData(currentFrame, newFrame, currentLevel, newLevel);
1527 else
1528 levels.SetData(newFrame, currentFrame, newLevel, currentLevel);
1530 return levels;
1533 nsresult
1534 nsFrameSelection::GetFrameFromLevel(nsIFrame *aFrameIn,
1535 nsDirection aDirection,
1536 PRUint8 aBidiLevel,
1537 nsIFrame **aFrameOut) const
1539 NS_ENSURE_STATE(mShell);
1540 PRUint8 foundLevel = 0;
1541 nsIFrame *foundFrame = aFrameIn;
1543 nsCOMPtr<nsIFrameEnumerator> frameTraversal;
1544 nsresult result;
1545 nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
1546 if (NS_FAILED(result))
1547 return result;
1549 result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
1550 mShell->GetPresContext(), aFrameIn,
1551 eLeaf,
1552 PR_FALSE, // aVisual
1553 PR_FALSE, // aLockInScrollView
1554 PR_FALSE // aFollowOOFs
1556 if (NS_FAILED(result))
1557 return result;
1559 do {
1560 *aFrameOut = foundFrame;
1561 if (aDirection == eDirNext)
1562 frameTraversal->Next();
1563 else
1564 frameTraversal->Prev();
1566 foundFrame = frameTraversal->CurrentItem();
1567 if (!foundFrame)
1568 return NS_ERROR_FAILURE;
1569 foundLevel = NS_GET_EMBEDDING_LEVEL(foundFrame);
1571 } while (foundLevel > aBidiLevel);
1573 return NS_OK;
1577 nsresult
1578 nsFrameSelection::MaintainSelection(nsSelectionAmount aAmount)
1580 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1581 if (!mDomSelections[index])
1582 return NS_ERROR_NULL_POINTER;
1584 mMaintainedAmount = aAmount;
1586 const nsIRange* anchorFocusRange =
1587 mDomSelections[index]->GetAnchorFocusRange();
1588 if (anchorFocusRange) {
1589 return anchorFocusRange->CloneRange(getter_AddRefs(mMaintainRange));
1592 mMaintainRange = nsnull;
1593 return NS_OK;
1597 /** After moving the caret, its Bidi level is set according to the following rules:
1599 * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character.
1600 * After Home and End, set to the paragraph embedding level.
1601 * After up/down arrow, PageUp/Down, set to the lower level of the 2 surrounding characters.
1602 * After mouse click, set to the level of the current frame.
1604 * The following two methods use GetPrevNextBidiLevels to determine the new Bidi level.
1605 * BidiLevelFromMove is called when the caret is moved in response to a keyboard event
1607 * @param aPresShell is the presentation shell
1608 * @param aNode is the content node
1609 * @param aContentOffset is the new caret position, as an offset into aNode
1610 * @param aKeycode is the keyboard event that moved the caret to the new position
1611 * @param aHint is the hint indicating in what logical direction the caret moved
1613 void nsFrameSelection::BidiLevelFromMove(nsIPresShell* aPresShell,
1614 nsIContent *aNode,
1615 PRUint32 aContentOffset,
1616 PRUint32 aKeycode,
1617 HINT aHint)
1619 switch (aKeycode) {
1621 // Right and Left: the new cursor Bidi level is the level of the character moved over
1622 case nsIDOMKeyEvent::DOM_VK_RIGHT:
1623 case nsIDOMKeyEvent::DOM_VK_LEFT:
1625 nsPrevNextBidiLevels levels = GetPrevNextBidiLevels(aNode, aContentOffset,
1626 aHint, PR_FALSE);
1628 if (HINTLEFT == aHint)
1629 SetCaretBidiLevel(levels.mLevelBefore);
1630 else
1631 SetCaretBidiLevel(levels.mLevelAfter);
1632 break;
1635 // Up and Down: the new cursor Bidi level is the smaller of the two surrounding characters
1636 case nsIDOMKeyEvent::DOM_VK_UP:
1637 case nsIDOMKeyEvent::DOM_VK_DOWN:
1638 GetPrevNextBidiLevels(aContext, aNode, aContentOffset, &firstFrame, &secondFrame, &firstLevel, &secondLevel);
1639 aPresShell->SetCaretBidiLevel(PR_MIN(firstLevel, secondLevel));
1640 break;
1643 default:
1644 UndefineCaretBidiLevel();
1649 * BidiLevelFromClick is called when the caret is repositioned by clicking the mouse
1651 * @param aNode is the content node
1652 * @param aContentOffset is the new caret position, as an offset into aNode
1654 void nsFrameSelection::BidiLevelFromClick(nsIContent *aNode,
1655 PRUint32 aContentOffset)
1657 nsIFrame* clickInFrame=nsnull;
1658 PRInt32 OffsetNotUsed;
1660 clickInFrame = GetFrameForNodeOffset(aNode, aContentOffset, mHint, &OffsetNotUsed);
1661 if (!clickInFrame)
1662 return;
1664 SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(clickInFrame));
1668 PRBool
1669 nsFrameSelection::AdjustForMaintainedSelection(nsIContent *aContent,
1670 PRInt32 aOffset)
1672 if (!mMaintainRange)
1673 return PR_FALSE;
1675 if (!aContent) {
1676 return PR_FALSE;
1679 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1680 if (!mDomSelections[index])
1681 return PR_FALSE;
1683 nsINode* rangeStartNode = mMaintainRange->GetStartParent();
1684 nsINode* rangeEndNode = mMaintainRange->GetEndParent();
1685 PRInt32 rangeStartOffset = mMaintainRange->StartOffset();
1686 PRInt32 rangeEndOffset = mMaintainRange->EndOffset();
1688 PRInt32 relToStart =
1689 nsContentUtils::ComparePoints(rangeStartNode, rangeStartOffset,
1690 aContent, aOffset);
1691 PRInt32 relToEnd =
1692 nsContentUtils::ComparePoints(rangeEndNode, rangeEndOffset,
1693 aContent, aOffset);
1695 // If aContent/aOffset is inside the maintained selection, or if it is on the
1696 // "anchor" side of the maintained selection, we need to do something.
1697 if (relToStart < 0 && relToEnd > 0 ||
1698 (relToStart > 0 &&
1699 mDomSelections[index]->GetDirection() == eDirNext) ||
1700 (relToEnd < 0 &&
1701 mDomSelections[index]->GetDirection() == eDirPrevious)) {
1702 // Set the current range to the maintained range.
1703 mDomSelections[index]->ReplaceAnchorFocusRange(mMaintainRange);
1704 if (relToStart < 0 && relToEnd > 0) {
1705 // We're inside the maintained selection, just keep it selected.
1706 return PR_TRUE;
1708 // Reverse the direction of the selection so that the anchor will be on the
1709 // far side of the maintained selection, relative to aContent/aOffset.
1710 mDomSelections[index]->SetDirection(relToStart > 0 ? eDirPrevious : eDirNext);
1712 return PR_FALSE;
1716 nsresult
1717 nsFrameSelection::HandleClick(nsIContent *aNewFocus,
1718 PRUint32 aContentOffset,
1719 PRUint32 aContentEndOffset,
1720 PRBool aContinueSelection,
1721 PRBool aMultipleSelection,
1722 PRBool aHint)
1724 if (!aNewFocus)
1725 return NS_ERROR_INVALID_ARG;
1727 InvalidateDesiredX();
1729 if (!aContinueSelection) {
1730 mMaintainRange = nsnull;
1731 if (!IsValidSelectionPoint(this, aNewFocus)) {
1732 mAncestorLimiter = nsnull;
1736 // Don't take focus when dragging off of a table
1737 if (!mDragSelectingCells)
1739 BidiLevelFromClick(aNewFocus, aContentOffset);
1740 PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON);
1741 if (aContinueSelection &&
1742 AdjustForMaintainedSelection(aNewFocus, aContentOffset))
1743 return NS_OK; //shift clicked to maintained selection. rejected.
1745 return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, HINT(aHint),
1746 aContinueSelection, aMultipleSelection);
1749 return NS_OK;
1752 void
1753 nsFrameSelection::HandleDrag(nsIFrame *aFrame, nsPoint aPoint)
1755 if (!aFrame || !mShell)
1756 return;
1758 nsresult result;
1759 nsIFrame *newFrame = 0;
1760 nsPoint newPoint;
1762 result = ConstrainFrameAndPointToAnchorSubtree(aFrame, aPoint, &newFrame, newPoint);
1763 if (NS_FAILED(result))
1764 return;
1765 if (!newFrame)
1766 return;
1768 nsIFrame::ContentOffsets offsets =
1769 newFrame->GetContentOffsetsFromPoint(newPoint);
1770 if (!offsets.content)
1771 return;
1773 if ((newFrame->GetStateBits() & NS_FRAME_SELECTED_CONTENT) &&
1774 AdjustForMaintainedSelection(offsets.content, offsets.offset))
1775 return;
1777 // Adjust offsets according to maintained amount
1778 if (mMaintainRange &&
1779 mMaintainedAmount != eSelectNoAmount) {
1781 nsINode* rangenode = mMaintainRange->GetStartParent();
1782 PRInt32 rangeOffset = mMaintainRange->StartOffset();
1783 PRInt32 relativePosition =
1784 nsContentUtils::ComparePoints(rangenode, rangeOffset,
1785 offsets.content, offsets.offset);
1787 nsDirection direction = relativePosition > 0 ? eDirPrevious : eDirNext;
1788 nsSelectionAmount amount = mMaintainedAmount;
1789 if (amount == eSelectBeginLine && direction == eDirNext)
1790 amount = eSelectEndLine;
1792 PRInt32 offset;
1793 nsIFrame* frame = GetFrameForNodeOffset(offsets.content, offsets.offset, HINTRIGHT, &offset);
1795 if (frame && amount == eSelectWord && direction == eDirPrevious) {
1796 // To avoid selecting the previous word when at start of word,
1797 // first move one character forward.
1798 nsPeekOffsetStruct charPos;
1799 charPos.SetData(eSelectCharacter, eDirNext, offset, 0,
1800 PR_FALSE, mLimiter != nsnull, PR_FALSE, PR_FALSE);
1801 if (NS_SUCCEEDED(frame->PeekOffset(&charPos))) {
1802 frame = charPos.mResultFrame;
1803 offset = charPos.mContentOffset;
1807 nsPeekOffsetStruct pos;
1808 pos.SetData(amount, direction, offset, 0,
1809 PR_FALSE, mLimiter != nsnull, PR_FALSE, PR_FALSE);
1811 if (frame && NS_SUCCEEDED(frame->PeekOffset(&pos)) && pos.mResultContent) {
1812 offsets.content = pos.mResultContent;
1813 offsets.offset = pos.mContentOffset;
1817 HandleClick(offsets.content, offsets.offset, offsets.offset,
1818 PR_TRUE, PR_FALSE, offsets.associateWithNext);
1821 nsresult
1822 nsFrameSelection::StartAutoScrollTimer(nsIView *aView,
1823 nsPoint aPoint,
1824 PRUint32 aDelay)
1826 NS_ENSURE_STATE(mShell);
1827 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1828 if (!mDomSelections[index])
1829 return NS_ERROR_NULL_POINTER;
1831 return mDomSelections[index]->StartAutoScrollTimer(mShell->GetPresContext(),
1832 aView, aPoint, aDelay);
1835 void
1836 nsFrameSelection::StopAutoScrollTimer()
1838 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1839 if (!mDomSelections[index])
1840 return;
1842 mDomSelections[index]->StopAutoScrollTimer();
1846 hard to go from nodes to frames, easy the other way!
1848 nsresult
1849 nsFrameSelection::TakeFocus(nsIContent *aNewFocus,
1850 PRUint32 aContentOffset,
1851 PRUint32 aContentEndOffset,
1852 HINT aHint,
1853 PRBool aContinueSelection,
1854 PRBool aMultipleSelection)
1856 if (!aNewFocus)
1857 return NS_ERROR_NULL_POINTER;
1859 NS_ENSURE_STATE(mShell);
1861 if (!IsValidSelectionPoint(this,aNewFocus))
1862 return NS_ERROR_FAILURE;
1864 // Clear all table selection data
1865 mSelectingTableCellMode = 0;
1866 mDragSelectingCells = PR_FALSE;
1867 mStartSelectedCell = nsnull;
1868 mEndSelectedCell = nsnull;
1869 mAppendStartSelectedCell = nsnull;
1871 //HACKHACKHACK
1872 if (!aNewFocus->GetParent())
1873 return NS_ERROR_FAILURE;
1874 //END HACKHACKHACK /checking for root frames/content
1876 mHint = aHint;
1878 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
1879 if (!mDomSelections[index])
1880 return NS_ERROR_NULL_POINTER;
1882 //traverse through document and unselect crap here
1883 if (!aContinueSelection) {//single click? setting cursor down
1884 PRUint32 batching = mBatching;//hack to use the collapse code.
1885 PRBool changes = mChangesDuringBatching;
1886 mBatching = 1;
1888 if (aMultipleSelection) {
1889 // Remove existing collapsed ranges as there's no point in having
1890 // non-anchor/focus collapsed ranges.
1891 mDomSelections[index]->RemoveCollapsedRanges();
1893 nsCOMPtr<nsIRange> newRange = new nsRange();
1894 if (!newRange) {
1895 return NS_ERROR_OUT_OF_MEMORY;
1898 newRange->SetStart(aNewFocus, aContentOffset);
1899 newRange->SetEnd(aNewFocus, aContentOffset);
1900 mDomSelections[index]->AddRange(newRange);
1901 mBatching = batching;
1902 mChangesDuringBatching = changes;
1904 else
1906 PRBool oldDesiredXSet = mDesiredXSet; //need to keep old desired X if it was set.
1907 mDomSelections[index]->Collapse(aNewFocus, aContentOffset);
1908 mDesiredXSet = oldDesiredXSet; //now reset desired X back.
1909 mBatching = batching;
1910 mChangesDuringBatching = changes;
1912 if (aContentEndOffset != aContentOffset)
1913 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);
1915 //find out if we are inside a table. if so, find out which one and which cell
1916 //once we do that, the next time we get a takefocus, check the parent tree.
1917 //if we are no longer inside same table ,cell then switch to table selection mode.
1918 // BUT only do this in an editor
1920 NS_ENSURE_STATE(mShell);
1921 PRInt16 displaySelection;
1922 nsresult result = mShell->GetSelectionFlags(&displaySelection);
1923 if (NS_FAILED(result))
1924 return result;
1926 // Editor has DISPLAY_ALL selection type
1927 if (displaySelection == nsISelectionDisplay::DISPLAY_ALL)
1929 mCellParent = GetCellParent(aNewFocus);
1930 #ifdef DEBUG_TABLE_SELECTION
1931 if (mCellParent)
1932 printf(" * TakeFocus - Collapsing into new cell\n");
1933 #endif
1936 else {
1937 // Now update the range list:
1938 if (aContinueSelection && aNewFocus)
1940 PRInt32 offset;
1941 nsINode *cellparent = GetCellParent(aNewFocus);
1942 if (mCellParent && cellparent && cellparent != mCellParent) //switch to cell selection mode
1944 #ifdef DEBUG_TABLE_SELECTION
1945 printf(" * TakeFocus - moving into new cell\n");
1946 #endif
1947 nsMouseEvent event(PR_FALSE, 0, nsnull, nsMouseEvent::eReal);
1949 // Start selecting in the cell we were in before
1950 nsINode* parent = ParentOffset(mCellParent, &offset);
1951 if (parent)
1952 HandleTableSelection(parent, offset,
1953 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1955 // Find the parent of this new cell and extend selection to it
1956 parent = ParentOffset(cellparent, &offset);
1958 // XXXX We need to REALLY get the current key shift state
1959 // (we'd need to add event listener -- let's not bother for now)
1960 event.isShift = PR_FALSE; //aContinueSelection;
1961 if (parent)
1963 mCellParent = cellparent;
1964 // Continue selection into next cell
1965 HandleTableSelection(parent, offset,
1966 nsISelectionPrivate::TABLESELECTION_CELL, &event);
1969 else
1971 // XXXX Problem: Shift+click in browser is appending text selection to selected table!!!
1972 // is this the place to erase seleced cells ?????
1973 if (mDomSelections[index]->GetDirection() == eDirNext && aContentEndOffset > aContentOffset) //didn't go far enough
1975 mDomSelections[index]->Extend(aNewFocus, aContentEndOffset);//this will only redraw the diff
1977 else
1978 mDomSelections[index]->Extend(aNewFocus, aContentOffset);
1983 // Don't notify selection listeners if batching is on:
1984 if (GetBatching())
1985 return NS_OK;
1986 return NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
1991 SelectionDetails*
1992 nsFrameSelection::LookUpSelection(nsIContent *aContent,
1993 PRInt32 aContentOffset,
1994 PRInt32 aContentLength,
1995 PRBool aSlowCheck) const
1997 if (!aContent || !mShell)
1998 return nsnull;
2000 SelectionDetails* details = nsnull;
2002 for (PRInt32 j = 0; j < nsISelectionController::NUM_SELECTIONTYPES; j++) {
2003 if (mDomSelections[j]) {
2004 mDomSelections[j]->LookUpSelection(aContent, aContentOffset,
2005 aContentLength, &details, (SelectionType)(1<<j), aSlowCheck);
2009 return details;
2012 void
2013 nsFrameSelection::SetMouseDownState(PRBool aState)
2015 if (mMouseDownState == aState)
2016 return;
2018 mMouseDownState = aState;
2020 if (!mMouseDownState)
2022 mDragSelectingCells = PR_FALSE;
2023 PostReason(nsISelectionListener::MOUSEUP_REASON);
2024 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL); //notify that reason is mouse up please.
2028 nsISelection*
2029 nsFrameSelection::GetSelection(SelectionType aType) const
2031 PRInt8 index = GetIndexFromSelectionType(aType);
2032 if (index < 0)
2033 return nsnull;
2035 return static_cast<nsISelection*>(mDomSelections[index]);
2038 nsresult
2039 nsFrameSelection::ScrollSelectionIntoView(SelectionType aType,
2040 SelectionRegion aRegion,
2041 PRBool aIsSynchronous) const
2043 PRInt8 index = GetIndexFromSelectionType(aType);
2044 if (index < 0)
2045 return NS_ERROR_INVALID_ARG;
2047 if (!mDomSelections[index])
2048 return NS_ERROR_NULL_POINTER;
2050 // After ScrollSelectionIntoView(), the pending notifications might be
2051 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
2052 return mDomSelections[index]->ScrollIntoView(aRegion, aIsSynchronous,
2053 PR_TRUE);
2056 nsresult
2057 nsFrameSelection::RepaintSelection(SelectionType aType) const
2059 PRInt8 index = GetIndexFromSelectionType(aType);
2060 if (index < 0)
2061 return NS_ERROR_INVALID_ARG;
2062 if (!mDomSelections[index])
2063 return NS_ERROR_NULL_POINTER;
2064 NS_ENSURE_STATE(mShell);
2065 return mDomSelections[index]->Repaint(mShell->GetPresContext());
2068 nsIFrame*
2069 nsFrameSelection::GetFrameForNodeOffset(nsIContent *aNode,
2070 PRInt32 aOffset,
2071 HINT aHint,
2072 PRInt32 *aReturnOffset) const
2074 if (!aNode || !aReturnOffset || !mShell)
2075 return nsnull;
2077 if (aOffset < 0)
2078 return nsnull;
2080 *aReturnOffset = aOffset;
2082 nsCOMPtr<nsIContent> theNode = aNode;
2084 if (aNode->IsNodeOfType(nsINode::eELEMENT))
2086 PRInt32 childIndex = 0;
2087 PRInt32 numChildren = theNode->GetChildCount();
2089 if (aHint == HINTLEFT)
2091 if (aOffset > 0)
2092 childIndex = aOffset - 1;
2093 else
2094 childIndex = aOffset;
2096 else // HINTRIGHT
2098 if (aOffset >= numChildren)
2100 if (numChildren > 0)
2101 childIndex = numChildren - 1;
2102 else
2103 childIndex = 0;
2105 else
2106 childIndex = aOffset;
2109 if (childIndex > 0 || numChildren > 0) {
2110 nsCOMPtr<nsIContent> childNode = theNode->GetChildAt(childIndex);
2112 if (!childNode)
2113 return nsnull;
2115 theNode = childNode;
2118 #ifdef DONT_DO_THIS_YET
2119 // XXX: We can't use this code yet because the hinting
2120 // can cause us to attach to the wrong line frame.
2122 // Now that we have the child node, check if it too
2123 // can contain children. If so, call this method again!
2125 if (theNode->IsNodeOfType(nsINode::eELEMENT))
2127 PRInt32 newOffset = 0;
2129 if (aOffset > childIndex)
2131 numChildren = theNode->GetChildCount();
2133 newOffset = numChildren;
2136 return GetFrameForNodeOffset(theNode, newOffset, aHint, aReturnOffset);
2138 else
2139 #endif // DONT_DO_THIS_YET
2141 // Check to see if theNode is a text node. If it is, translate
2142 // aOffset into an offset into the text node.
2144 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(theNode);
2146 if (textNode)
2148 if (aOffset > childIndex)
2150 PRUint32 textLength = 0;
2152 nsresult rv = textNode->GetLength(&textLength);
2153 if (NS_FAILED(rv))
2154 return nsnull;
2156 *aReturnOffset = (PRInt32)textLength;
2158 else
2159 *aReturnOffset = 0;
2164 nsIFrame* returnFrame = mShell->GetPrimaryFrameFor(theNode);
2165 if (!returnFrame)
2166 return nsnull;
2168 // find the child frame containing the offset we want
2169 returnFrame->GetChildFrameContainingOffset(*aReturnOffset, aHint,
2170 &aOffset, &returnFrame);
2171 return returnFrame;
2174 void
2175 nsFrameSelection::CommonPageMove(PRBool aForward,
2176 PRBool aExtend,
2177 nsIScrollableView *aScrollableView)
2179 if (!aScrollableView)
2180 return;
2181 // expected behavior for PageMove is to scroll AND move the caret
2182 // and remain relative position of the caret in view. see Bug 4302.
2184 nsresult result;
2185 //get the frame from the scrollable view
2187 nsIFrame* mainframe = nsnull;
2189 // The view's client data points back to its frame
2190 nsIView *scrolledView;
2191 result = aScrollableView->GetScrolledView(scrolledView);
2193 if (NS_FAILED(result))
2194 return;
2196 if (scrolledView)
2197 mainframe = static_cast<nsIFrame*>(scrolledView->GetClientData());
2199 if (!mainframe)
2200 return;
2202 // find out where the caret is.
2203 // we should know mDesiredX value of nsFrameSelection, but I havent seen that behavior in other windows applications yet.
2204 nsISelection* domSel = GetSelection(nsISelectionController::SELECTION_NORMAL);
2206 if (!domSel)
2207 return;
2209 nsRefPtr<nsCaret> caret;
2210 nsRect caretPos;
2211 PRBool isCollapsed;
2212 result = mShell->GetCaret(getter_AddRefs(caret));
2214 if (NS_FAILED(result))
2215 return;
2217 nsIView *caretView;
2218 result = caret->GetCaretCoordinates(nsCaret::eClosestViewCoordinates, domSel, &caretPos, &isCollapsed, &caretView);
2220 if (NS_FAILED(result))
2221 return;
2223 //need to adjust caret jump by percentage scroll
2224 nsSize scrollDelta;
2225 aScrollableView->GetPageScrollDistances(&scrollDelta);
2227 if (aForward)
2228 caretPos.y += scrollDelta.height;
2229 else
2230 caretPos.y -= scrollDelta.height;
2233 if (caretView)
2235 caretPos += caretView->GetOffsetTo(scrolledView);
2238 // get a content at desired location
2239 nsPoint desiredPoint;
2240 desiredPoint.x = caretPos.x;
2241 desiredPoint.y = caretPos.y + caretPos.height/2;
2242 nsIFrame::ContentOffsets offsets =
2243 mainframe->GetContentOffsetsFromPoint(desiredPoint);
2245 if (!offsets.content)
2246 return;
2248 // scroll one page
2249 aScrollableView->ScrollByPages(0, aForward ? 1 : -1);
2251 // place the caret
2252 HandleClick(offsets.content, offsets.offset,
2253 offsets.offset, aExtend, PR_FALSE, PR_TRUE);
2256 nsresult
2257 nsFrameSelection::CharacterMove(PRBool aForward, PRBool aExtend)
2259 if (aForward)
2260 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectCharacter);
2261 else
2262 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectCharacter);
2265 nsresult
2266 nsFrameSelection::CharacterExtendForDelete()
2268 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectCharacter);
2271 nsresult
2272 nsFrameSelection::WordMove(PRBool aForward, PRBool aExtend)
2274 if (aForward)
2275 return MoveCaret(nsIDOMKeyEvent::DOM_VK_RIGHT,aExtend,eSelectWord);
2276 else
2277 return MoveCaret(nsIDOMKeyEvent::DOM_VK_LEFT,aExtend,eSelectWord);
2280 nsresult
2281 nsFrameSelection::WordExtendForDelete(PRBool aForward)
2283 if (aForward)
2284 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DELETE, PR_TRUE, eSelectWord);
2285 else
2286 return MoveCaret(nsIDOMKeyEvent::DOM_VK_BACK_SPACE, PR_TRUE, eSelectWord);
2289 nsresult
2290 nsFrameSelection::LineMove(PRBool aForward, PRBool aExtend)
2292 if (aForward)
2293 return MoveCaret(nsIDOMKeyEvent::DOM_VK_DOWN,aExtend,eSelectLine);
2294 else
2295 return MoveCaret(nsIDOMKeyEvent::DOM_VK_UP,aExtend,eSelectLine);
2298 nsresult
2299 nsFrameSelection::IntraLineMove(PRBool aForward, PRBool aExtend)
2301 if (aForward)
2302 return MoveCaret(nsIDOMKeyEvent::DOM_VK_END,aExtend,eSelectLine);
2303 else
2304 return MoveCaret(nsIDOMKeyEvent::DOM_VK_HOME,aExtend,eSelectLine);
2307 nsresult
2308 nsFrameSelection::SelectAll()
2310 nsCOMPtr<nsIContent> rootContent;
2311 if (mLimiter)
2313 rootContent = mLimiter;//addrefit
2315 else if (mAncestorLimiter) {
2316 rootContent = mAncestorLimiter;
2318 else
2320 NS_ENSURE_STATE(mShell);
2321 nsIDocument *doc = mShell->GetDocument();
2322 if (!doc)
2323 return NS_ERROR_FAILURE;
2324 rootContent = doc->GetRootContent();
2325 if (!rootContent)
2326 return NS_ERROR_FAILURE;
2328 PRInt32 numChildren = rootContent->GetChildCount();
2329 PostReason(nsISelectionListener::NO_REASON);
2330 return TakeFocus(rootContent, 0, numChildren, HINTLEFT, PR_FALSE, PR_FALSE);
2333 //////////END FRAMESELECTION
2335 void
2336 nsFrameSelection::StartBatchChanges()
2338 mBatching++;
2341 void
2342 nsFrameSelection::EndBatchChanges()
2344 mBatching--;
2345 NS_ASSERTION(mBatching >=0,"Bad mBatching");
2346 if (mBatching == 0 && mChangesDuringBatching){
2347 mChangesDuringBatching = PR_FALSE;
2348 NotifySelectionListeners(nsISelectionController::SELECTION_NORMAL);
2353 nsresult
2354 nsFrameSelection::NotifySelectionListeners(SelectionType aType)
2356 PRInt8 index = GetIndexFromSelectionType(aType);
2357 if (index >=0 && mDomSelections[index])
2359 return mDomSelections[index]->NotifySelectionListeners();
2361 return NS_ERROR_FAILURE;
2364 // Start of Table Selection methods
2366 static PRBool IsCell(nsIContent *aContent)
2368 return ((aContent->Tag() == nsGkAtoms::td ||
2369 aContent->Tag() == nsGkAtoms::th) &&
2370 aContent->IsNodeOfType(nsINode::eHTML));
2373 nsITableCellLayout*
2374 nsFrameSelection::GetCellLayout(nsIContent *aCellContent) const
2376 NS_ENSURE_TRUE(mShell, nsnull);
2377 // Get frame for cell
2378 nsIFrame *cellFrame = mShell->GetPrimaryFrameFor(aCellContent);
2379 if (!cellFrame)
2380 return nsnull;
2382 nsITableCellLayout *cellLayoutObject = do_QueryFrame(cellFrame);
2383 return cellLayoutObject;
2386 nsITableLayout*
2387 nsFrameSelection::GetTableLayout(nsIContent *aTableContent) const
2389 NS_ENSURE_TRUE(mShell, nsnull);
2390 // Get frame for table
2391 nsIFrame *tableFrame = mShell->GetPrimaryFrameFor(aTableContent);
2392 if (!tableFrame)
2393 return nsnull;
2395 nsITableLayout *tableLayoutObject = do_QueryFrame(tableFrame);
2396 return tableLayoutObject;
2399 nsresult
2400 nsFrameSelection::ClearNormalSelection()
2402 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2403 if (!mDomSelections[index])
2404 return NS_ERROR_NULL_POINTER;
2406 return mDomSelections[index]->RemoveAllRanges();
2409 static nsIContent*
2410 GetFirstSelectedContent(nsIRange* aRange)
2412 if (!aRange) {
2413 return nsnull;
2416 NS_PRECONDITION(aRange->GetStartParent(), "Must have start parent!");
2417 NS_PRECONDITION(aRange->GetStartParent()->IsNodeOfType(nsINode::eELEMENT),
2418 "Unexpected parent");
2420 return aRange->GetStartParent()->GetChildAt(aRange->StartOffset());
2423 // Table selection support.
2424 // TODO: Separate table methods into a separate nsITableSelection interface
2425 nsresult
2426 nsFrameSelection::HandleTableSelection(nsINode *aParentContent,
2427 PRInt32 aContentOffset,
2428 PRInt32 aTarget,
2429 nsMouseEvent *aMouseEvent)
2431 NS_ENSURE_TRUE(aParentContent, NS_ERROR_NULL_POINTER);
2432 NS_ENSURE_TRUE(aMouseEvent, NS_ERROR_NULL_POINTER);
2434 if (mMouseDownState && mDragSelectingCells && (aTarget & nsISelectionPrivate::TABLESELECTION_TABLE))
2436 // We were selecting cells and user drags mouse in table border or inbetween cells,
2437 // just do nothing
2438 return NS_OK;
2441 nsresult result = NS_OK;
2443 nsIContent *childContent = aParentContent->GetChildAt(aContentOffset);
2445 // When doing table selection, always set the direction to next so
2446 // we can be sure that anchorNode's offset always points to the
2447 // selected cell
2448 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2449 if (!mDomSelections[index])
2450 return NS_ERROR_NULL_POINTER;
2452 mDomSelections[index]->SetDirection(eDirNext);
2454 // Stack-class to wrap all table selection changes in
2455 // BeginBatchChanges() / EndBatchChanges()
2456 nsSelectionBatcher selectionBatcher(mDomSelections[index]);
2458 PRInt32 startRowIndex, startColIndex, curRowIndex, curColIndex;
2459 if (mMouseDownState && mDragSelectingCells)
2461 // We are drag-selecting
2462 if (aTarget != nsISelectionPrivate::TABLESELECTION_TABLE)
2464 // If dragging in the same cell as last event, do nothing
2465 if (mEndSelectedCell == childContent)
2466 return NS_OK;
2468 #ifdef DEBUG_TABLE_SELECTION
2469 printf(" mStartSelectedCell = %x, mEndSelectedCell = %x, childContent = %x \n", mStartSelectedCell, mEndSelectedCell, childContent);
2470 #endif
2471 // aTarget can be any "cell mode",
2472 // so we can easily drag-select rows and columns
2473 // Once we are in row or column mode,
2474 // we can drift into any cell to stay in that mode
2475 // even if aTarget = TABLESELECTION_CELL
2477 if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW ||
2478 mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN)
2480 if (mEndSelectedCell)
2482 // Also check if cell is in same row/col
2483 result = GetCellIndexes(mEndSelectedCell, startRowIndex, startColIndex);
2484 if (NS_FAILED(result)) return result;
2485 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2486 if (NS_FAILED(result)) return result;
2488 #ifdef DEBUG_TABLE_SELECTION
2489 printf(" curRowIndex = %d, startRowIndex = %d, curColIndex = %d, startColIndex = %d\n", curRowIndex, startRowIndex, curColIndex, startColIndex);
2490 #endif
2491 if ((mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_ROW && startRowIndex == curRowIndex) ||
2492 (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_COLUMN && startColIndex == curColIndex))
2493 return NS_OK;
2495 #ifdef DEBUG_TABLE_SELECTION
2496 printf(" Dragged into a new column or row\n");
2497 #endif
2498 // Continue dragging row or column selection
2499 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2501 else if (mSelectingTableCellMode == nsISelectionPrivate::TABLESELECTION_CELL)
2503 #ifdef DEBUG_TABLE_SELECTION
2504 printf("HandleTableSelection: Dragged into a new cell\n");
2505 #endif
2506 // Trick for quick selection of rows and columns
2507 // Hold down shift, then start selecting in one direction
2508 // If next cell dragged into is in same row, select entire row,
2509 // if next cell is in same column, select entire column
2510 if (mStartSelectedCell && aMouseEvent->isShift)
2512 result = GetCellIndexes(mStartSelectedCell, startRowIndex, startColIndex);
2513 if (NS_FAILED(result)) return result;
2514 result = GetCellIndexes(childContent, curRowIndex, curColIndex);
2515 if (NS_FAILED(result)) return result;
2517 if (startRowIndex == curRowIndex ||
2518 startColIndex == curColIndex)
2520 // Force new selection block
2521 mStartSelectedCell = nsnull;
2522 mDomSelections[index]->RemoveAllRanges();
2524 if (startRowIndex == curRowIndex)
2525 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_ROW;
2526 else
2527 mSelectingTableCellMode = nsISelectionPrivate::TABLESELECTION_COLUMN;
2529 return SelectRowOrColumn(childContent, mSelectingTableCellMode);
2533 // Reselect block of cells to new end location
2534 return SelectBlockOfCells(mStartSelectedCell, childContent);
2537 // Do nothing if dragging in table, but outside a cell
2538 return NS_OK;
2540 else
2542 // Not dragging -- mouse event is down or up
2543 if (mMouseDownState)
2545 #ifdef DEBUG_TABLE_SELECTION
2546 printf("HandleTableSelection: Mouse down event\n");
2547 #endif
2548 // Clear cell we stored in mouse-down
2549 mUnselectCellOnMouseUp = nsnull;
2551 if (aTarget == nsISelectionPrivate::TABLESELECTION_CELL)
2553 PRBool isSelected = PR_FALSE;
2555 // Check if we have other selected cells
2556 nsIContent* previousCellNode =
2557 GetFirstSelectedContent(GetFirstCellRange());
2558 if (previousCellNode)
2560 // We have at least 1 other selected cell
2562 // Check if new cell is already selected
2563 NS_ENSURE_STATE(mShell);
2564 nsIFrame *cellFrame = mShell->GetPrimaryFrameFor(childContent);
2565 if (!cellFrame) return NS_ERROR_NULL_POINTER;
2566 result = cellFrame->GetSelected(&isSelected);
2567 if (NS_FAILED(result)) return result;
2569 else
2571 // No cells selected -- remove non-cell selection
2572 mDomSelections[index]->RemoveAllRanges();
2574 mDragSelectingCells = PR_TRUE; // Signal to start drag-cell-selection
2575 mSelectingTableCellMode = aTarget;
2576 // Set start for new drag-selection block (not appended)
2577 mStartSelectedCell = childContent;
2578 // The initial block end is same as the start
2579 mEndSelectedCell = childContent;
2581 if (isSelected)
2583 // Remember this cell to (possibly) unselect it on mouseup
2584 mUnselectCellOnMouseUp = childContent;
2585 #ifdef DEBUG_TABLE_SELECTION
2586 printf("HandleTableSelection: Saving mUnselectCellOnMouseUp\n");
2587 #endif
2589 else
2591 // Select an unselected cell
2592 // but first remove existing selection if not in same table
2593 if (previousCellNode &&
2594 !IsInSameTable(previousCellNode, childContent))
2596 mDomSelections[index]->RemoveAllRanges();
2597 // Reset selection mode that is cleared in RemoveAllRanges
2598 mSelectingTableCellMode = aTarget;
2601 return SelectCellElement(childContent);
2604 return NS_OK;
2606 else if (aTarget == nsISelectionPrivate::TABLESELECTION_TABLE)
2608 //TODO: We currently select entire table when clicked between cells,
2609 // should we restrict to only around border?
2610 // *** How do we get location data for cell and click?
2611 mDragSelectingCells = PR_FALSE;
2612 mStartSelectedCell = nsnull;
2613 mEndSelectedCell = nsnull;
2615 // Remove existing selection and select the table
2616 mDomSelections[index]->RemoveAllRanges();
2617 return CreateAndAddRange(aParentContent, aContentOffset);
2619 else if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW || aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2621 #ifdef DEBUG_TABLE_SELECTION
2622 printf("aTarget == %d\n", aTarget);
2623 #endif
2625 // Start drag-selecting mode so multiple rows/cols can be selected
2626 // Note: Currently, nsFrame::GetDataForTableSelection
2627 // will never call us for row or column selection on mouse down
2628 mDragSelectingCells = PR_TRUE;
2630 // Force new selection block
2631 mStartSelectedCell = nsnull;
2632 mDomSelections[index]->RemoveAllRanges();
2633 // Always do this AFTER RemoveAllRanges
2634 mSelectingTableCellMode = aTarget;
2635 return SelectRowOrColumn(childContent, aTarget);
2638 else
2640 #ifdef DEBUG_TABLE_SELECTION
2641 printf("HandleTableSelection: Mouse UP event. mDragSelectingCells=%d, mStartSelectedCell=%d\n", mDragSelectingCells, mStartSelectedCell);
2642 #endif
2643 // First check if we are extending a block selection
2644 PRInt32 rangeCount;
2645 result = mDomSelections[index]->GetRangeCount(&rangeCount);
2646 if (NS_FAILED(result))
2647 return result;
2649 if (rangeCount > 0 && aMouseEvent->isShift &&
2650 mAppendStartSelectedCell && mAppendStartSelectedCell != childContent)
2652 // Shift key is down: append a block selection
2653 mDragSelectingCells = PR_FALSE;
2654 return SelectBlockOfCells(mAppendStartSelectedCell, childContent);
2657 if (mDragSelectingCells)
2658 mAppendStartSelectedCell = mStartSelectedCell;
2660 mDragSelectingCells = PR_FALSE;
2661 mStartSelectedCell = nsnull;
2662 mEndSelectedCell = nsnull;
2664 // Any other mouseup actions require that Ctrl or Cmd key is pressed
2665 // else stop table selection mode
2666 PRBool doMouseUpAction = PR_FALSE;
2667 #ifdef XP_MACOSX
2668 doMouseUpAction = aMouseEvent->isMeta;
2669 #else
2670 doMouseUpAction = aMouseEvent->isControl;
2671 #endif
2672 if (!doMouseUpAction)
2674 #ifdef DEBUG_TABLE_SELECTION
2675 printf("HandleTableSelection: Ending cell selection on mouseup: mAppendStartSelectedCell=%d\n", mAppendStartSelectedCell);
2676 #endif
2677 return NS_OK;
2679 // Unselect a cell only if it wasn't
2680 // just selected on mousedown
2681 if( childContent == mUnselectCellOnMouseUp)
2683 // Scan ranges to find the cell to unselect (the selection range to remove)
2684 // XXXbz it's really weird that this lives outside the loop, so once we
2685 // find one we keep looking at it even if we find no more cells...
2686 nsINode* previousCellParent = nsnull;
2687 #ifdef DEBUG_TABLE_SELECTION
2688 printf("HandleTableSelection: Unselecting mUnselectCellOnMouseUp; rangeCount=%d\n", rangeCount);
2689 #endif
2690 for( PRInt32 i = 0; i < rangeCount; i++)
2692 // Strong reference, because sometimes we want to remove
2693 // this range, and then we might be the only owner.
2694 nsCOMPtr<nsIRange> range = mDomSelections[index]->GetRangeAt(i);
2695 if (!range) return NS_ERROR_NULL_POINTER;
2697 nsINode* parent = range->GetStartParent();
2698 if (!parent) return NS_ERROR_NULL_POINTER;
2700 PRInt32 offset = range->StartOffset();
2701 // Be sure previous selection is a table cell
2702 nsIContent* child = parent->GetChildAt(offset);
2703 if (child && IsCell(child))
2704 previousCellParent = parent;
2706 // We're done if we didn't find parent of a previously-selected cell
2707 if (!previousCellParent) break;
2709 if (previousCellParent == aParentContent && offset == aContentOffset)
2711 // Cell is already selected
2712 if (rangeCount == 1)
2714 #ifdef DEBUG_TABLE_SELECTION
2715 printf("HandleTableSelection: Unselecting single selected cell\n");
2716 #endif
2717 // This was the only cell selected.
2718 // Collapse to "normal" selection inside the cell
2719 mStartSelectedCell = nsnull;
2720 mEndSelectedCell = nsnull;
2721 mAppendStartSelectedCell = nsnull;
2722 //TODO: We need a "Collapse to just before deepest child" routine
2723 // Even better, should we collapse to just after the LAST deepest child
2724 // (i.e., at the end of the cell's contents)?
2725 return mDomSelections[index]->Collapse(childContent, 0);
2727 #ifdef DEBUG_TABLE_SELECTION
2728 printf("HandleTableSelection: Removing cell from multi-cell selection\n");
2729 #endif
2730 // Unselecting the start of previous block
2731 // XXX What do we use now!
2732 if (childContent == mAppendStartSelectedCell)
2733 mAppendStartSelectedCell = nsnull;
2735 // Deselect cell by removing its range from selection
2736 return mDomSelections[index]->RemoveRange(range);
2739 mUnselectCellOnMouseUp = nsnull;
2743 return result;
2746 nsresult
2747 nsFrameSelection::SelectBlockOfCells(nsIContent *aStartCell, nsIContent *aEndCell)
2749 NS_ENSURE_TRUE(aStartCell, NS_ERROR_NULL_POINTER);
2750 NS_ENSURE_TRUE(aEndCell, NS_ERROR_NULL_POINTER);
2751 mEndSelectedCell = aEndCell;
2753 nsCOMPtr<nsIContent> startCell;
2754 nsresult result = NS_OK;
2756 // If new end cell is in a different table, do nothing
2757 nsIContent* table = IsInSameTable(aStartCell, aEndCell);
2758 if (!table) {
2759 return NS_OK;
2762 // Get starting and ending cells' location in the cellmap
2763 PRInt32 startRowIndex, startColIndex, endRowIndex, endColIndex;
2764 result = GetCellIndexes(aStartCell, startRowIndex, startColIndex);
2765 if(NS_FAILED(result)) return result;
2766 result = GetCellIndexes(aEndCell, endRowIndex, endColIndex);
2767 if(NS_FAILED(result)) return result;
2769 // Check that |table| is a table.
2770 if (!GetTableLayout(table)) return NS_ERROR_FAILURE;
2772 PRInt32 curRowIndex, curColIndex;
2774 if (mDragSelectingCells)
2776 // Drag selecting: remove selected cells outside of new block limits
2778 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
2779 if (!mDomSelections[index])
2780 return NS_ERROR_NULL_POINTER;
2782 // Strong reference because we sometimes remove the range
2783 nsCOMPtr<nsIRange> range = GetFirstCellRange();
2784 nsIContent* cellNode = GetFirstSelectedContent(range);
2785 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2787 PRInt32 minRowIndex = PR_MIN(startRowIndex, endRowIndex);
2788 PRInt32 maxRowIndex = PR_MAX(startRowIndex, endRowIndex);
2789 PRInt32 minColIndex = PR_MIN(startColIndex, endColIndex);
2790 PRInt32 maxColIndex = PR_MAX(startColIndex, endColIndex);
2792 while (cellNode)
2794 result = GetCellIndexes(cellNode, curRowIndex, curColIndex);
2795 if (NS_FAILED(result)) return result;
2797 #ifdef DEBUG_TABLE_SELECTION
2798 if (!range)
2799 printf("SelectBlockOfCells -- range is null\n");
2800 #endif
2801 if (range &&
2802 (curRowIndex < minRowIndex || curRowIndex > maxRowIndex ||
2803 curColIndex < minColIndex || curColIndex > maxColIndex))
2805 mDomSelections[index]->RemoveRange(range);
2806 // Since we've removed the range, decrement pointer to next range
2807 mSelectedCellIndex--;
2809 range = GetNextCellRange();
2810 cellNode = GetFirstSelectedContent(range);
2811 NS_PRECONDITION(!range || cellNode, "Must have cellNode if had a range");
2815 nsCOMPtr<nsIDOMElement> cellElement;
2816 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan;
2817 PRBool isSelected;
2819 // Note that we select block in the direction of user's mouse dragging,
2820 // which means start cell may be after the end cell in either row or column
2821 PRInt32 row = startRowIndex;
2822 while(PR_TRUE)
2824 PRInt32 col = startColIndex;
2825 while(PR_TRUE)
2827 // Get TableLayout interface to access cell data based on cellmap location
2828 // frames are not ref counted, so don't use an nsCOMPtr
2829 nsITableLayout *tableLayoutObject = GetTableLayout(table);
2830 if (!tableLayoutObject) return NS_ERROR_FAILURE;
2832 result = tableLayoutObject->GetCellDataAt(row, col, *getter_AddRefs(cellElement),
2833 curRowIndex, curColIndex, rowSpan, colSpan,
2834 actualRowSpan, actualColSpan, isSelected);
2835 if (NS_FAILED(result)) return result;
2837 NS_ASSERTION(actualColSpan, "!actualColSpan is 0!");
2839 // Skip cells that are spanned from previous locations or are already selected
2840 if (!isSelected && cellElement && row == curRowIndex && col == curColIndex)
2842 nsCOMPtr<nsIContent> cellContent = do_QueryInterface(cellElement);
2843 result = SelectCellElement(cellContent);
2844 if (NS_FAILED(result)) return result;
2846 // Done when we reach end column
2847 if (col == endColIndex) break;
2849 if (startColIndex < endColIndex)
2850 col ++;
2851 else
2852 col--;
2854 if (row == endRowIndex) break;
2856 if (startRowIndex < endRowIndex)
2857 row++;
2858 else
2859 row--;
2861 return result;
2864 nsresult
2865 nsFrameSelection::SelectRowOrColumn(nsIContent *aCellContent, PRUint32 aTarget)
2867 if (!aCellContent) return NS_ERROR_NULL_POINTER;
2869 nsIContent* table = GetParentTable(aCellContent);
2870 if (!table) return NS_ERROR_NULL_POINTER;
2872 // Get table and cell layout interfaces to access
2873 // cell data based on cellmap location
2874 // Frames are not ref counted, so don't use an nsCOMPtr
2875 nsITableLayout *tableLayout = GetTableLayout(table);
2876 if (!tableLayout) return NS_ERROR_FAILURE;
2877 nsITableCellLayout *cellLayout = GetCellLayout(aCellContent);
2878 if (!cellLayout) return NS_ERROR_FAILURE;
2880 // Get location of target cell:
2881 PRInt32 rowIndex, colIndex, curRowIndex, curColIndex;
2882 nsresult result = cellLayout->GetCellIndexes(rowIndex, colIndex);
2883 if (NS_FAILED(result)) return result;
2885 // Be sure we start at proper beginning
2886 // (This allows us to select row or col given ANY cell!)
2887 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2888 colIndex = 0;
2889 if (aTarget == nsISelectionPrivate::TABLESELECTION_COLUMN)
2890 rowIndex = 0;
2892 nsCOMPtr<nsIDOMElement> cellElement;
2893 nsCOMPtr<nsIContent> firstCell;
2894 nsCOMPtr<nsIDOMElement> lastCell;
2895 PRInt32 rowSpan, colSpan, actualRowSpan, actualColSpan;
2896 PRBool isSelected;
2898 do {
2899 // Loop through all cells in column or row to find first and last
2900 result = tableLayout->GetCellDataAt(rowIndex, colIndex, *getter_AddRefs(cellElement),
2901 curRowIndex, curColIndex, rowSpan, colSpan,
2902 actualRowSpan, actualColSpan, isSelected);
2903 if (NS_FAILED(result)) return result;
2904 if (cellElement)
2906 NS_ASSERTION(actualRowSpan > 0 && actualColSpan> 0, "SelectRowOrColumn: Bad rowspan or colspan\n");
2907 if (!firstCell)
2908 firstCell = do_QueryInterface(cellElement);
2910 lastCell = cellElement;
2912 // Move to next cell in cellmap, skipping spanned locations
2913 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2914 colIndex += actualColSpan;
2915 else
2916 rowIndex += actualRowSpan;
2919 while (cellElement);
2921 // Use SelectBlockOfCells:
2922 // This will replace existing selection,
2923 // but allow unselecting by dragging out of selected region
2924 if (firstCell && lastCell)
2926 if (!mStartSelectedCell)
2928 // We are starting a new block, so select the first cell
2929 result = SelectCellElement(firstCell);
2930 if (NS_FAILED(result)) return result;
2931 mStartSelectedCell = firstCell;
2933 nsCOMPtr<nsIContent> lastCellContent = do_QueryInterface(lastCell);
2934 result = SelectBlockOfCells(mStartSelectedCell, lastCellContent);
2936 // This gets set to the cell at end of row/col,
2937 // but we need it to be the cell under cursor
2938 mEndSelectedCell = aCellContent;
2939 return result;
2942 #if 0
2943 // This is a more efficient strategy that appends row to current selection,
2944 // but doesn't allow dragging OFF of an existing selection to unselect!
2945 do {
2946 // Loop through all cells in column or row
2947 result = tableLayout->GetCellDataAt(rowIndex, colIndex,
2948 getter_AddRefs(cellElement),
2949 curRowIndex, curColIndex,
2950 rowSpan, colSpan,
2951 actualRowSpan, actualColSpan,
2952 isSelected);
2953 if (NS_FAILED(result)) return result;
2954 // We're done when cell is not found
2955 if (!cellElement) break;
2958 // Check spans else we infinitely loop
2959 NS_ASSERTION(actualColSpan, "actualColSpan is 0!");
2960 NS_ASSERTION(actualRowSpan, "actualRowSpan is 0!");
2962 // Skip cells that are already selected or span from outside our region
2963 if (!isSelected && rowIndex == curRowIndex && colIndex == curColIndex)
2965 result = SelectCellElement(cellElement);
2966 if (NS_FAILED(result)) return result;
2968 // Move to next row or column in cellmap, skipping spanned locations
2969 if (aTarget == nsISelectionPrivate::TABLESELECTION_ROW)
2970 colIndex += actualColSpan;
2971 else
2972 rowIndex += actualRowSpan;
2974 while (cellElement);
2975 #endif
2977 return NS_OK;
2980 nsIContent*
2981 nsFrameSelection::GetFirstCellNodeInRange(nsIRange *aRange) const
2983 if (!aRange) return nsnull;
2985 nsINode* startParent = aRange->GetStartParent();
2986 if (!startParent)
2987 return nsnull;
2989 PRInt32 offset = aRange->StartOffset();
2991 nsIContent* childContent = startParent->GetChildAt(offset);
2992 if (!childContent)
2993 return nsnull;
2994 // Don't return node if not a cell
2995 if (!IsCell(childContent))
2996 return nsnull;
2998 return childContent;
3001 nsIRange*
3002 nsFrameSelection::GetFirstCellRange()
3004 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3005 if (!mDomSelections[index])
3006 return nsnull;
3008 nsIRange* firstRange = mDomSelections[index]->GetRangeAt(0);
3009 if (!GetFirstCellNodeInRange(firstRange)) {
3010 return nsnull;
3013 // Setup for next cell
3014 mSelectedCellIndex = 1;
3016 return firstRange;
3019 nsIRange*
3020 nsFrameSelection::GetNextCellRange()
3022 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3023 if (!mDomSelections[index])
3024 return nsnull;
3026 nsIRange* range = mDomSelections[index]->GetRangeAt(mSelectedCellIndex);
3028 // Get first node in next range of selection - test if it's a cell
3029 if (!GetFirstCellNodeInRange(range)) {
3030 return nsnull;
3033 // Setup for next cell
3034 mSelectedCellIndex++;
3036 return range;
3039 nsresult
3040 nsFrameSelection::GetCellIndexes(nsIContent *aCell,
3041 PRInt32 &aRowIndex,
3042 PRInt32 &aColIndex)
3044 if (!aCell) return NS_ERROR_NULL_POINTER;
3046 aColIndex=0; // initialize out params
3047 aRowIndex=0;
3049 nsITableCellLayout *cellLayoutObject = GetCellLayout(aCell);
3050 if (!cellLayoutObject) return NS_ERROR_FAILURE;
3051 return cellLayoutObject->GetCellIndexes(aRowIndex, aColIndex);
3054 nsIContent*
3055 nsFrameSelection::IsInSameTable(nsIContent *aContent1,
3056 nsIContent *aContent2) const
3058 if (!aContent1 || !aContent2) return PR_FALSE;
3060 nsIContent* tableNode1 = GetParentTable(aContent1);
3061 nsIContent* tableNode2 = GetParentTable(aContent2);
3063 // Must be in the same table. Note that we want to return false for
3064 // the test if both tables are null.
3065 return (tableNode1 == tableNode2) ? tableNode1 : nsnull;
3068 nsIContent*
3069 nsFrameSelection::GetParentTable(nsIContent *aCell) const
3071 if (!aCell) {
3072 return nsnull;
3075 for (nsIContent* parent = aCell->GetParent(); parent;
3076 parent = parent->GetParent()) {
3077 if (parent->Tag() == nsGkAtoms::table &&
3078 parent->IsNodeOfType(nsINode::eHTML)) {
3079 return parent;
3083 return nsnull;
3086 nsresult
3087 nsFrameSelection::SelectCellElement(nsIContent *aCellElement)
3089 nsIContent *parent = aCellElement->GetParent();
3091 // Get child offset
3092 PRInt32 offset = parent->IndexOf(aCellElement);
3094 return CreateAndAddRange(parent, offset);
3097 nsresult
3098 nsTypedSelection::getTableCellLocationFromRange(nsIRange *aRange, PRInt32 *aSelectionType, PRInt32 *aRow, PRInt32 *aCol)
3100 if (!aRange || !aSelectionType || !aRow || !aCol)
3101 return NS_ERROR_NULL_POINTER;
3103 *aSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
3104 *aRow = 0;
3105 *aCol = 0;
3107 // Must have access to frame selection to get cell info
3108 if (!mFrameSelection) return NS_OK;
3110 nsresult result = GetTableSelectionType(aRange, aSelectionType);
3111 if (NS_FAILED(result)) return result;
3113 // Don't fail if range does not point to a single table cell,
3114 // let aSelectionType tell user if we don't have a cell
3115 if (*aSelectionType != nsISelectionPrivate::TABLESELECTION_CELL)
3116 return NS_OK;
3118 // Get the child content (the cell) pointed to by starting node of range
3119 // We do minimal checking since GetTableSelectionType assures
3120 // us that this really is a table cell
3121 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
3122 if (!content)
3123 return NS_ERROR_FAILURE;
3125 nsIContent *child = content->GetChildAt(aRange->StartOffset());
3126 if (!child)
3127 return NS_ERROR_FAILURE;
3129 //Note: This is a non-ref-counted pointer to the frame
3130 nsITableCellLayout *cellLayout = mFrameSelection->GetCellLayout(child);
3131 if (NS_FAILED(result))
3132 return result;
3133 if (!cellLayout)
3134 return NS_ERROR_FAILURE;
3136 return cellLayout->GetCellIndexes(*aRow, *aCol);
3139 nsresult
3140 nsTypedSelection::addTableCellRange(nsIRange *aRange, PRBool *aDidAddRange,
3141 PRInt32 *aOutIndex)
3143 if (!aDidAddRange || !aOutIndex)
3144 return NS_ERROR_NULL_POINTER;
3146 *aDidAddRange = PR_FALSE;
3147 *aOutIndex = -1;
3149 if (!mFrameSelection)
3150 return NS_OK;
3152 if (!aRange)
3153 return NS_ERROR_NULL_POINTER;
3155 nsresult result;
3157 // Get if we are adding a cell selection and the row, col of cell if we are
3158 PRInt32 newRow, newCol, tableMode;
3159 result = getTableCellLocationFromRange(aRange, &tableMode, &newRow, &newCol);
3160 if (NS_FAILED(result)) return result;
3162 // If not adding a cell range, we are done here
3163 if (tableMode != nsISelectionPrivate::TABLESELECTION_CELL)
3165 mFrameSelection->mSelectingTableCellMode = tableMode;
3166 // Don't fail if range isn't a selected cell, aDidAddRange tells caller if we didn't proceed
3167 return NS_OK;
3170 // Set frame selection mode only if not already set to a table mode
3171 // so we don't lose the select row and column flags (not detected by getTableCellLocation)
3172 if (mFrameSelection->mSelectingTableCellMode == TABLESELECTION_NONE)
3173 mFrameSelection->mSelectingTableCellMode = tableMode;
3175 *aDidAddRange = PR_TRUE;
3176 return AddItem(aRange, aOutIndex);
3179 //TODO: Figure out TABLESELECTION_COLUMN and TABLESELECTION_ALLCELLS
3180 NS_IMETHODIMP
3181 nsTypedSelection::GetTableSelectionType(nsIDOMRange* aRange,
3182 PRInt32* aTableSelectionType)
3184 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
3185 return GetTableSelectionType(range, aTableSelectionType);
3188 nsresult
3189 nsTypedSelection::GetTableSelectionType(nsIRange* aRange,
3190 PRInt32* aTableSelectionType)
3192 if (!aRange || !aTableSelectionType)
3193 return NS_ERROR_NULL_POINTER;
3195 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_NONE;
3197 // Must have access to frame selection to get cell info
3198 if(!mFrameSelection) return NS_OK;
3200 nsINode* startNode = aRange->GetStartParent();
3201 if (!startNode) return NS_ERROR_FAILURE;
3203 nsINode* endNode = aRange->GetEndParent();
3204 if (!endNode) return NS_ERROR_FAILURE;
3206 // Not a single selected node
3207 if (startNode != endNode) return NS_OK;
3209 PRInt32 startOffset = aRange->StartOffset();
3210 PRInt32 endOffset = aRange->EndOffset();
3212 // Not a single selected node
3213 if ((endOffset - startOffset) != 1)
3214 return NS_OK;
3216 if (!startNode->IsNodeOfType(nsINode::eHTML)) {
3217 // Implies a check for being an element; if we ever make this work
3218 // for non-HTML, need to keep checking for elements.
3219 return NS_OK;
3222 nsIAtom *tag = static_cast<nsIContent*>(startNode)->Tag();
3224 if (tag == nsGkAtoms::tr)
3226 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_CELL;
3228 else //check to see if we are selecting a table or row (column and all cells not done yet)
3230 nsIContent *child = startNode->GetChildAt(startOffset);
3231 if (!child)
3232 return NS_ERROR_FAILURE;
3234 tag = child->Tag();
3236 if (tag == nsGkAtoms::table)
3237 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_TABLE;
3238 else if (tag == nsGkAtoms::tr)
3239 *aTableSelectionType = nsISelectionPrivate::TABLESELECTION_ROW;
3242 return NS_OK;
3245 nsresult
3246 nsFrameSelection::CreateAndAddRange(nsINode *aParentNode, PRInt32 aOffset)
3248 if (!aParentNode) return NS_ERROR_NULL_POINTER;
3250 nsCOMPtr<nsIRange> range = new nsRange();
3251 if (!range) return NS_ERROR_OUT_OF_MEMORY;
3253 // Set range around child at given offset
3254 nsresult result = range->SetStart(aParentNode, aOffset);
3255 if (NS_FAILED(result)) return result;
3256 result = range->SetEnd(aParentNode, aOffset+1);
3257 if (NS_FAILED(result)) return result;
3259 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3260 if (!mDomSelections[index])
3261 return NS_ERROR_NULL_POINTER;
3263 return mDomSelections[index]->AddRange(range);
3266 // End of Table Selection
3268 void
3269 nsFrameSelection::SetAncestorLimiter(nsIContent *aLimiter)
3271 if (mAncestorLimiter != aLimiter) {
3272 mAncestorLimiter = aLimiter;
3273 PRInt8 index =
3274 GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3275 if (!mDomSelections[index])
3276 return;
3278 if (!IsValidSelectionPoint(this, mDomSelections[index]->GetFocusNode())) {
3279 ClearNormalSelection();
3280 if (mAncestorLimiter) {
3281 PostReason(nsISelectionListener::NO_REASON);
3282 TakeFocus(mAncestorLimiter, 0, 0, HINTLEFT, PR_FALSE, PR_FALSE);
3288 //END nsFrameSelection methods
3291 //BEGIN nsISelection interface implementations
3295 nsresult
3296 nsFrameSelection::DeleteFromDocument()
3298 nsresult res;
3300 // If we're already collapsed, then set ourselves to include the
3301 // last item BEFORE the current range, rather than the range itself,
3302 // before we do the delete.
3303 PRBool isCollapsed;
3304 PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL);
3305 if (!mDomSelections[index])
3306 return NS_ERROR_NULL_POINTER;
3308 mDomSelections[index]->GetIsCollapsed( &isCollapsed);
3309 if (isCollapsed)
3311 // If the offset is positive, then it's easy:
3312 if (mDomSelections[index]->GetFocusOffset() > 0)
3314 mDomSelections[index]->Extend(mDomSelections[index]->GetFocusNode(), mDomSelections[index]->GetFocusOffset() - 1);
3316 else
3318 // Otherwise it's harder, have to find the previous node
3319 printf("Sorry, don't know how to delete across frame boundaries yet\n");
3320 return NS_ERROR_NOT_IMPLEMENTED;
3324 // Get an iterator
3325 nsSelectionIterator iter(mDomSelections[index]);
3326 res = iter.First();
3327 if (NS_FAILED(res))
3328 return res;
3330 while (iter.IsDone())
3332 nsCOMPtr<nsIRange> range = iter.CurrentItem();
3333 res = range->DeleteContents();
3334 if (NS_FAILED(res))
3335 return res;
3336 iter.Next();
3339 // Collapse to the new location.
3340 // If we deleted one character, then we move back one element.
3341 // FIXME We don't know how to do this past frame boundaries yet.
3342 if (isCollapsed)
3343 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->GetAnchorOffset()-1);
3344 else if (mDomSelections[index]->GetAnchorOffset() > 0)
3345 mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->GetAnchorOffset());
3346 #ifdef DEBUG
3347 else
3348 printf("Don't know how to set selection back past frame boundary\n");
3349 #endif
3351 return NS_OK;
3354 void
3355 nsFrameSelection::SetDelayedCaretData(nsMouseEvent *aMouseEvent)
3357 if (aMouseEvent)
3359 mDelayedMouseEventValid = PR_TRUE;
3360 mDelayedMouseEvent = *aMouseEvent;
3362 // Don't cache the widget. We don't need it and it could go away.
3363 mDelayedMouseEvent.widget = nsnull;
3365 else
3366 mDelayedMouseEventValid = PR_FALSE;
3369 nsMouseEvent*
3370 nsFrameSelection::GetDelayedCaretData()
3372 if (mDelayedMouseEventValid)
3373 return &mDelayedMouseEvent;
3375 return nsnull;
3378 //END nsISelection interface implementations
3380 #if 0
3381 #pragma mark -
3382 #endif
3384 // nsTypedSelection implementation
3386 // note: this can return a nil anchor node
3388 nsTypedSelection::nsTypedSelection()
3389 : mCachedOffsetForFrame(nsnull)
3390 , mDirection(eDirNext)
3391 , mType(nsISelectionController::SELECTION_NORMAL)
3395 nsTypedSelection::nsTypedSelection(nsFrameSelection *aList)
3396 : mFrameSelection(aList)
3397 , mCachedOffsetForFrame(nsnull)
3398 , mDirection(eDirNext)
3399 , mType(nsISelectionController::SELECTION_NORMAL)
3403 nsTypedSelection::~nsTypedSelection()
3405 setAnchorFocusRange(-1);
3407 if (mAutoScrollTimer) {
3408 mAutoScrollTimer->Stop();
3409 mAutoScrollTimer = nsnull;
3412 mScrollEvent.Revoke();
3414 if (mCachedOffsetForFrame) {
3415 delete mCachedOffsetForFrame;
3416 mCachedOffsetForFrame = nsnull;
3421 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTypedSelection)
3422 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTypedSelection)
3423 // Unlink the selection listeners *before* we do RemoveAllRanges since
3424 // we don't want to notify the listeners during JS GC (they could be
3425 // in JS!).
3426 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mSelectionListeners)
3427 tmp->RemoveAllRanges();
3428 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFrameSelection)
3429 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
3430 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTypedSelection)
3432 PRUint32 i, count = tmp->mRanges.Length();
3433 for (i = 0; i < count; ++i) {
3434 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRanges[i].mRange)
3437 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAnchorFocusRange)
3438 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFrameSelection)
3439 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mSelectionListeners)
3440 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
3442 // QueryInterface implementation for nsTypedSelection
3443 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypedSelection)
3444 NS_INTERFACE_MAP_ENTRY(nsISelection)
3445 NS_INTERFACE_MAP_ENTRY(nsISelection2)
3446 NS_INTERFACE_MAP_ENTRY(nsISelectionPrivate)
3447 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
3448 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISelection)
3449 NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(Selection)
3450 NS_INTERFACE_MAP_END
3452 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypedSelection)
3453 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypedSelection)
3455 NS_IMETHODIMP
3456 nsTypedSelection::SetPresShell(nsIPresShell *aPresShell)
3458 mPresShellWeak = do_GetWeakReference(aPresShell);
3459 return NS_OK;
3464 NS_IMETHODIMP
3465 nsTypedSelection::GetAnchorNode(nsIDOMNode** aAnchorNode)
3467 nsINode* anchorNode = GetAnchorNode();
3468 if (anchorNode) {
3469 return CallQueryInterface(anchorNode, aAnchorNode);
3472 *aAnchorNode = nsnull;
3473 return NS_OK;
3476 nsINode*
3477 nsTypedSelection::GetAnchorNode()
3479 if (!mAnchorFocusRange)
3480 return nsnull;
3482 if (GetDirection() == eDirNext) {
3483 return mAnchorFocusRange->GetStartParent();
3486 return mAnchorFocusRange->GetEndParent();
3489 NS_IMETHODIMP
3490 nsTypedSelection::GetAnchorOffset(PRInt32* aAnchorOffset)
3492 *aAnchorOffset = GetAnchorOffset();
3493 return NS_OK;
3496 // note: this can return a nil focus node
3497 NS_IMETHODIMP
3498 nsTypedSelection::GetFocusNode(nsIDOMNode** aFocusNode)
3500 nsINode* focusNode = GetFocusNode();
3501 if (focusNode) {
3502 return CallQueryInterface(focusNode, aFocusNode);
3505 *aFocusNode = nsnull;
3506 return NS_OK;
3509 nsINode*
3510 nsTypedSelection::GetFocusNode()
3512 if (!mAnchorFocusRange)
3513 return nsnull;
3515 if (GetDirection() == eDirNext){
3516 return mAnchorFocusRange->GetEndParent();
3519 return mAnchorFocusRange->GetStartParent();
3522 NS_IMETHODIMP nsTypedSelection::GetFocusOffset(PRInt32* aFocusOffset)
3524 *aFocusOffset = GetFocusOffset();
3525 return NS_OK;
3528 void nsTypedSelection::setAnchorFocusRange(PRInt32 indx)
3530 if (indx >= (PRInt32)mRanges.Length())
3531 return;
3532 if (indx < 0) //release all
3534 mAnchorFocusRange = nsnull;
3536 else{
3537 mAnchorFocusRange = mRanges[indx].mRange;
3541 PRInt32
3542 nsTypedSelection::GetAnchorOffset()
3544 if (!mAnchorFocusRange)
3545 return 0;
3547 if (GetDirection() == eDirNext){
3548 return mAnchorFocusRange->StartOffset();
3551 return mAnchorFocusRange->EndOffset();
3554 PRInt32
3555 nsTypedSelection::GetFocusOffset()
3557 if (!mAnchorFocusRange)
3558 return 0;
3560 if (GetDirection() == eDirNext){
3561 return mAnchorFocusRange->EndOffset();
3564 return mAnchorFocusRange->StartOffset();
3567 static PRInt32
3568 CompareToRangeStart(nsINode* aCompareNode, PRInt32 aCompareOffset,
3569 nsIRange* aRange)
3571 return nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3572 aRange->GetStartParent(),
3573 aRange->StartOffset());
3576 static PRInt32
3577 CompareToRangeEnd(nsINode* aCompareNode, PRInt32 aCompareOffset,
3578 nsIRange* aRange)
3580 return nsContentUtils::ComparePoints(aCompareNode, aCompareOffset,
3581 aRange->GetEndParent(),
3582 aRange->EndOffset());
3585 // nsTypedSelection::FindInsertionPoint
3587 // Binary searches the given sorted array of ranges for the insertion point
3588 // for the given node/offset. The given comparator is used, and the index
3589 // where the point should appear in the array is placed in *aInsertionPoint.
3591 // If there is an item in the array equal to the input point, we will return
3592 // the index of this item.
3594 PRInt32
3595 nsTypedSelection::FindInsertionPoint(
3596 nsTArray<RangeData>* aElementArray,
3597 nsINode* aPointNode, PRInt32 aPointOffset,
3598 PRInt32 (*aComparator)(nsINode*,PRInt32,nsIRange*))
3600 PRInt32 beginSearch = 0;
3601 PRInt32 endSearch = aElementArray->Length(); // one beyond what to check
3602 while (endSearch - beginSearch > 0) {
3603 PRInt32 center = (endSearch - beginSearch) / 2 + beginSearch;
3605 nsIRange* range = (*aElementArray)[center].mRange;
3607 PRInt32 cmp = aComparator(aPointNode, aPointOffset, range);
3609 if (cmp < 0) { // point < cur
3610 endSearch = center;
3611 } else if (cmp > 0) { // point > cur
3612 beginSearch = center + 1;
3613 } else { // found match, done
3614 beginSearch = center;
3615 break;
3618 return beginSearch;
3621 // nsTypedSelection::SubtractRange
3623 // A helper function that subtracts aSubtract from aRange, and adds
3624 // 1 or 2 RangeData objects representing the remaining non-overlapping
3625 // difference to aOutput. It is assumed that the caller has checked that
3626 // aRange and aSubtract do indeed overlap
3628 nsresult
3629 nsTypedSelection::SubtractRange(RangeData* aRange, nsIRange* aSubtract,
3630 nsTArray<RangeData>* aOutput)
3632 nsIRange* range = aRange->mRange;
3634 // First we want to compare to the range start
3635 PRInt32 cmp = CompareToRangeStart(range->GetStartParent(),
3636 range->StartOffset(),
3637 aSubtract);
3639 // Also, make a comparison to the range end
3640 PRInt32 cmp2 = CompareToRangeEnd(range->GetEndParent(),
3641 range->EndOffset(),
3642 aSubtract);
3644 // If the existing range left overlaps the new range (aSubtract) then
3645 // cmp < 0, and cmp2 < 0
3646 // If it right overlaps the new range then cmp > 0 and cmp2 > 0
3647 // If it fully contains the new range, then cmp < 0 and cmp2 > 0
3649 if (cmp2 > 0) {
3650 // We need to add a new RangeData to the output, running from
3651 // the end of aSubtract to the end of range
3652 nsIRange* postOverlap = new nsRange();
3653 if (!postOverlap)
3654 return NS_ERROR_OUT_OF_MEMORY;
3656 nsresult rv =
3657 postOverlap->SetStart(aSubtract->GetEndParent(), aSubtract->EndOffset());
3658 NS_ENSURE_SUCCESS(rv, rv);
3659 rv =
3660 postOverlap->SetEnd(range->GetEndParent(), range->EndOffset());
3661 NS_ENSURE_SUCCESS(rv, rv);
3662 if (!postOverlap->Collapsed()) {
3663 if (!aOutput->InsertElementAt(0, RangeData(postOverlap)))
3664 return NS_ERROR_OUT_OF_MEMORY;
3665 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3669 if (cmp < 0) {
3670 // We need to add a new RangeData to the output, running from
3671 // the start of the range to the start of aSubtract
3672 nsIRange* preOverlap = new nsRange();
3673 if (!preOverlap)
3674 return NS_ERROR_OUT_OF_MEMORY;
3676 nsresult rv =
3677 preOverlap->SetStart(range->GetStartParent(), range->StartOffset());
3678 NS_ENSURE_SUCCESS(rv, rv);
3679 rv =
3680 preOverlap->SetEnd(aSubtract->GetStartParent(), aSubtract->StartOffset());
3681 NS_ENSURE_SUCCESS(rv, rv);
3683 if (!preOverlap->Collapsed()) {
3684 if (!aOutput->InsertElementAt(0, RangeData(preOverlap)))
3685 return NS_ERROR_OUT_OF_MEMORY;
3686 (*aOutput)[0].mTextRangeStyle = aRange->mTextRangeStyle;
3690 return NS_OK;
3693 nsresult
3694 nsTypedSelection::AddItem(nsIRange *aItem, PRInt32 *aOutIndex)
3696 if (!aItem)
3697 return NS_ERROR_NULL_POINTER;
3698 if (!aItem->IsPositioned())
3699 return NS_ERROR_UNEXPECTED;
3700 if (aOutIndex)
3701 *aOutIndex = -1;
3703 // a common case is that we have no ranges yet
3704 if (mRanges.Length() == 0) {
3705 if (!mRanges.AppendElement(RangeData(aItem)))
3706 return NS_ERROR_OUT_OF_MEMORY;
3707 if (aOutIndex)
3708 *aOutIndex = 0;
3709 return NS_OK;
3712 PRInt32 startIndex, endIndex;
3713 GetIndicesForInterval(aItem->GetStartParent(), aItem->StartOffset(),
3714 aItem->GetEndParent(), aItem->EndOffset(),
3715 PR_FALSE, &startIndex, &endIndex);
3717 if (endIndex == -1) {
3718 // All ranges start after the given range. We can insert our range at
3719 // position 0, knowing there are no overlaps (handled below)
3720 startIndex = 0;
3721 endIndex = 0;
3722 } else if (startIndex == -1) {
3723 // All ranges end before the given range. We can insert our range at
3724 // the end of the array, knowing there are no overlaps (handled below)
3725 startIndex = mRanges.Length();
3726 endIndex = endIndex;
3729 if (startIndex == endIndex) {
3730 // The new range doesn't overlap any existing ranges
3731 if (!mRanges.InsertElementAt(startIndex, RangeData(aItem)))
3732 return NS_ERROR_OUT_OF_MEMORY;
3733 if (aOutIndex)
3734 *aOutIndex = startIndex;
3735 return NS_OK;
3738 // If the range is already contained in mRanges, silently succeed
3739 PRBool sameRange = EqualsRangeAtPoint(aItem->GetStartParent(),
3740 aItem->StartOffset(),
3741 aItem->GetEndParent(),
3742 aItem->EndOffset(), startIndex);
3743 if (sameRange) {
3744 if (aOutIndex)
3745 *aOutIndex = startIndex;
3746 return NS_OK;
3749 // We now know that at least 1 existing range overlaps with the range that
3750 // we are trying to add. In fact, the only ranges of interest are those at
3751 // the two end points, startIndex and endIndex - 1 (which may point to the
3752 // same range) as these may partially overlap the new range. Any ranges
3753 // between these indices are fully overlapped by the new range, and so can be
3754 // removed
3755 nsTArray<RangeData> overlaps;
3756 if (!overlaps.InsertElementAt(0, mRanges[startIndex]))
3757 return NS_ERROR_OUT_OF_MEMORY;
3759 if (endIndex - 1 != startIndex) {
3760 if (!overlaps.InsertElementAt(1, mRanges[endIndex - 1]))
3761 return NS_ERROR_OUT_OF_MEMORY;
3764 // Remove all the overlapping ranges
3765 mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
3767 nsTArray<RangeData> temp;
3768 for (PRInt32 i = overlaps.Length() - 1; i >= 0; i--) {
3769 nsresult rv = SubtractRange(&overlaps[i], aItem, &temp);
3770 NS_ENSURE_SUCCESS(rv, rv);
3773 // Insert the new element into our "leftovers" array
3774 PRInt32 insertionPoint = FindInsertionPoint(&temp, aItem->GetStartParent(),
3775 aItem->StartOffset(),
3776 CompareToRangeStart);
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 return NS_OK;
3788 nsresult
3789 nsTypedSelection::RemoveItem(nsIRange *aItem)
3791 if (!aItem)
3792 return NS_ERROR_NULL_POINTER;
3794 // Find the range's index & remove it. We could use FindInsertionPoint to
3795 // get O(log n) time, but that requires many expensive DOM comparisons.
3796 // For even several thousand items, this is probably faster because the
3797 // comparisons are so fast.
3798 PRInt32 idx = -1;
3799 PRUint32 i;
3800 for (i = 0; i < mRanges.Length(); i ++) {
3801 if (mRanges[i].mRange == aItem) {
3802 idx = (PRInt32)i;
3803 break;
3806 if (idx < 0)
3807 return NS_ERROR_INVALID_ARG;
3809 mRanges.RemoveElementAt(idx);
3810 return NS_OK;
3813 nsresult
3814 nsTypedSelection::RemoveCollapsedRanges()
3816 PRUint32 i = 0;
3817 while (i < mRanges.Length()) {
3818 if (mRanges[i].mRange->Collapsed()) {
3819 nsresult rv = RemoveItem(mRanges[i].mRange);
3820 NS_ENSURE_SUCCESS(rv, rv);
3821 } else {
3822 ++i;
3825 return NS_OK;
3828 nsresult
3829 nsTypedSelection::Clear(nsPresContext* aPresContext)
3831 setAnchorFocusRange(-1);
3833 for (PRInt32 i = 0; i < (PRInt32)mRanges.Length(); i ++) {
3834 selectFrames(aPresContext, mRanges[i].mRange, 0);
3836 mRanges.Clear();
3838 // Reset direction so for more dependable table selection range handling
3839 SetDirection(eDirNext);
3841 // If this was an ATTENTION selection, change it back to normal now
3842 if (mFrameSelection &&
3843 mFrameSelection->GetDisplaySelection() ==
3844 nsISelectionController::SELECTION_ATTENTION) {
3845 mFrameSelection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
3848 return NS_OK;
3851 NS_IMETHODIMP
3852 nsTypedSelection::GetType(PRInt16 *aType)
3854 NS_ENSURE_ARG_POINTER(aType);
3855 *aType = mType;
3857 return NS_OK;
3860 // nsTypedSelection::GetRangesForInterval
3862 // XPCOM wrapper for the COMArray version
3864 NS_IMETHODIMP
3865 nsTypedSelection::GetRangesForInterval(nsIDOMNode* aBeginNode, PRInt32 aBeginOffset,
3866 nsIDOMNode* aEndNode, PRInt32 aEndOffset,
3867 PRBool aAllowAdjacent,
3868 PRUint32 *aResultCount,
3869 nsIDOMRange ***aResults)
3871 if (!aBeginNode || ! aEndNode || ! aResultCount || ! aResults)
3872 return NS_ERROR_NULL_POINTER;
3874 *aResultCount = 0;
3875 *aResults = nsnull;
3877 nsCOMArray<nsIDOMRange> results;
3878 nsresult rv = GetRangesForIntervalCOMArray(aBeginNode, aBeginOffset,
3879 aEndNode, aEndOffset,
3880 aAllowAdjacent,
3881 &results);
3882 NS_ENSURE_SUCCESS(rv, rv);
3883 if (results.Count() == 0)
3884 return NS_OK;
3886 *aResults = static_cast<nsIDOMRange**>
3887 (nsMemory::Alloc(sizeof(nsIDOMRange*) * results.Count()));
3888 NS_ENSURE_TRUE(*aResults, NS_ERROR_OUT_OF_MEMORY);
3890 *aResultCount = results.Count();
3891 for (PRInt32 i = 0; i < results.Count(); i ++)
3892 NS_ADDREF((*aResults)[i] = results[i]);
3893 return NS_OK;
3896 // nsTypedSelection::GetRangesForIntervalCOMArray
3898 // Fills a COM array with the ranges overlapping the range specified by
3899 // the given endpoints. Ranges in the selection exactly adjacent to the
3900 // input range are not returned unless aAllowAdjacent is set.
3902 NS_IMETHODIMP
3903 nsTypedSelection::GetRangesForIntervalCOMArray(nsIDOMNode* aBeginNode, PRInt32 aBeginOffset,
3904 nsIDOMNode* aEndNode, PRInt32 aEndOffset,
3905 PRBool aAllowAdjacent,
3906 nsCOMArray<nsIDOMRange>* aRanges)
3908 nsCOMPtr<nsINode> begin = do_QueryInterface(aBeginNode);
3909 nsCOMPtr<nsINode> end = do_QueryInterface(aEndNode);
3910 nsCOMArray<nsIRange> ranges;
3911 nsresult rv = GetRangesForIntervalCOMArray(begin, aBeginOffset,
3912 end, aEndOffset,
3913 aAllowAdjacent, &ranges);
3914 NS_ENSURE_SUCCESS(rv, rv);
3915 for (PRInt32 i = 0; i < ranges.Count(); ++i) {
3916 nsCOMPtr<nsIDOMRange> r = do_QueryInterface(ranges[i]);
3917 if (!aRanges->AppendObject(r)) {
3918 return NS_ERROR_OUT_OF_MEMORY;
3922 return NS_OK;
3925 // nsTypedSelection::GetRangesForIntervalCOMArray
3927 // Fills a COM array with the ranges overlapping the range specified by
3928 // the given endpoints. Ranges in the selection exactly adjacent to the
3929 // input range are not returned unless aAllowAdjacent is set.
3931 // For example, if the following ranges were in the selection
3932 // (assume everything is within the same node)
3934 // Start Offset: 0 2 7 9
3935 // End Offset: 2 5 9 10
3937 // and passed aBeginOffset of 2 and aEndOffset of 9, then with
3938 // aAllowAdjacent set, all the ranges should be returned. If
3939 // aAllowAdjacent was false, the ranges [2, 5] and [7, 9] only
3940 // should be returned
3942 // Now that overlapping ranges are disallowed, there can be a maximum of
3943 // 2 adjacent ranges
3945 nsresult
3946 nsTypedSelection::GetRangesForIntervalCOMArray(nsINode* aBeginNode, PRInt32 aBeginOffset,
3947 nsINode* aEndNode, PRInt32 aEndOffset,
3948 PRBool aAllowAdjacent,
3949 nsCOMArray<nsIRange>* aRanges)
3951 aRanges->Clear();
3952 PRInt32 startIndex, endIndex;
3953 GetIndicesForInterval(aBeginNode, aBeginOffset, aEndNode, aEndOffset,
3954 aAllowAdjacent, &startIndex, &endIndex);
3955 if (startIndex == -1 || endIndex == -1)
3956 return NS_OK;
3958 for (PRInt32 i = startIndex; i < endIndex; i++) {
3959 if (!aRanges->AppendObject(mRanges[i].mRange))
3960 return NS_ERROR_OUT_OF_MEMORY;
3963 return NS_OK;
3966 // nsTypedSelection::GetIndicesForInterval
3968 // Works on the same principle as GetRangesForIntervalCOMArray above, however
3969 // instead this returns the indices into mRanges between which the
3970 // overlapping ranges lie.
3972 void
3973 nsTypedSelection::GetIndicesForInterval(nsINode* aBeginNode,
3974 PRInt32 aBeginOffset,
3975 nsINode* aEndNode, PRInt32 aEndOffset,
3976 PRBool aAllowAdjacent,
3977 PRInt32 *aStartIndex,
3978 PRInt32 *aEndIndex)
3980 if (aStartIndex)
3981 *aStartIndex = -1;
3982 if (aEndIndex)
3983 *aEndIndex = -1;
3985 if (mRanges.Length() == 0)
3986 return;
3988 // Ranges that end before the given interval and begin after the given
3989 // interval can be discarded
3990 PRInt32 endsBeforeIndex =
3991 FindInsertionPoint(&mRanges, aEndNode, aEndOffset, &CompareToRangeStart);
3992 if (endsBeforeIndex == 0)
3993 return; // optimization: all ranges are after us
3994 *aEndIndex = endsBeforeIndex;
3996 PRInt32 beginsAfterIndex =
3997 FindInsertionPoint(&mRanges, aBeginNode, aBeginOffset, &CompareToRangeEnd);
3998 if (beginsAfterIndex == (PRInt32) mRanges.Length())
3999 return; // optimization: all ranges are before us
4001 if (aAllowAdjacent) {
4002 // If there is a range that starts on aEndNode, aEndOffset, then
4003 // endsBeforeIndex will point to it (there can be only 1 such range),
4004 // so increment endsBeforeIndex to encompass that range
4005 if (endsBeforeIndex < mRanges.Length()) {
4006 nsINode* endNode = mRanges[endsBeforeIndex].mRange->GetStartParent();
4007 PRInt32 endOffset = mRanges[endsBeforeIndex].mRange->StartOffset();
4008 if (endNode == aEndNode && endOffset == aEndOffset)
4009 endsBeforeIndex++;
4012 // Likewise, if there is a range that ends on aStartNode, aStartOffset
4013 // then beginsAfterIndex will already point to it, and doesn't need
4014 // altered
4015 } else {
4016 // If there is a range that ends on aStartNode, aStartOffset then
4017 // beginsAfterIndex will point to it, so increment it to exclude the range
4018 nsINode* startNode = mRanges[beginsAfterIndex].mRange->GetEndParent();
4019 PRInt32 startOffset = mRanges[beginsAfterIndex].mRange->EndOffset();
4020 if (startNode == aBeginNode && startOffset == aBeginOffset)
4021 beginsAfterIndex++;
4023 // Likewise, if there is a range that starts on aEndNode, aEndOffset
4024 // then endsBeforeIndex will already point to it, and doesn't need
4025 // altered
4028 *aStartIndex = beginsAfterIndex;
4029 *aEndIndex = endsBeforeIndex;
4030 return;
4033 // RangeMatches*Point
4035 // Compares the range beginning or ending point, and returns true if it
4036 // exactly matches the given DOM point.
4038 static inline PRBool
4039 RangeMatchesBeginPoint(nsIRange* aRange, nsINode* aNode, PRInt32 aOffset)
4041 return aRange->GetStartParent() == aNode && aRange->StartOffset() == aOffset;
4044 static inline PRBool
4045 RangeMatchesEndPoint(nsIRange* aRange, nsINode* aNode, PRInt32 aOffset)
4047 return aRange->GetEndParent() == aNode && aRange->EndOffset() == aOffset;
4050 // nsTypedSelection::EqualsRangeAtPoint
4052 // Utility method for checking equivalence of two ranges.
4054 PRBool
4055 nsTypedSelection::EqualsRangeAtPoint(
4056 nsINode* aBeginNode, PRInt32 aBeginOffset,
4057 nsINode* aEndNode, PRInt32 aEndOffset,
4058 PRInt32 aRangeIndex)
4060 if (aRangeIndex >=0 && aRangeIndex < mRanges.Length()) {
4061 nsIRange* range = mRanges[aRangeIndex].mRange;
4062 if (RangeMatchesBeginPoint(range, aBeginNode, aBeginOffset)
4063 && RangeMatchesEndPoint(range, aEndNode, aEndOffset))
4064 return PR_TRUE;
4066 return PR_FALSE;
4069 //utility method to get the primary frame of node or use the offset to get frame of child node
4071 #if 0
4072 NS_IMETHODIMP
4073 nsTypedSelection::GetPrimaryFrameForRangeEndpoint(nsIDOMNode *aNode, PRInt32 aOffset, PRBool aIsEndNode, nsIFrame **aReturnFrame)
4075 if (!aNode || !aReturnFrame || !mFrameSelection)
4076 return NS_ERROR_NULL_POINTER;
4078 if (aOffset < 0)
4079 return NS_ERROR_FAILURE;
4081 *aReturnFrame = 0;
4083 nsresult result = NS_OK;
4085 nsCOMPtr<nsIDOMNode> node = aNode;
4087 if (!node)
4088 return NS_ERROR_NULL_POINTER;
4090 nsCOMPtr<nsIContent> content = do_QueryInterface(node, &result);
4092 if (NS_FAILED(result))
4093 return result;
4095 if (!content)
4096 return NS_ERROR_NULL_POINTER;
4098 if (content->IsNodeOfType(nsINode::eELEMENT))
4100 if (aIsEndNode)
4101 aOffset--;
4103 if (aOffset >= 0)
4105 nsIContent *child = content->GetChildAt(aOffset);
4106 if (!child) //out of bounds?
4107 return NS_ERROR_FAILURE;
4109 content = child; // releases the focusnode
4112 *aReturnFrame = mFrameSelection->GetShell()->GetPrimaryFrameFor(content);
4113 return NS_OK;
4115 #endif
4118 NS_IMETHODIMP
4119 nsTypedSelection::GetPrimaryFrameForAnchorNode(nsIFrame **aReturnFrame)
4121 if (!aReturnFrame)
4122 return NS_ERROR_NULL_POINTER;
4124 PRInt32 frameOffset = 0;
4125 *aReturnFrame = 0;
4126 nsCOMPtr<nsIContent> content = do_QueryInterface(GetAnchorNode());
4127 if (content && mFrameSelection)
4129 *aReturnFrame = mFrameSelection->
4130 GetFrameForNodeOffset(content, GetAnchorOffset(),
4131 mFrameSelection->GetHint(), &frameOffset);
4132 if (*aReturnFrame)
4133 return NS_OK;
4135 return NS_ERROR_FAILURE;
4138 NS_IMETHODIMP
4139 nsTypedSelection::GetPrimaryFrameForFocusNode(nsIFrame **aReturnFrame, PRInt32 *aOffsetUsed,
4140 PRBool aVisual)
4142 if (!aReturnFrame)
4143 return NS_ERROR_NULL_POINTER;
4145 nsCOMPtr<nsIContent> content = do_QueryInterface(GetFocusNode());
4146 if (!content || !mFrameSelection)
4147 return NS_ERROR_FAILURE;
4149 nsIPresShell *presShell = mFrameSelection->GetShell();
4151 PRInt32 frameOffset = 0;
4152 *aReturnFrame = 0;
4153 if (!aOffsetUsed)
4154 aOffsetUsed = &frameOffset;
4156 nsFrameSelection::HINT hint = mFrameSelection->GetHint();
4158 if (aVisual) {
4159 nsRefPtr<nsCaret> caret;
4160 nsresult result = presShell->GetCaret(getter_AddRefs(caret));
4161 if (NS_FAILED(result) || !caret)
4162 return NS_ERROR_FAILURE;
4164 PRUint8 caretBidiLevel = mFrameSelection->GetCaretBidiLevel();
4166 return caret->GetCaretFrameForNodeOffset(content, GetFocusOffset(),
4167 hint, caretBidiLevel, aReturnFrame, aOffsetUsed);
4170 *aReturnFrame = mFrameSelection->
4171 GetFrameForNodeOffset(content, GetFocusOffset(),
4172 hint, aOffsetUsed);
4173 if (!*aReturnFrame)
4174 return NS_ERROR_FAILURE;
4176 return NS_OK;
4179 //select all content children of aContent
4180 nsresult
4181 nsTypedSelection::SelectAllFramesForContent(nsIContentIterator *aInnerIter,
4182 nsIContent *aContent,
4183 PRBool aSelected)
4185 if (!mFrameSelection)
4186 return NS_OK; // nothing to do
4187 nsIPresShell* shell = mFrameSelection->GetShell();
4188 if (!shell)
4189 return NS_OK;
4190 nsresult result;
4191 if (!aInnerIter)
4192 return NS_ERROR_NULL_POINTER;
4193 result = aInnerIter->Init(aContent);
4194 nsIFrame *frame;
4195 if (NS_SUCCEEDED(result))
4197 // First select frame of content passed in
4198 frame = shell->GetPrimaryFrameFor(aContent);
4199 if (frame)
4201 frame->SetSelected(aSelected, mType);
4202 if (mFrameSelection->GetTableCellSelection())
4204 nsITableCellLayout *tcl = do_QueryFrame(frame);
4205 if (tcl)
4207 return NS_OK;
4211 // Now iterated through the child frames and set them
4212 while (!aInnerIter->IsDone())
4214 nsCOMPtr<nsIContent> innercontent =
4215 do_QueryInterface(aInnerIter->GetCurrentNode());
4217 frame = shell->GetPrimaryFrameFor(innercontent);
4218 if (frame)
4220 frame->SetSelected(aSelected, mType);
4223 aInnerIter->Next();
4226 return NS_OK;
4229 return NS_ERROR_FAILURE;
4234 //the idea of this helper method is to select, deselect "top to bottom" traversing through the frames
4235 nsresult
4236 nsTypedSelection::selectFrames(nsPresContext* aPresContext, nsIRange *aRange, PRBool aFlags)
4238 if (!mFrameSelection || !aPresContext)
4239 return NS_OK; // nothing to do
4240 nsIPresShell *presShell = aPresContext->GetPresShell();
4241 if (!presShell)
4242 return NS_OK;
4243 // Ensure all frames are properly constructed
4244 presShell->FlushPendingNotifications(Flush_Frames);
4245 // Re-get shell because the flush might have destroyed it
4246 presShell = aPresContext->GetPresShell();
4247 if (!presShell)
4248 return NS_OK;
4250 nsCOMPtr<nsIDOMRange> domRange = do_QueryInterface(aRange);
4251 if (!domRange || !aPresContext)
4252 return NS_ERROR_NULL_POINTER;
4254 nsresult result;
4255 nsCOMPtr<nsIContentIterator> iter = do_CreateInstance(
4256 kCSubtreeIteratorCID,
4257 &result);
4258 if (NS_FAILED(result))
4259 return result;
4261 nsCOMPtr<nsIContentIterator> inneriter = do_CreateInstance(
4262 kCContentIteratorCID,
4263 &result);
4265 if ((NS_SUCCEEDED(result)) && iter && inneriter)
4267 result = iter->Init(aRange);
4269 // loop through the content iterator for each content node
4270 // for each text node, call SetSelected on it:
4271 nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetStartParent());
4273 // we must call first one explicitly
4274 if (!content)
4275 return NS_ERROR_UNEXPECTED;
4277 nsIFrame *frame;
4278 if (content->IsNodeOfType(nsINode::eTEXT))
4280 frame = presShell->GetPrimaryFrameFor(content);
4281 // The frame could be an SVG text frame, in which case we'll ignore
4282 // it.
4283 if (frame && frame->GetType() == nsGkAtoms::textFrame)
4285 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4286 PRUint32 startOffset = aRange->StartOffset();
4287 PRUint32 endOffset;
4288 if (aRange->GetEndParent() == content) {
4289 endOffset = aRange->EndOffset();
4290 } else {
4291 endOffset = content->GetText()->GetLength();
4293 textFrame->SetSelectedRange(startOffset, endOffset, aFlags, mType);
4297 iter->First();
4299 while (!iter->IsDone())
4301 content = do_QueryInterface(iter->GetCurrentNode());
4303 SelectAllFramesForContent(inneriter, content, aFlags);
4305 iter->Next();
4308 //we must now do the last one if it is not the same as the first
4309 if (aRange->GetEndParent() != aRange->GetStartParent())
4311 content = do_QueryInterface(aRange->GetEndParent(), &result);
4312 if (NS_FAILED(result) || !content)
4313 return result;
4315 if (content->IsNodeOfType(nsINode::eTEXT))
4317 frame = presShell->GetPrimaryFrameFor(content);
4318 // The frame could be an SVG text frame, in which case we'll
4319 // ignore it.
4320 if (frame && frame->GetType() == nsGkAtoms::textFrame)
4322 nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
4323 textFrame->SetSelectedRange(0, aRange->EndOffset(), aFlags, mType);
4328 return result;
4331 // nsTypedSelection::LookUpSelection
4333 // This function is called when a node wants to know where the selection is
4334 // over itself.
4336 // Usually, this is called when we already know there is a selection over
4337 // the node in question, and we only need to find the boundaries of it on
4338 // that node. This is when slowCheck is false--a strict test is not needed.
4339 // Other times, the caller has no idea, and wants us to test everything,
4340 // so we are supposed to determine whether there is a selection over the
4341 // node at all.
4343 // A previous version of this code used this flag to do less work when
4344 // inclusion was already known (slowCheck=false). However, our tree
4345 // structure allows us to quickly determine ranges overlapping the node,
4346 // so we just ignore the slowCheck flag and do the full test every time.
4348 // PERFORMANCE: a common case is that we are doing a fast check with exactly
4349 // one range in the selection. In this case, this function is slower than
4350 // brute force because of the overhead of checking the tree. We can optimize
4351 // this case to make it faster by doing the same thing the previous version
4352 // of this function did in the case of 1 range. This would also mean that
4353 // the aSlowCheck flag would have meaning again.
4355 NS_IMETHODIMP
4356 nsTypedSelection::LookUpSelection(nsIContent *aContent, PRInt32 aContentOffset,
4357 PRInt32 aContentLength,
4358 SelectionDetails **aReturnDetails,
4359 SelectionType aType, PRBool aSlowCheck)
4361 nsresult rv;
4362 if (!aContent || ! aReturnDetails)
4363 return NS_ERROR_NULL_POINTER;
4365 // it is common to have no ranges, to optimize that
4366 if (mRanges.Length() == 0)
4367 return NS_OK;
4369 nsCOMArray<nsIRange> overlappingRanges;
4370 rv = GetRangesForIntervalCOMArray(aContent, aContentOffset,
4371 aContent, aContentOffset + aContentLength,
4372 PR_FALSE,
4373 &overlappingRanges);
4374 NS_ENSURE_SUCCESS(rv, rv);
4375 if (overlappingRanges.Count() == 0)
4376 return NS_OK;
4378 for (PRInt32 i = 0; i < overlappingRanges.Count(); i ++) {
4379 nsIRange* range = overlappingRanges[i];
4380 nsINode* startNode = range->GetStartParent();
4381 nsINode* endNode = range->GetEndParent();
4382 PRInt32 startOffset = range->StartOffset();
4383 PRInt32 endOffset = range->EndOffset();
4385 PRInt32 start = -1, end = -1;
4386 if (startNode == aContent && endNode == aContent) {
4387 if (startOffset < (aContentOffset + aContentLength) &&
4388 endOffset > aContentOffset) {
4389 // this range is totally inside the requested content range
4390 start = PR_MAX(0, startOffset - aContentOffset);
4391 end = PR_MIN(aContentLength, endOffset - aContentOffset);
4393 // otherwise, range is inside the requested node, but does not intersect
4394 // the requested content range, so ignore it
4395 } else if (startNode == aContent) {
4396 if (startOffset < (aContentOffset + aContentLength)) {
4397 // the beginning of the range is inside the requested node, but the
4398 // end is outside, select everything from there to the end
4399 start = PR_MAX(0, startOffset - aContentOffset);
4400 end = aContentLength;
4402 } else if (endNode == aContent) {
4403 if (endOffset > aContentOffset) {
4404 // the end of the range is inside the requested node, but the beginning
4405 // is outside, select everything from the beginning to there
4406 start = 0;
4407 end = PR_MIN(aContentLength, endOffset - aContentOffset);
4409 } else {
4410 // this range does not begin or end in the requested node, but since
4411 // GetRangesForInterval returned this range, we know it overlaps.
4412 // Therefore, this node is enclosed in the range, and we select all
4413 // of it.
4414 start = 0;
4415 end = aContentLength;
4417 if (start < 0)
4418 continue; // the ranges do not overlap the input range
4420 SelectionDetails* details = new SelectionDetails;
4421 if (!details)
4422 return NS_ERROR_OUT_OF_MEMORY;
4424 details->mNext = *aReturnDetails;
4425 details->mStart = start;
4426 details->mEnd = end;
4427 details->mType = aType;
4428 RangeData *rd = FindRangeData(range);
4429 if (rd) {
4430 details->mTextRangeStyle = rd->mTextRangeStyle;
4432 *aReturnDetails = details;
4434 return NS_OK;
4437 NS_IMETHODIMP
4438 nsTypedSelection::Repaint(nsPresContext* aPresContext)
4440 PRInt32 arrCount = (PRInt32)mRanges.Length();
4442 if (arrCount < 1)
4443 return NS_OK;
4445 PRInt32 i;
4447 for (i = 0; i < arrCount; i++)
4449 nsresult rv = selectFrames(aPresContext, mRanges[i].mRange, PR_TRUE);
4451 if (NS_FAILED(rv)) {
4452 return rv;
4456 return NS_OK;
4459 NS_IMETHODIMP
4460 nsTypedSelection::GetCanCacheFrameOffset(PRBool *aCanCacheFrameOffset)
4462 NS_ENSURE_ARG_POINTER(aCanCacheFrameOffset);
4464 if (mCachedOffsetForFrame)
4465 *aCanCacheFrameOffset = mCachedOffsetForFrame->mCanCacheFrameOffset;
4466 else
4467 *aCanCacheFrameOffset = PR_FALSE;
4469 return NS_OK;
4472 NS_IMETHODIMP
4473 nsTypedSelection::SetCanCacheFrameOffset(PRBool aCanCacheFrameOffset)
4475 if (!mCachedOffsetForFrame) {
4476 mCachedOffsetForFrame = new CachedOffsetForFrame;
4479 mCachedOffsetForFrame->mCanCacheFrameOffset = aCanCacheFrameOffset;
4481 // clean up cached frame when turn off cache
4482 // fix bug 207936
4483 if (!aCanCacheFrameOffset) {
4484 mCachedOffsetForFrame->mLastCaretFrame = nsnull;
4487 return NS_OK;
4490 NS_IMETHODIMP
4491 nsTypedSelection::GetCachedFrameOffset(nsIFrame *aFrame, PRInt32 inOffset, nsPoint& aPoint)
4493 if (!mCachedOffsetForFrame) {
4494 mCachedOffsetForFrame = new CachedOffsetForFrame;
4497 nsresult rv = NS_OK;
4498 if (mCachedOffsetForFrame->mCanCacheFrameOffset &&
4499 mCachedOffsetForFrame->mLastCaretFrame &&
4500 (aFrame == mCachedOffsetForFrame->mLastCaretFrame) &&
4501 (inOffset == mCachedOffsetForFrame->mLastContentOffset))
4503 // get cached frame offset
4504 aPoint = mCachedOffsetForFrame->mCachedFrameOffset;
4506 else
4508 // Recalculate frame offset and cache it. Don't cache a frame offset if
4509 // GetPointFromOffset fails, though.
4510 rv = GetPointFromOffset(aFrame, inOffset, &aPoint);
4511 if (NS_SUCCEEDED(rv) && mCachedOffsetForFrame->mCanCacheFrameOffset) {
4512 mCachedOffsetForFrame->mCachedFrameOffset = aPoint;
4513 mCachedOffsetForFrame->mLastCaretFrame = aFrame;
4514 mCachedOffsetForFrame->mLastContentOffset = inOffset;
4518 return rv;
4521 NS_IMETHODIMP
4522 nsTypedSelection::GetFrameSelection(nsFrameSelection **aFrameSelection) {
4523 NS_ENSURE_ARG_POINTER(aFrameSelection);
4524 *aFrameSelection = mFrameSelection;
4525 NS_IF_ADDREF(*aFrameSelection);
4526 return NS_OK;
4529 NS_IMETHODIMP
4530 nsTypedSelection::SetAncestorLimiter(nsIContent *aContent)
4532 if (mFrameSelection)
4533 mFrameSelection->SetAncestorLimiter(aContent);
4534 return NS_OK;
4537 RangeData*
4538 nsTypedSelection::FindRangeData(nsIDOMRange* aRange)
4540 NS_ENSURE_TRUE(aRange, nsnull);
4541 for (PRUint32 i = 0; i < mRanges.Length(); i++) {
4542 if (mRanges[i].mRange == aRange)
4543 return &mRanges[i];
4545 return nsnull;
4548 NS_IMETHODIMP
4549 nsTypedSelection::SetTextRangeStyle(nsIDOMRange *aRange,
4550 const nsTextRangeStyle &aTextRangeStyle)
4552 NS_ENSURE_ARG_POINTER(aRange);
4553 RangeData *rd = FindRangeData(aRange);
4554 if (rd) {
4555 rd->mTextRangeStyle = aTextRangeStyle;
4557 return NS_OK;
4560 nsresult
4561 nsTypedSelection::StartAutoScrollTimer(nsPresContext *aPresContext,
4562 nsIView *aView,
4563 nsPoint& aPoint,
4564 PRUint32 aDelay)
4566 NS_PRECONDITION(aView, "Need a view");
4568 nsresult result;
4569 if (!mFrameSelection)
4570 return NS_OK;//nothing to do
4572 if (!mAutoScrollTimer)
4574 mAutoScrollTimer = new nsAutoScrollTimer();
4576 if (!mAutoScrollTimer)
4577 return NS_ERROR_OUT_OF_MEMORY;
4579 result = mAutoScrollTimer->Init(mFrameSelection, this);
4581 if (NS_FAILED(result))
4582 return result;
4585 result = mAutoScrollTimer->SetDelay(aDelay);
4587 if (NS_FAILED(result))
4588 return result;
4590 return DoAutoScrollView(aPresContext, aView, aPoint, PR_TRUE);
4593 nsresult
4594 nsTypedSelection::StopAutoScrollTimer()
4596 if (mAutoScrollTimer)
4597 return mAutoScrollTimer->Stop();
4599 return NS_OK;
4602 nsresult
4603 nsTypedSelection::GetViewAncestorOffset(nsIView *aView, nsIView *aAncestorView, nscoord *aXOffset, nscoord *aYOffset)
4605 // Note: A NULL aAncestorView pointer means that the caller wants
4606 // the view's global offset.
4608 if (!aView || !aXOffset || !aYOffset)
4609 return NS_ERROR_FAILURE;
4611 nsPoint offset = aView->GetOffsetTo(aAncestorView);
4613 *aXOffset = offset.x;
4614 *aYOffset = offset.y;
4616 return NS_OK;
4619 nsresult
4620 nsTypedSelection::ScrollPointIntoClipView(nsPresContext *aPresContext, nsIView *aView, nsPoint& aPoint, PRBool *aDidScroll)
4622 nsresult result;
4624 if (!aPresContext || !aView || !aDidScroll)
4625 return NS_ERROR_NULL_POINTER;
4627 *aDidScroll = PR_FALSE;
4630 // Get aView's scrollable view.
4633 nsIScrollableView *scrollableView =
4634 nsLayoutUtils::GetNearestScrollingView(aView, nsLayoutUtils::eEither);
4636 if (!scrollableView)
4637 return NS_OK; // Nothing to do!
4640 // Get the view that is being scrolled.
4643 nsIView *scrolledView = 0;
4645 result = scrollableView->GetScrolledView(scrolledView);
4648 // Now walk up aView's hierarchy, this time keeping track of
4649 // the view offsets until you hit the scrolledView.
4652 nsPoint viewOffset(0,0);
4654 result = GetViewAncestorOffset(aView, scrolledView, &viewOffset.x, &viewOffset.y);
4656 if (NS_FAILED(result))
4657 return result;
4660 // See if aPoint is outside the clip view's boundaries.
4661 // If it is, scroll the view till it is inside the visible area!
4664 nsRect bounds = scrollableView->View()->GetBounds();
4666 result = scrollableView->GetScrollPosition(bounds.x,bounds.y);
4668 if (NS_FAILED(result))
4669 return result;
4672 // Calculate the amount we would have to scroll in
4673 // the vertical and horizontal directions to get the point
4674 // within the clip area.
4677 nscoord dx = 0, dy = 0;
4679 nsPresContext::ScrollbarStyles ss =
4680 nsLayoutUtils::ScrollbarStylesOfView(scrollableView);
4682 if (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
4683 nscoord e = aPoint.x + viewOffset.x;
4685 nscoord x1 = bounds.x;
4686 nscoord x2 = bounds.x + bounds.width;
4688 if (e < x1)
4689 dx = e - x1;
4690 else if (e > x2)
4691 dx = e - x2;
4694 if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
4695 nscoord e = aPoint.y + viewOffset.y;
4697 nscoord y1 = bounds.y;
4698 nscoord y2 = bounds.y + bounds.height;
4700 if (e < y1)
4701 dy = e - y1;
4702 else if (e > y2)
4703 dy = e - y2;
4707 // Now scroll the view if necessary.
4710 if (dx != 0 || dy != 0)
4712 nsCOMPtr<nsIPresShell> presShell = aPresContext->GetPresShell();
4713 NS_ENSURE_STATE(presShell);
4715 nsWeakView weakView = scrollableView->View();
4717 // Make sure latest bits are available before we scroll them. This flushes
4718 // pending notifications and thus might destroy stuff (bug 421839).
4719 // We need to hold a strong ref on the view manager to keep it alive.
4720 nsCOMPtr<nsIViewManager> viewManager = presShell->GetViewManager();
4721 viewManager->Composite();
4723 if (!weakView.IsAlive()) {
4724 return NS_ERROR_NULL_POINTER;
4727 if (presShell->IsDestroying()) {
4728 return NS_ERROR_NULL_POINTER;
4731 result = scrollableView->ScrollTo(bounds.x + dx, bounds.y + dy, 0);
4733 if (NS_FAILED(result))
4734 return result;
4736 nsPoint newPos;
4738 result = scrollableView->GetScrollPosition(newPos.x, newPos.y);
4740 if (NS_FAILED(result))
4741 return result;
4743 *aDidScroll = (bounds.x != newPos.x || bounds.y != newPos.y);
4746 return result;
4749 nsresult
4750 nsTypedSelection::ScrollPointIntoView(nsPresContext *aPresContext, nsIView *aView, nsPoint& aPoint, PRBool aScrollParentViews, PRBool *aDidScroll)
4752 if (!aPresContext || !aView || !aDidScroll)
4753 return NS_ERROR_NULL_POINTER;
4755 nsresult result;
4757 *aDidScroll = PR_FALSE;
4760 // Calculate the global offset of the view.
4763 nsPoint globalOffset;
4765 result = GetViewAncestorOffset(aView, nsnull, &globalOffset.x, &globalOffset.y);
4767 if (NS_FAILED(result))
4768 return result;
4771 // Convert aPoint into global coordinates so it is easier to map
4772 // into other views.
4775 nsPoint globalPoint = aPoint + globalOffset;
4778 // Scroll the point into the visible rect of the closest
4779 // scrollable view.
4781 result = ScrollPointIntoClipView(aPresContext, aView, aPoint, aDidScroll);
4783 if (NS_FAILED(result))
4784 return result;
4787 // Now scroll the parent scrollable views.
4790 if (aScrollParentViews)
4793 // Find aView's parent scrollable view.
4796 nsIScrollableView *scrollableView =
4797 nsLayoutUtils::GetNearestScrollingView(aView, nsLayoutUtils::eEither);
4799 if (scrollableView)
4802 // Convert scrollableView to nsIView.
4805 nsIView *scrolledView = 0;
4806 nsIView *view = scrollableView->View();
4808 if (view)
4811 // Now get the scrollableView's parent, then search for it's
4812 // closest scrollable view.
4815 view = view->GetParent();
4817 while (view)
4819 scrollableView =
4820 nsLayoutUtils::GetNearestScrollingView(view,
4821 nsLayoutUtils::eEither);
4823 if (!scrollableView)
4824 break;
4826 scrolledView = 0;
4827 result = scrollableView->GetScrolledView(scrolledView);
4829 if (NS_FAILED(result))
4830 return result;
4833 // Map the global point into this scrolledView's coordinate space.
4836 result = GetViewAncestorOffset(scrolledView, nsnull, &globalOffset.x, &globalOffset.y);
4838 if (NS_FAILED(result))
4839 return result;
4841 nsPoint newPoint = globalPoint - globalOffset;
4844 // Scroll the point into the visible rect of the scrolled view.
4847 PRBool parentDidScroll = PR_FALSE;
4849 result = ScrollPointIntoClipView(aPresContext, scrolledView, newPoint, &parentDidScroll);
4851 if (NS_FAILED(result))
4852 return result;
4854 *aDidScroll = *aDidScroll || parentDidScroll;
4857 // Now get the parent of this scrollable view so we
4858 // can scroll the next parent view.
4861 view = scrollableView->View()->GetParent();
4867 return NS_OK;
4870 nsresult
4871 nsTypedSelection::DoAutoScrollView(nsPresContext *aPresContext,
4872 nsIView *aView,
4873 nsPoint& aPoint,
4874 PRBool aScrollParentViews)
4876 if (!aPresContext || !aView)
4877 return NS_ERROR_NULL_POINTER;
4879 nsresult result;
4881 if (mAutoScrollTimer)
4882 result = mAutoScrollTimer->Stop();
4885 // Calculate the global offset of the view.
4888 nsPoint globalOffset;
4889 result = GetViewAncestorOffset(aView, nsnull, &globalOffset.x, &globalOffset.y);
4890 NS_ENSURE_SUCCESS(result, result);
4893 // Convert aPoint into global coordinates so we can get back
4894 // to the same point after all the parent views have scrolled.
4896 nsPoint globalPoint = aPoint + globalOffset;
4898 // Now scroll aPoint into view.
4901 PRBool didScroll = PR_FALSE;
4903 result = ScrollPointIntoView(aPresContext, aView, aPoint, aScrollParentViews, &didScroll);
4904 NS_ENSURE_SUCCESS(result, result);
4907 // Start the AutoScroll timer if necessary.
4910 if (didScroll && mAutoScrollTimer)
4913 // Map the globalPoint back into aView's coordinate system. We
4914 // have to get the globalOffsets again because aView's
4915 // window and its parents may have changed their offsets.
4917 result = GetViewAncestorOffset(aView, nsnull, &globalOffset.x, &globalOffset.y);
4918 NS_ENSURE_SUCCESS(result, result);
4920 nsPoint svPoint = globalPoint - globalOffset;
4921 mAutoScrollTimer->Start(aPresContext, aView, svPoint);
4924 return NS_OK;
4927 NS_IMETHODIMP
4928 nsTypedSelection::GetEnumerator(nsIEnumerator **aIterator)
4930 nsresult status = NS_ERROR_OUT_OF_MEMORY;
4931 nsSelectionIterator *iterator = new nsSelectionIterator(this);
4932 if (iterator && NS_FAILED(status = CallQueryInterface(iterator, aIterator)) )
4933 delete iterator;
4934 return status;
4939 /** RemoveAllRanges zeroes the selection
4941 NS_IMETHODIMP
4942 nsTypedSelection::RemoveAllRanges()
4944 if (!mFrameSelection)
4945 return NS_OK;//nothing to do
4946 nsCOMPtr<nsPresContext> presContext;
4947 GetPresContext(getter_AddRefs(presContext));
4950 nsresult result = Clear(presContext);
4951 if (NS_FAILED(result))
4952 return result;
4954 // Turn off signal for table selection
4955 mFrameSelection->ClearTableCellSelection();
4957 return mFrameSelection->NotifySelectionListeners(GetType());
4958 // Also need to notify the frames!
4959 // PresShell::CharacterDataChanged should do that on DocumentChanged
4962 /** AddRange adds the specified range to the selection
4963 * @param aRange is the range to be added
4965 NS_IMETHODIMP
4966 nsTypedSelection::AddRange(nsIDOMRange* aRange)
4968 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
4969 return AddRange(range);
4972 nsresult
4973 nsTypedSelection::AddRange(nsIRange* aRange)
4975 if (!aRange) return NS_ERROR_NULL_POINTER;
4977 // This inserts a table cell range in proper document order
4978 // and returns NS_OK if range doesn't contain just one table cell
4979 PRBool didAddRange;
4980 PRInt32 rangeIndex;
4981 nsresult result = addTableCellRange(aRange, &didAddRange, &rangeIndex);
4982 if (NS_FAILED(result)) return result;
4984 if (!didAddRange)
4986 result = AddItem(aRange, &rangeIndex);
4987 if (NS_FAILED(result)) return result;
4990 NS_ASSERTION(rangeIndex >= 0, "Range index not returned");
4991 setAnchorFocusRange(rangeIndex);
4993 // Make sure the caret appears on the next line, if at a newline
4994 if (mType == nsISelectionController::SELECTION_NORMAL)
4995 SetInterlinePosition(PR_TRUE);
4997 nsCOMPtr<nsPresContext> presContext;
4998 GetPresContext(getter_AddRefs(presContext));
4999 selectFrames(presContext, aRange, PR_TRUE);
5001 //ScrollIntoView(); this should not happen automatically
5002 if (!mFrameSelection)
5003 return NS_OK;//nothing to do
5005 return mFrameSelection->NotifySelectionListeners(GetType());
5008 // nsTypedSelection::RemoveRange
5010 // Removes the given range from the selection. The tricky part is updating
5011 // the flags on the frames that indicate whether they have a selection or
5012 // not. There could be several selection ranges on the frame, and clearing
5013 // the bit would cause the selection to not be drawn, even when there is
5014 // another range on the frame (bug 346185).
5016 // We therefore find any ranges that intersect the same nodes as the range
5017 // being removed, and cause them to set the selected bits back on their
5018 // selected frames after we've cleared the bit from ours.
5020 NS_IMETHODIMP
5021 nsTypedSelection::RemoveRange(nsIDOMRange* aRange)
5023 nsCOMPtr<nsIRange> range = do_QueryInterface(aRange);
5024 return RemoveRange(range);
5027 nsresult
5028 nsTypedSelection::RemoveRange(nsIRange* aRange)
5030 if (!aRange)
5031 return NS_ERROR_INVALID_ARG;
5032 nsresult rv = RemoveItem(aRange);
5033 if (NS_FAILED(rv))
5034 return rv;
5036 nsINode* beginNode = aRange->GetStartParent();
5037 nsINode* endNode = aRange->GetEndParent();
5039 // find out the length of the end node, so we can select all of it
5040 PRInt32 beginOffset, endOffset;
5041 if (endNode->IsNodeOfType(nsINode::eTEXT)) {
5042 // Get the length of the text. We can't just use the offset because
5043 // another range could be touching this text node but not intersect our
5044 // range.
5045 beginOffset = 0;
5046 endOffset = static_cast<nsIContent*>(endNode)->TextLength();
5047 } else {
5048 // For non-text nodes, the given offsets should be sufficient.
5049 beginOffset = aRange->StartOffset();
5050 endOffset = aRange->EndOffset();
5053 // clear the selected bit from the removed range's frames
5054 nsCOMPtr<nsPresContext> presContext;
5055 GetPresContext(getter_AddRefs(presContext));
5056 selectFrames(presContext, aRange, PR_FALSE);
5058 // add back the selected bit for each range touching our nodes
5059 nsCOMArray<nsIRange> affectedRanges;
5060 rv = GetRangesForIntervalCOMArray(beginNode, beginOffset,
5061 endNode, endOffset,
5062 PR_TRUE, &affectedRanges);
5063 NS_ENSURE_SUCCESS(rv, rv);
5064 for (PRInt32 i = 0; i < affectedRanges.Count(); i ++) {
5065 selectFrames(presContext, affectedRanges[i], PR_TRUE);
5068 PRInt32 cnt = mRanges.Length();
5069 if (aRange == mAnchorFocusRange) {
5070 // Reset anchor to LAST range or clear it if there are no ranges.
5071 setAnchorFocusRange(cnt - 1);
5073 // When the selection is user-created it makes sense to scroll the range
5074 // into view. The spell-check selection, however, is created and destroyed
5075 // in the background. We don't want to scroll in this case or the view
5076 // might appear to be moving randomly (bug 337871).
5077 if (mType != nsISelectionController::SELECTION_SPELLCHECK && cnt > 0)
5078 ScrollIntoView(nsISelectionController::SELECTION_FOCUS_REGION, PR_FALSE,
5079 PR_FALSE);
5082 if (!mFrameSelection)
5083 return NS_OK;//nothing to do
5084 return mFrameSelection->NotifySelectionListeners(GetType());
5090 * Collapse sets the whole selection to be one point.
5092 NS_IMETHODIMP
5093 nsTypedSelection::Collapse(nsIDOMNode* aParentNode, PRInt32 aOffset)
5095 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
5096 return Collapse(parentNode, aOffset);
5099 nsresult
5100 nsTypedSelection::Collapse(nsINode* aParentNode, PRInt32 aOffset)
5102 if (!aParentNode)
5103 return NS_ERROR_INVALID_ARG;
5104 if (!mFrameSelection)
5105 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
5106 mFrameSelection->InvalidateDesiredX();
5107 if (!IsValidSelectionPoint(mFrameSelection, aParentNode))
5108 return NS_ERROR_FAILURE;
5109 nsresult result;
5110 // Delete all of the current ranges
5111 nsCOMPtr<nsPresContext> presContext;
5112 GetPresContext(getter_AddRefs(presContext));
5113 Clear(presContext);
5115 // Turn off signal for table selection
5116 mFrameSelection->ClearTableCellSelection();
5118 nsCOMPtr<nsIRange> range = new nsRange();
5119 if (!range) {
5120 NS_ASSERTION(PR_FALSE,"Couldn't make a range - nsFrameSelection::Collapse");
5121 return NS_ERROR_UNEXPECTED;
5123 result = range->SetEnd(aParentNode, aOffset);
5124 if (NS_FAILED(result))
5125 return result;
5126 result = range->SetStart(aParentNode, aOffset);
5127 if (NS_FAILED(result))
5128 return result;
5130 #ifdef DEBUG_SELECTION
5131 if (aParentNode)
5133 nsCOMPtr<nsIContent>content;
5134 content = do_QueryInterface(aParentNode);
5135 if (!content)
5136 return NS_ERROR_FAILURE;
5138 const char *tagString;
5139 content->Tag()->GetUTF8String(&tagString);
5140 printf ("Sel. Collapse to %p %s %d\n", content.get(), tagString, aOffset);
5142 else {
5143 printf ("Sel. Collapse set to null parent.\n");
5145 #endif
5148 result = AddItem(range);
5149 setAnchorFocusRange(0);
5150 selectFrames(presContext, range, PR_TRUE);
5151 if (NS_FAILED(result))
5152 return result;
5153 return mFrameSelection->NotifySelectionListeners(GetType());
5157 * Sets the whole selection to be one point
5158 * at the start of the current selection
5160 NS_IMETHODIMP
5161 nsTypedSelection::CollapseToStart()
5163 PRInt32 cnt;
5164 nsresult rv = GetRangeCount(&cnt);
5165 if (NS_FAILED(rv) || cnt <= 0)
5166 return NS_ERROR_FAILURE;
5168 // Get the first range
5169 nsIRange* firstRange = mRanges[0].mRange;
5170 if (!firstRange)
5171 return NS_ERROR_FAILURE;
5173 return Collapse(firstRange->GetStartParent(), firstRange->StartOffset());
5177 * Sets the whole selection to be one point
5178 * at the end of the current selection
5180 NS_IMETHODIMP
5181 nsTypedSelection::CollapseToEnd()
5183 PRInt32 cnt;
5184 nsresult rv = GetRangeCount(&cnt);
5185 if (NS_FAILED(rv) || cnt <= 0)
5186 return NS_ERROR_FAILURE;
5188 // Get the last range
5189 nsIRange* lastRange = mRanges[cnt-1].mRange;
5190 if (!lastRange)
5191 return NS_ERROR_FAILURE;
5193 return Collapse(lastRange->GetEndParent(), lastRange->EndOffset());
5197 * IsCollapsed -- is the whole selection just one point, or unset?
5199 NS_IMETHODIMP
5200 nsTypedSelection::GetIsCollapsed(PRBool* aIsCollapsed)
5202 if (!aIsCollapsed)
5203 return NS_ERROR_NULL_POINTER;
5205 PRInt32 cnt = (PRInt32)mRanges.Length();;
5206 if (cnt == 0)
5208 *aIsCollapsed = PR_TRUE;
5209 return NS_OK;
5212 if (cnt != 1)
5214 *aIsCollapsed = PR_FALSE;
5215 return NS_OK;
5218 *aIsCollapsed = mRanges[0].mRange->Collapsed();
5219 return NS_OK;
5222 NS_IMETHODIMP
5223 nsTypedSelection::GetRangeCount(PRInt32* aRangeCount)
5225 *aRangeCount = (PRInt32)mRanges.Length();
5227 return NS_OK;
5230 NS_IMETHODIMP
5231 nsTypedSelection::GetRangeAt(PRInt32 aIndex, nsIDOMRange** aReturn)
5233 *aReturn = mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
5234 if (!*aReturn) {
5235 return NS_ERROR_INVALID_ARG;
5238 NS_ADDREF(*aReturn);
5240 return NS_OK;
5243 nsIRange*
5244 nsTypedSelection::GetRangeAt(PRInt32 aIndex)
5246 return mRanges.SafeElementAt(aIndex, sEmptyData).mRange;
5250 utility function
5252 nsresult
5253 nsTypedSelection::CopyRangeToAnchorFocus(nsIRange *aRange)
5255 // XXXbz could we just clone into mAnchorFocusRange, or do consumers
5256 // expect that pointer to not change across this call?
5257 NS_ENSURE_STATE(mAnchorFocusRange);
5259 nsINode* startNode = aRange->GetStartParent();
5260 nsINode* endNode = aRange->GetEndParent();
5261 PRInt32 startOffset = aRange->StartOffset();
5262 PRInt32 endOffset = aRange->EndOffset();;
5263 if (NS_FAILED(mAnchorFocusRange->SetStart(startNode,startOffset)))
5265 // XXXbz what is this doing exactly?
5266 if (NS_FAILED(mAnchorFocusRange->SetEnd(endNode,endOffset)))
5267 return NS_ERROR_FAILURE;//???
5268 if (NS_FAILED(mAnchorFocusRange->SetStart(startNode,startOffset)))
5269 return NS_ERROR_FAILURE;//???
5271 else if (NS_FAILED(mAnchorFocusRange->SetEnd(endNode,endOffset)))
5272 return NS_ERROR_FAILURE;//???
5273 return NS_OK;
5276 void
5277 nsTypedSelection::ReplaceAnchorFocusRange(nsIRange *aRange)
5279 nsCOMPtr<nsPresContext> presContext;
5280 GetPresContext(getter_AddRefs(presContext));
5281 if (presContext) {
5282 selectFrames(presContext, mAnchorFocusRange, PR_FALSE);
5283 CopyRangeToAnchorFocus(aRange);
5284 selectFrames(presContext, mAnchorFocusRange, PR_TRUE);
5289 Notes which might come in handy for extend:
5291 We can tell the direction of the selection by asking for the anchors selection
5292 if the begin is less than the end then we know the selection is to the "right".
5293 else it is a backwards selection.
5294 a = anchor
5295 1 = old cursor
5296 2 = new cursor
5298 if (a <= 1 && 1 <=2) a,1,2 or (a1,2)
5299 if (a < 2 && 1 > 2) a,2,1
5300 if (1 < a && a <2) 1,a,2
5301 if (a > 2 && 2 >1) 1,2,a
5302 if (2 < a && a <1) 2,a,1
5303 if (a > 1 && 1 >2) 2,1,a
5304 then execute
5305 a 1 2 select from 1 to 2
5306 a 2 1 deselect from 2 to 1
5307 1 a 2 deselect from 1 to a select from a to 2
5308 1 2 a deselect from 1 to 2
5309 2 1 a = continue selection from 2 to 1
5314 * Extend extends the selection away from the anchor.
5315 * We don't need to know the direction, because we always change the focus.
5317 NS_IMETHODIMP
5318 nsTypedSelection::Extend(nsIDOMNode* aParentNode, PRInt32 aOffset)
5320 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
5321 return Extend(parentNode, aOffset);
5324 nsresult
5325 nsTypedSelection::Extend(nsINode* aParentNode, PRInt32 aOffset)
5327 if (!aParentNode)
5328 return NS_ERROR_INVALID_ARG;
5330 // First, find the range containing the old focus point:
5331 if (!mAnchorFocusRange)
5332 return NS_ERROR_NOT_INITIALIZED;
5334 if (!mFrameSelection)
5335 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
5337 nsresult res;
5338 if (!IsValidSelectionPoint(mFrameSelection, aParentNode))
5339 return NS_ERROR_FAILURE;
5341 //mFrameSelection->InvalidateDesiredX();
5342 nsCOMPtr<nsIRange> difRange = new nsRange();
5343 nsCOMPtr<nsIRange> range;
5345 nsINode* anchorNode = GetAnchorNode();
5346 nsINode* focusNode = GetFocusNode();
5347 PRInt32 anchorOffset = GetAnchorOffset();
5348 PRInt32 focusOffset = GetFocusOffset();
5350 if (focusNode == aParentNode && focusOffset == aOffset)
5351 return NS_OK; //same node nothing to do!
5353 res = mAnchorFocusRange->CloneRange(getter_AddRefs(range));
5354 if (NS_FAILED(res))
5355 return res;
5356 //range = mAnchorFocusRange;
5358 nsINode* startNode = range->GetStartParent();
5359 nsINode* endNode = range->GetEndParent();
5360 PRInt32 startOffset = range->StartOffset();
5361 PRInt32 endOffset = range->EndOffset();;
5363 nsDirection dir = GetDirection();
5365 //compare anchor to old cursor.
5367 if (NS_FAILED(res))
5368 return res;
5369 PRInt32 result1 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
5370 focusNode, focusOffset);
5371 //compare old cursor to new cursor
5372 PRInt32 result2 = nsContentUtils::ComparePoints(focusNode, focusOffset,
5373 aParentNode, aOffset);
5374 //compare anchor to new cursor
5375 PRInt32 result3 = nsContentUtils::ComparePoints(anchorNode, anchorOffset,
5376 aParentNode, aOffset);
5378 if (result2 == 0) //not selecting anywhere
5379 return NS_OK;
5381 nsCOMPtr<nsPresContext> presContext;
5382 GetPresContext(getter_AddRefs(presContext));
5383 if ((result1 == 0 && result3 < 0) || (result1 <= 0 && result2 < 0)){//a1,2 a,1,2
5384 //select from 1 to 2 unless they are collapsed
5385 res = range->SetEnd(aParentNode, aOffset);
5386 if (NS_FAILED(res))
5387 return res;
5388 dir = eDirNext;
5389 res = difRange->SetEnd(range->GetEndParent(), range->EndOffset());
5390 res |= difRange->SetStart(focusNode, focusOffset);
5391 if (NS_FAILED(res))
5392 return res;
5393 selectFrames(presContext, difRange , PR_TRUE);
5394 res = CopyRangeToAnchorFocus(range);
5395 if (NS_FAILED(res))
5396 return res;
5398 else if (result1 == 0 && result3 > 0){//2, a1
5399 //select from 2 to 1a
5400 dir = eDirPrevious;
5401 res = range->SetStart(aParentNode, aOffset);
5402 if (NS_FAILED(res))
5403 return res;
5404 selectFrames(presContext, range, PR_TRUE);
5405 res = CopyRangeToAnchorFocus(range);
5406 if (NS_FAILED(res))
5407 return res;
5409 else if (result3 <= 0 && result2 >= 0) {//a,2,1 or a2,1 or a,21 or a21
5410 //deselect from 2 to 1
5411 res = difRange->SetEnd(focusNode, focusOffset);
5412 res |= difRange->SetStart(aParentNode, aOffset);
5413 if (NS_FAILED(res))
5414 return res;
5416 res = range->SetEnd(aParentNode, aOffset);
5417 if (NS_FAILED(res))
5418 return res;
5419 res = CopyRangeToAnchorFocus(range);
5420 if (NS_FAILED(res))
5421 return res;
5422 selectFrames(presContext, difRange, PR_FALSE); // deselect now
5423 difRange->SetEnd(range->GetEndParent(), range->EndOffset());
5424 selectFrames(presContext, difRange, PR_TRUE); // must reselect last node maybe more
5426 else if (result1 >= 0 && result3 <= 0) {//1,a,2 or 1a,2 or 1,a2 or 1a2
5427 if (GetDirection() == eDirPrevious){
5428 res = range->SetStart(endNode, endOffset);
5429 if (NS_FAILED(res))
5430 return res;
5432 dir = eDirNext;
5433 res = range->SetEnd(aParentNode, aOffset);
5434 if (NS_FAILED(res))
5435 return res;
5436 if (focusNode != anchorNode || focusOffset != anchorOffset) {//if collapsed diff dont do anything
5437 res = difRange->SetStart(focusNode, focusOffset);
5438 res |= difRange->SetEnd(anchorNode, anchorOffset);
5439 if (NS_FAILED(res))
5440 return res;
5441 res = CopyRangeToAnchorFocus(range);
5442 if (NS_FAILED(res))
5443 return res;
5444 //deselect from 1 to a
5445 selectFrames(presContext, difRange , PR_FALSE);
5447 else
5449 res = CopyRangeToAnchorFocus(range);
5450 if (NS_FAILED(res))
5451 return res;
5453 //select from a to 2
5454 selectFrames(presContext, range , PR_TRUE);
5456 else if (result2 <= 0 && result3 >= 0) {//1,2,a or 12,a or 1,2a or 12a
5457 //deselect from 1 to 2
5458 res = difRange->SetEnd(aParentNode, aOffset);
5459 res |= difRange->SetStart(focusNode, focusOffset);
5460 if (NS_FAILED(res))
5461 return res;
5462 dir = eDirPrevious;
5463 res = range->SetStart(aParentNode, aOffset);
5464 if (NS_FAILED(res))
5465 return res;
5467 res = CopyRangeToAnchorFocus(range);
5468 if (NS_FAILED(res))
5469 return res;
5470 selectFrames(presContext, difRange , PR_FALSE);
5471 difRange->SetStart(range->GetStartParent(), range->StartOffset());
5472 selectFrames(presContext, difRange, PR_TRUE);//must reselect last node
5474 else if (result3 >= 0 && result1 <= 0) {//2,a,1 or 2a,1 or 2,a1 or 2a1
5475 if (GetDirection() == eDirNext){
5476 range->SetEnd(startNode, startOffset);
5478 dir = eDirPrevious;
5479 res = range->SetStart(aParentNode, aOffset);
5480 if (NS_FAILED(res))
5481 return res;
5482 //deselect from a to 1
5483 if (focusNode != anchorNode || focusOffset!= anchorOffset) {//if collapsed diff dont do anything
5484 res = difRange->SetStart(anchorNode, anchorOffset);
5485 res |= difRange->SetEnd(focusNode, focusOffset);
5486 res |= CopyRangeToAnchorFocus(range);
5487 if (NS_FAILED(res))
5488 return res;
5489 selectFrames(presContext, difRange, PR_FALSE);
5491 else
5493 res = CopyRangeToAnchorFocus(range);
5494 if (NS_FAILED(res))
5495 return res;
5497 //select from 2 to a
5498 selectFrames(presContext, range , PR_TRUE);
5500 else if (result2 >= 0 && result1 >= 0) {//2,1,a or 21,a or 2,1a or 21a
5501 //select from 2 to 1
5502 res = range->SetStart(aParentNode, aOffset);
5503 if (NS_FAILED(res))
5504 return res;
5505 dir = eDirPrevious;
5506 res = difRange->SetEnd(focusNode, focusOffset);
5507 res |= difRange->SetStart(range->GetStartParent(), range->StartOffset());
5508 if (NS_FAILED(res))
5509 return res;
5511 selectFrames(presContext, difRange, PR_TRUE);
5512 res = CopyRangeToAnchorFocus(range);
5513 if (NS_FAILED(res))
5514 return res;
5517 DEBUG_OUT_RANGE(range);
5518 #ifdef DEBUG_SELECTION
5519 if (eDirNext == mDirection)
5520 printf(" direction = 1 LEFT TO RIGHT\n");
5521 else
5522 printf(" direction = 0 RIGHT TO LEFT\n");
5523 #endif
5524 SetDirection(dir);
5525 #ifdef DEBUG_SELECTION
5526 if (aParentNode)
5528 nsCOMPtr<nsIContent>content;
5529 content = do_QueryInterface(aParentNode);
5531 const char *tagString;
5532 content->Tag()->GetUTF8String(&tagString);
5533 printf ("Sel. Extend to %p %s %d\n", content.get(), tagString, aOffset);
5535 else {
5536 printf ("Sel. Extend set to null parent.\n");
5538 #endif
5539 return mFrameSelection->NotifySelectionListeners(GetType());
5542 static nsresult
5543 GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, PRInt32 &aOffset)
5545 NS_ASSERTION((aChild && aParent), "bad args");
5546 nsCOMPtr<nsIContent> content = do_QueryInterface(aParent);
5547 nsCOMPtr<nsIContent> cChild = do_QueryInterface(aChild);
5549 if (!cChild || !content)
5550 return NS_ERROR_NULL_POINTER;
5552 aOffset = content->IndexOf(cChild);
5554 return NS_OK;
5557 NS_IMETHODIMP
5558 nsTypedSelection::SelectAllChildren(nsIDOMNode* aParentNode)
5560 NS_ENSURE_ARG_POINTER(aParentNode);
5562 if (mFrameSelection)
5564 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5566 nsresult result = Collapse(aParentNode, 0);
5567 if (NS_SUCCEEDED(result))
5569 nsCOMPtr<nsIDOMNode>lastChild;
5570 result = aParentNode->GetLastChild(getter_AddRefs(lastChild));
5571 if ((NS_SUCCEEDED(result)) && lastChild)
5573 PRInt32 numBodyChildren=0;
5574 GetChildOffset(lastChild, aParentNode, numBodyChildren);
5575 if (mFrameSelection)
5577 mFrameSelection->PostReason(nsISelectionListener::SELECTALL_REASON);
5579 result = Extend(aParentNode, numBodyChildren+1);
5582 return result;
5585 NS_IMETHODIMP
5586 nsTypedSelection::ContainsNode(nsIDOMNode* aNode, PRBool aAllowPartial,
5587 PRBool* aYes)
5589 nsresult rv;
5590 if (!aYes)
5591 return NS_ERROR_NULL_POINTER;
5592 *aYes = PR_FALSE;
5594 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
5595 if (mRanges.Length() == 0 || !node)
5596 return NS_OK;
5598 // XXXbz this duplicates the GetNodeLength code in nsRange.cpp
5599 PRUint32 nodeLength;
5600 PRBool isData = node->IsNodeOfType(nsINode::eDATA_NODE);
5601 if (isData) {
5602 nodeLength = static_cast<nsIContent*>(node.get())->TextLength();
5603 } else {
5604 nodeLength = node->GetChildCount();
5607 nsCOMArray<nsIRange> overlappingRanges;
5608 rv = GetRangesForIntervalCOMArray(node, 0, node, nodeLength,
5609 PR_FALSE, &overlappingRanges);
5610 NS_ENSURE_SUCCESS(rv, rv);
5611 if (overlappingRanges.Count() == 0)
5612 return NS_OK; // no ranges overlap
5614 // if the caller said partial intersections are OK, we're done
5615 if (aAllowPartial) {
5616 *aYes = PR_TRUE;
5617 return NS_OK;
5620 // text nodes always count as inside
5621 if (isData) {
5622 *aYes = PR_TRUE;
5623 return NS_OK;
5626 // The caller wants to know if the node is entirely within the given range,
5627 // so we have to check all intersecting ranges.
5628 for (PRInt32 i = 0; i < overlappingRanges.Count(); i ++) {
5629 PRBool nodeStartsBeforeRange, nodeEndsAfterRange;
5630 if (NS_SUCCEEDED(nsRange::CompareNodeToRange(node, overlappingRanges[i],
5631 &nodeStartsBeforeRange,
5632 &nodeEndsAfterRange))) {
5633 if (!nodeStartsBeforeRange && !nodeEndsAfterRange) {
5634 *aYes = PR_TRUE;
5635 return NS_OK;
5639 return NS_OK;
5643 nsresult
5644 nsTypedSelection::GetPresContext(nsPresContext **aPresContext)
5646 if (!mFrameSelection)
5647 return NS_ERROR_FAILURE;//nothing to do
5648 nsIPresShell *shell = mFrameSelection->GetShell();
5650 if (!shell)
5651 return NS_ERROR_NULL_POINTER;
5653 NS_IF_ADDREF(*aPresContext = shell->GetPresContext());
5654 return NS_OK;
5657 nsresult
5658 nsTypedSelection::GetPresShell(nsIPresShell **aPresShell)
5660 if (mPresShellWeak)
5662 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShellWeak);
5663 if (presShell)
5664 NS_ADDREF(*aPresShell = presShell);
5665 return NS_OK;
5667 nsresult rv = NS_OK;
5668 if (!mFrameSelection)
5669 return NS_ERROR_FAILURE;//nothing to do
5671 nsIPresShell *shell = mFrameSelection->GetShell();
5673 mPresShellWeak = do_GetWeakReference(shell); // the presshell owns us, so no addref
5674 if (mPresShellWeak)
5675 NS_ADDREF(*aPresShell = shell);
5676 return rv;
5679 nsresult
5680 nsTypedSelection::GetRootScrollableView(nsIScrollableView **aScrollableView)
5683 // NOTE: This method returns a NON-AddRef'd pointer
5684 // to the scrollable view!
5686 NS_ENSURE_ARG_POINTER(aScrollableView);
5688 if (!mFrameSelection)
5689 return NS_ERROR_FAILURE;//nothing to do
5691 nsIScrollableView *scrollView = mFrameSelection->GetScrollableView();
5692 if (!scrollView)
5694 nsCOMPtr<nsIPresShell> presShell;
5696 nsresult rv = GetPresShell(getter_AddRefs(presShell));
5698 if (NS_FAILED(rv))
5699 return rv;
5701 if (!presShell)
5702 return NS_ERROR_NULL_POINTER;
5704 nsIViewManager* viewManager = presShell->GetViewManager();
5706 if (!viewManager)
5707 return NS_ERROR_NULL_POINTER;
5710 // nsIViewManager::GetRootScrollableView() does not
5711 // AddRef the pointer it returns.
5713 return viewManager->GetRootScrollableView(aScrollableView);
5716 *aScrollableView = scrollView;
5717 return NS_OK;
5720 nsresult
5721 nsTypedSelection::GetFrameToScrolledViewOffsets(nsIScrollableView *aScrollableView, nsIFrame *aFrame, nscoord *aX, nscoord *aY)
5723 nsresult rv = NS_OK;
5724 if (!mFrameSelection)
5725 return NS_ERROR_FAILURE;//nothing to do
5727 if (!aScrollableView || !aFrame || !aX || !aY) {
5728 return NS_ERROR_NULL_POINTER;
5731 *aX = 0;
5732 *aY = 0;
5734 nsIView* scrolledView;
5735 nsPoint offset;
5736 nsIView* closestView;
5738 // Determine the offset from aFrame to the scrolled view. We do that by
5739 // getting the offset from its closest view and then walking up
5740 aScrollableView->GetScrolledView(scrolledView);
5741 nsIPresShell *shell = mFrameSelection->GetShell();
5743 if (!shell)
5744 return NS_ERROR_NULL_POINTER;
5746 aFrame->GetOffsetFromView(offset, &closestView);
5748 // XXX Deal with the case where there is a scrolled element, e.g., a
5749 // DIV in the middle...
5750 offset += closestView->GetOffsetTo(scrolledView);
5752 *aX = offset.x;
5753 *aY = offset.y;
5755 return rv;
5758 nsresult
5759 nsTypedSelection::GetPointFromOffset(nsIFrame *aFrame, PRInt32 aContentOffset, nsPoint *aPoint)
5761 nsresult rv = NS_OK;
5762 if (!mFrameSelection)
5763 return NS_ERROR_FAILURE;//nothing to do
5764 if (!aFrame || !aPoint)
5765 return NS_ERROR_NULL_POINTER;
5767 aPoint->x = 0;
5768 aPoint->y = 0;
5771 // Now get the closest view with a widget so we can create
5772 // a rendering context.
5775 nsIWidget* widget = nsnull;
5776 nsIView *closestView = nsnull;
5777 nsPoint offset(0, 0);
5779 rv = aFrame->GetOffsetFromView(offset, &closestView);
5781 while (!widget && closestView)
5783 widget = closestView->GetWidget();
5785 if (!widget)
5787 closestView = closestView->GetParent();
5791 if (!closestView)
5792 return NS_ERROR_FAILURE;
5795 // Now get the point and return!
5798 rv = aFrame->GetPointFromOffset(aContentOffset, aPoint);
5800 return rv;
5803 nsresult
5804 nsTypedSelection::GetSelectionRegionRectAndScrollableView(SelectionRegion aRegion, nsRect *aRect, nsIScrollableView **aScrollableView)
5806 if (!mFrameSelection)
5807 return NS_ERROR_FAILURE; // nothing to do
5809 NS_ENSURE_TRUE(aRect && aScrollableView, NS_ERROR_NULL_POINTER);
5811 aRect->SetRect(0, 0, 0, 0);
5812 *aScrollableView = nsnull;
5814 nsINode *node = nsnull;
5815 PRInt32 nodeOffset = 0;
5816 nsIFrame *frame = nsnull;
5818 switch (aRegion) {
5819 case nsISelectionController::SELECTION_ANCHOR_REGION:
5820 node = GetAnchorNode();
5821 nodeOffset = GetAnchorOffset();
5822 break;
5823 case nsISelectionController::SELECTION_FOCUS_REGION:
5824 node = GetFocusNode();
5825 nodeOffset = GetFocusOffset();
5826 break;
5827 default:
5828 return NS_ERROR_FAILURE;
5831 if (!node)
5832 return NS_ERROR_NULL_POINTER;
5834 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
5835 NS_ENSURE_TRUE(content.get(), NS_ERROR_FAILURE);
5836 PRInt32 frameOffset = 0;
5837 frame = mFrameSelection->GetFrameForNodeOffset(content, nodeOffset,
5838 mFrameSelection->GetHint(),
5839 &frameOffset);
5840 if (!frame)
5841 return NS_ERROR_FAILURE;
5843 // Get the frame's nearest scrollable view.
5844 nsIFrame* parentWithView = frame->GetAncestorWithView();
5845 if (!parentWithView)
5846 return NS_ERROR_FAILURE;
5847 nsIView* view = parentWithView->GetView();
5848 *aScrollableView =
5849 nsLayoutUtils::GetNearestScrollingView(view, nsLayoutUtils::eEither);
5850 if (!*aScrollableView)
5851 return NS_OK;
5853 // Figure out what node type we have, then get the
5854 // appropriate rect for it's nodeOffset.
5855 PRBool isText = node->IsNodeOfType(nsINode::eTEXT);
5857 nsPoint pt(0, 0);
5858 if (isText) {
5859 nsIFrame* childFrame = nsnull;
5860 frameOffset = 0;
5861 nsresult rv =
5862 frame->GetChildFrameContainingOffset(nodeOffset,
5863 mFrameSelection->GetHint(),
5864 &frameOffset, &childFrame);
5865 if (NS_FAILED(rv))
5866 return rv;
5867 if (!childFrame)
5868 return NS_ERROR_NULL_POINTER;
5870 frame = childFrame;
5872 // Get the x coordinate of the offset into the text frame.
5873 rv = GetCachedFrameOffset(frame, nodeOffset, pt);
5874 if (NS_FAILED(rv))
5875 return rv;
5878 // Get the frame's rect in scroll view coordinates.
5879 *aRect = frame->GetRect();
5880 nsresult rv = GetFrameToScrolledViewOffsets(*aScrollableView, frame,
5881 &aRect->x, &aRect->y);
5882 NS_ENSURE_SUCCESS(rv, rv);
5884 if (isText) {
5885 aRect->x += pt.x;
5887 else if (mFrameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
5888 // It's the frame's right edge we're interested in.
5889 aRect->x += aRect->width;
5892 nsRect clipRect = (*aScrollableView)->View()->GetBounds();
5893 rv = (*aScrollableView)->GetScrollPosition(clipRect.x, clipRect.y);
5894 NS_ENSURE_SUCCESS(rv, rv);
5896 // If the point we are interested in is outside the clip region, we aim
5897 // to over-scroll it by a quarter of the clip's width.
5898 PRInt32 pad = clipRect.width / 4;
5900 if (pad == 0)
5901 pad = 3; // Arbitrary
5903 if (aRect->x >= clipRect.XMost()) {
5904 aRect->width = pad;
5906 else if (aRect->x <= clipRect.x) {
5907 aRect->x -= pad;
5908 aRect->width = pad;
5910 else {
5911 aRect->width = 60; // Arbitrary
5914 return rv;
5917 static void
5918 ClampPointInsideRect(nsPoint& aPoint, const nsRect& aRect)
5920 if (aPoint.x < aRect.x)
5921 aPoint.x = aRect.x;
5922 if (aPoint.x > aRect.XMost())
5923 aPoint.x = aRect.XMost();
5924 if (aPoint.y < aRect.y)
5925 aPoint.y = aRect.y;
5926 if (aPoint.y > aRect.YMost())
5927 aPoint.y = aRect.YMost();
5930 nsresult
5931 nsTypedSelection::ScrollRectIntoView(nsIScrollableView *aScrollableView,
5932 nsRect& aRect,
5933 PRIntn aVPercent,
5934 PRIntn aHPercent,
5935 PRBool aScrollParentViews)
5937 nsresult rv = NS_OK;
5938 if (!mFrameSelection)
5939 return NS_OK;//nothing to do
5941 if (!aScrollableView)
5942 return NS_ERROR_NULL_POINTER;
5944 // Determine the visible rect in the scrolled view's coordinate space.
5945 // The size of the visible area is the clip view size
5946 nsRect visibleRect = aScrollableView->View()->GetBounds();
5947 aScrollableView->GetScrollPosition(visibleRect.x, visibleRect.y);
5949 // The actual scroll offsets
5950 nscoord scrollOffsetX = visibleRect.x;
5951 nscoord scrollOffsetY = visibleRect.y;
5953 nsPresContext::ScrollbarStyles ss =
5954 nsLayoutUtils::ScrollbarStylesOfView(aScrollableView);
5956 // See how aRect should be positioned vertically
5957 if (ss.mVertical != NS_STYLE_OVERFLOW_HIDDEN) {
5958 if (NS_PRESSHELL_SCROLL_ANYWHERE == aVPercent) {
5959 // The caller doesn't care where aRect is positioned vertically,
5960 // so long as it's fully visible
5961 if (aRect.y < visibleRect.y) {
5962 // Scroll up so aRect's top edge is visible
5963 scrollOffsetY = aRect.y;
5964 } else if (aRect.YMost() > visibleRect.YMost()) {
5965 // Scroll down so aRect's bottom edge is visible. Make sure
5966 // aRect's top edge is still visible
5967 scrollOffsetY += aRect.YMost() - visibleRect.YMost();
5968 if (scrollOffsetY > aRect.y) {
5969 scrollOffsetY = aRect.y;
5972 } else {
5973 // Align the aRect edge according to the specified percentage
5974 nscoord frameAlignY = aRect.y + (aRect.height * aVPercent) / 100;
5975 scrollOffsetY = frameAlignY - (visibleRect.height * aVPercent) / 100;
5979 // See how the aRect should be positioned horizontally
5980 if (ss.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN) {
5981 if (NS_PRESSHELL_SCROLL_ANYWHERE == aHPercent) {
5982 // The caller doesn't care where the aRect is positioned horizontally,
5983 // so long as it's fully visible
5984 if (aRect.x < visibleRect.x) {
5985 // Scroll left so the aRect's left edge is visible
5986 scrollOffsetX = aRect.x;
5987 } else if (aRect.XMost() > visibleRect.XMost()) {
5988 // Scroll right so the aRect's right edge is visible. Make sure the
5989 // aRect's left edge is still visible
5990 scrollOffsetX += aRect.XMost() - visibleRect.XMost();
5991 if (scrollOffsetX > aRect.x) {
5992 scrollOffsetX = aRect.x;
5996 } else {
5997 // Align the aRect edge according to the specified percentage
5998 nscoord frameAlignX = aRect.x + (aRect.width * aHPercent) / 100;
5999 scrollOffsetX = frameAlignX - (visibleRect.width * aHPercent) / 100;
6003 aScrollableView->ScrollTo(scrollOffsetX, scrollOffsetY, 0);
6005 if (aScrollParentViews)
6008 // Get aScrollableView's scrolled view.
6011 nsIView *scrolledView = 0;
6013 rv = aScrollableView->GetScrolledView(scrolledView);
6015 if (NS_FAILED(rv))
6016 return rv;
6018 if (!scrolledView)
6019 return NS_ERROR_FAILURE;
6022 // Check if aScrollableRect has a parent scrollable view!
6025 nsIView *view = aScrollableView->View()->GetParent();
6027 if (view)
6029 nsIScrollableView *parentSV =
6030 nsLayoutUtils::GetNearestScrollingView(view, nsLayoutUtils::eEither);
6032 if (parentSV)
6035 // Clip the x dimensions of aRect so that they are
6036 // completely within the bounds of the scrolledView.
6037 // This helps avoid unnecessary scrolling of parent
6038 // scrolled views.
6040 nsRect svRect = scrolledView->GetBounds() - scrolledView->GetPosition();
6041 nsPoint topLeft = aRect.TopLeft();
6042 nsPoint bottomRight = aRect.BottomRight();
6043 ClampPointInsideRect(topLeft, svRect);
6044 ClampPointInsideRect(bottomRight, svRect);
6045 nsRect newRect(topLeft.x, topLeft.y, bottomRight.x - topLeft.x,
6046 bottomRight.y - topLeft.y);
6049 // We have a parent scrollable view, so now map aRect
6050 // into it's scrolled view's coordinate space.
6053 rv = parentSV->GetScrolledView(view);
6055 if (NS_FAILED(rv))
6056 return rv;
6058 if (!view)
6059 return NS_ERROR_FAILURE;
6061 nscoord offsetX, offsetY;
6062 rv = GetViewAncestorOffset(scrolledView, view, &offsetX, &offsetY);
6064 if (NS_FAILED(rv))
6065 return rv;
6067 newRect.x += offsetX;
6068 newRect.y += offsetY;
6071 // Now scroll the rect into the parent's view.
6074 rv = ScrollRectIntoView(parentSV, newRect, aVPercent, aHPercent, aScrollParentViews);
6079 return rv;
6082 NS_IMETHODIMP
6083 nsTypedSelection::ScrollSelectionIntoViewEvent::Run()
6085 if (!mTypedSelection)
6086 return NS_OK; // event revoked
6088 mTypedSelection->mScrollEvent.Forget();
6089 mTypedSelection->ScrollIntoView(mRegion, PR_TRUE, PR_TRUE);
6090 return NS_OK;
6093 nsresult
6094 nsTypedSelection::PostScrollSelectionIntoViewEvent(SelectionRegion aRegion)
6096 // If we've already posted an event, revoke it and place a new one at the
6097 // end of the queue to make sure that any new pending reflow events are
6098 // processed before we scroll. This will insure that we scroll to the
6099 // correct place on screen.
6100 mScrollEvent.Revoke();
6102 nsRefPtr<ScrollSelectionIntoViewEvent> ev =
6103 new ScrollSelectionIntoViewEvent(this, aRegion);
6104 nsresult rv = NS_DispatchToCurrentThread(ev);
6105 NS_ENSURE_SUCCESS(rv, rv);
6107 mScrollEvent = ev;
6108 return NS_OK;
6111 NS_IMETHODIMP
6112 nsTypedSelection::ScrollIntoView(SelectionRegion aRegion, PRBool aIsSynchronous,
6113 PRInt16 aVPercent, PRInt16 aHPercent)
6115 return ScrollIntoView(aRegion, aIsSynchronous, PR_FALSE,
6116 aVPercent, aHPercent);
6119 nsresult
6120 nsTypedSelection::ScrollIntoView(SelectionRegion aRegion,
6121 PRBool aIsSynchronous, PRBool aDoFlush,
6122 PRInt16 aVPercent, PRInt16 aHPercent)
6124 nsresult result;
6125 if (!mFrameSelection)
6126 return NS_OK;//nothing to do
6128 if (mFrameSelection->GetBatching())
6129 return NS_OK;
6131 if (!aIsSynchronous)
6132 return PostScrollSelectionIntoViewEvent(aRegion);
6135 // Shut the caret off before scrolling to avoid
6136 // leaving caret turds on the screen!
6138 nsCOMPtr<nsIPresShell> presShell;
6139 result = GetPresShell(getter_AddRefs(presShell));
6140 if (NS_FAILED(result) || !presShell)
6141 return result;
6142 nsRefPtr<nsCaret> caret;
6143 presShell->GetCaret(getter_AddRefs(caret));
6144 if (caret)
6146 // Now that text frame character offsets are always valid (though not
6147 // necessarily correct), the worst that will happen if we don't flush here
6148 // is that some callers might scroll to the wrong place. Those should
6149 // either manually flush if they're in a safe position for it or use the
6150 // async version of this method.
6151 if (aDoFlush) {
6152 presShell->FlushPendingNotifications(Flush_Layout);
6154 // Reget the presshell, since it might have gone away.
6155 result = GetPresShell(getter_AddRefs(presShell));
6156 if (NS_FAILED(result) || !presShell)
6157 return result;
6160 StCaretHider caretHider(caret); // stack-based class hides and shows the caret
6163 // Scroll the selection region into view.
6166 nsRect rect;
6167 nsIScrollableView *scrollableView = 0;
6169 result = GetSelectionRegionRectAndScrollableView(aRegion, &rect, &scrollableView);
6171 if (NS_FAILED(result))
6172 return result;
6175 // It's ok if we don't have a scrollable view, just return early.
6177 if (!scrollableView)
6178 return NS_OK;
6180 result = ScrollRectIntoView(scrollableView, rect, aVPercent, aHPercent,
6181 PR_TRUE);
6183 return result;
6188 NS_IMETHODIMP
6189 nsTypedSelection::AddSelectionListener(nsISelectionListener* aNewListener)
6191 if (!aNewListener)
6192 return NS_ERROR_NULL_POINTER;
6193 return mSelectionListeners.AppendObject(aNewListener) ? NS_OK : NS_ERROR_FAILURE; // addrefs
6198 NS_IMETHODIMP
6199 nsTypedSelection::RemoveSelectionListener(nsISelectionListener* aListenerToRemove)
6201 if (!aListenerToRemove )
6202 return NS_ERROR_NULL_POINTER;
6203 return mSelectionListeners.RemoveObject(aListenerToRemove) ? NS_OK : NS_ERROR_FAILURE; // releases
6207 nsresult
6208 nsTypedSelection::NotifySelectionListeners()
6210 if (!mFrameSelection)
6211 return NS_OK;//nothing to do
6213 if (mFrameSelection->GetBatching()){
6214 mFrameSelection->SetDirty();
6215 return NS_OK;
6217 PRInt32 cnt = mSelectionListeners.Count();
6218 nsCOMArray<nsISelectionListener> selectionListeners(mSelectionListeners);
6220 nsCOMPtr<nsIDOMDocument> domdoc;
6221 nsCOMPtr<nsIPresShell> shell;
6222 nsresult rv = GetPresShell(getter_AddRefs(shell));
6223 if (NS_SUCCEEDED(rv) && shell)
6224 domdoc = do_QueryInterface(shell->GetDocument());
6225 short reason = mFrameSelection->PopReason();
6226 for (PRInt32 i = 0; i < cnt; i++)
6228 nsISelectionListener* thisListener = selectionListeners[i];
6229 if (thisListener)
6230 thisListener->NotifySelectionChanged(domdoc, this, reason);
6232 return NS_OK;
6235 NS_IMETHODIMP
6236 nsTypedSelection::StartBatchChanges()
6238 if (mFrameSelection)
6239 mFrameSelection->StartBatchChanges();
6241 return NS_OK;
6246 NS_IMETHODIMP
6247 nsTypedSelection::EndBatchChanges()
6249 if (mFrameSelection)
6250 mFrameSelection->EndBatchChanges();
6252 return NS_OK;
6257 NS_IMETHODIMP
6258 nsTypedSelection::DeleteFromDocument()
6260 if (!mFrameSelection)
6261 return NS_OK;//nothing to do
6262 return mFrameSelection->DeleteFromDocument();
6265 /** SelectionLanguageChange modifies the cursor Bidi level after a change in keyboard direction
6266 * @param aLangRTL is PR_TRUE if the new language is right-to-left or PR_FALSE if the new language is left-to-right
6268 NS_IMETHODIMP
6269 nsTypedSelection::SelectionLanguageChange(PRBool aLangRTL)
6271 if (!mFrameSelection)
6272 return NS_ERROR_NOT_INITIALIZED; // Can't do selection
6273 nsresult result;
6274 nsIFrame *focusFrame = 0;
6276 result = GetPrimaryFrameForFocusNode(&focusFrame, nsnull, PR_FALSE);
6277 if (NS_FAILED(result)) {
6278 return result;
6280 if (!focusFrame) {
6281 return NS_ERROR_FAILURE;
6284 PRInt32 frameStart, frameEnd;
6285 focusFrame->GetOffsets(frameStart, frameEnd);
6286 nsCOMPtr<nsPresContext> context;
6287 PRUint8 levelBefore, levelAfter;
6288 result = GetPresContext(getter_AddRefs(context));
6289 if (NS_FAILED(result) || !context)
6290 return result?result:NS_ERROR_FAILURE;
6292 PRUint8 level = NS_GET_EMBEDDING_LEVEL(focusFrame);
6293 PRInt32 focusOffset = GetFocusOffset();
6294 if ((focusOffset != frameStart) && (focusOffset != frameEnd))
6295 // the cursor is not at a frame boundary, so the level of both the characters (logically) before and after the cursor
6296 // is equal to the frame level
6297 levelBefore = levelAfter = level;
6298 else {
6299 // the cursor is at a frame boundary, so use GetPrevNextBidiLevels to find the level of the characters
6300 // before and after the cursor
6301 nsCOMPtr<nsIContent> focusContent = do_QueryInterface(GetFocusNode());
6303 nsFrameSelection::HINT hint;
6305 if ((focusOffset == frameStart && level) // beginning of an RTL frame
6306 || (focusOffset == frameEnd && !level)) { // end of an LTR frame
6307 hint = nsFrameSelection::HINTRIGHT;
6309 else { // end of an RTL frame or beginning of an LTR frame
6310 hint = nsFrameSelection::HINTLEFT;
6312 mFrameSelection->SetHint(hint);
6314 nsPrevNextBidiLevels levels = mFrameSelection->
6315 GetPrevNextBidiLevels(focusContent, focusOffset, PR_FALSE);
6317 levelBefore = levels.mLevelBefore;
6318 levelAfter = levels.mLevelAfter;
6321 if ((levelBefore & 1) == (levelAfter & 1)) {
6322 // if cursor is between two characters with the same orientation, changing the keyboard language
6323 // must toggle the cursor level between the level of the character with the lowest level
6324 // (if the new language corresponds to the orientation of that character) and this level plus 1
6325 // (if the new language corresponds to the opposite orientation)
6326 if ((level != levelBefore) && (level != levelAfter))
6327 level = PR_MIN(levelBefore, levelAfter);
6328 if ((level & 1) == aLangRTL)
6329 mFrameSelection->SetCaretBidiLevel(level);
6330 else
6331 mFrameSelection->SetCaretBidiLevel(level + 1);
6333 else {
6334 // if cursor is between characters with opposite orientations, changing the keyboard language must change
6335 // the cursor level to that of the adjacent character with the orientation corresponding to the new language.
6336 if ((levelBefore & 1) == aLangRTL)
6337 mFrameSelection->SetCaretBidiLevel(levelBefore);
6338 else
6339 mFrameSelection->SetCaretBidiLevel(levelAfter);
6342 // The caret might have moved, so invalidate the desired X position
6343 // for future usages of up-arrow or down-arrow
6344 mFrameSelection->InvalidateDesiredX();
6346 return NS_OK;
6350 // nsAutoCopyListener
6352 nsAutoCopyListener* nsAutoCopyListener::sInstance = nsnull;
6354 NS_IMPL_ISUPPORTS1(nsAutoCopyListener, nsISelectionListener)
6357 * What we do now:
6358 * On every selection change, we copy to the clipboard anew, creating a
6359 * HTML buffer, a transferable, an nsISupportsString and
6360 * a huge mess every time. This is basically what nsPresShell::DoCopy does
6361 * to move the selection into the clipboard for Edit->Copy.
6363 * What we should do, to make our end of the deal faster:
6364 * Create a singleton transferable with our own magic converter. When selection
6365 * changes (use a quick cache to detect ``real'' changes), we put the new
6366 * nsISelection in the transferable. Our magic converter will take care of
6367 * transferable->whatever-other-format when the time comes to actually
6368 * hand over the clipboard contents.
6370 * Other issues:
6371 * - which X clipboard should we populate?
6372 * - should we use a different one than Edit->Copy, so that inadvertant
6373 * selections (or simple clicks, which currently cause a selection
6374 * notification, regardless of if they're in the document which currently has
6375 * selection!) don't lose the contents of the ``application''? Or should we
6376 * just put some intelligence in the ``is this a real selection?'' code to
6377 * protect our selection against clicks in other documents that don't create
6378 * selections?
6379 * - maybe we should just never clear the X clipboard? That would make this
6380 * problem just go away, which is very tempting.
6383 NS_IMETHODIMP
6384 nsAutoCopyListener::NotifySelectionChanged(nsIDOMDocument *aDoc,
6385 nsISelection *aSel, PRInt16 aReason)
6387 if (!(aReason & nsISelectionListener::MOUSEUP_REASON ||
6388 aReason & nsISelectionListener::SELECTALL_REASON ||
6389 aReason & nsISelectionListener::KEYPRESS_REASON))
6390 return NS_OK; //dont care if we are still dragging
6392 PRBool collapsed;
6393 if (!aDoc || !aSel ||
6394 NS_FAILED(aSel->GetIsCollapsed(&collapsed)) || collapsed) {
6395 #ifdef DEBUG_CLIPBOARD
6396 fprintf(stderr, "CLIPBOARD: no selection/collapsed selection\n");
6397 #endif
6398 /* clear X clipboard? */
6399 return NS_OK;
6402 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
6403 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
6405 // call the copy code
6406 return nsCopySupport::HTMLCopy(aSel, doc, nsIClipboard::kSelectionClipboard);