diff --git a/content/base/src/nsSelection.cpp b/content/base/src/nsSelection.cpp index 8a493765e398..1769c961cc93 100644 --- a/content/base/src/nsSelection.cpp +++ b/content/base/src/nsSelection.cpp @@ -373,8 +373,8 @@ public: nsDirection aDirection, PRUint8 aBidiLevel, nsIFrame **aFrameOut); - - /*END nsIFrameSelection interfacse*/ + NS_IMETHOD MaintainSelection(); + /*END nsIFrameSelection interfaces */ @@ -422,6 +422,8 @@ private: nsPeekOffsetStruct aPos); #endif // VISUALSELECTION + PRBool AdjustForMaintainedSelection(nsIContent *aContent, PRInt32 aOffset); + // post and pop reasons for notifications. we may stack these later void PostReason(PRInt16 aReason) { mSelectionChangeReason = aReason; } PRInt16 PopReason() @@ -486,6 +488,9 @@ private: PRInt32 mSelectingTableCellMode; PRInt32 mSelectedCellIndex; + // maintain selection + nsCOMPtr mMaintainRange; + //batching PRInt32 mBatching; @@ -2451,6 +2456,37 @@ NS_IMETHODIMP nsSelection::GetFrameFromLevel(nsIPresContext *aPresContext, return NS_OK; } + +NS_IMETHODIMP +nsSelection::MaintainSelection() +{ + PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); + nsCOMPtr range; + nsresult rv = mDomSelections[index]->GetRangeAt(0, getter_AddRefs(range)); + if (NS_FAILED(rv)) + return rv; + if (!range) + return NS_ERROR_FAILURE; + + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset; + PRInt32 endOffset; + range->GetStartContainer(getter_AddRefs(startNode)); + range->GetEndContainer(getter_AddRefs(endNode)); + range->GetStartOffset(&startOffset); + range->GetEndOffset(&endOffset); + + mMaintainRange = nsnull; + NS_NewRange(getter_AddRefs(mMaintainRange)); + if (!mMaintainRange) + return NS_ERROR_OUT_OF_MEMORY; + + mMaintainRange->SetStart(startNode, startOffset); + return mMaintainRange->SetEnd(endNode, endOffset); +} + + /** After moving the caret, its Bidi level is set according to the following rules: * * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character. @@ -2538,6 +2574,62 @@ void nsSelection::BidiLevelFromClick(nsIContent *aNode, PRUint32 aContentOffset) shell->SetCaretBidiLevel(frameLevel); } + +PRBool +nsSelection::AdjustForMaintainedSelection(nsIContent *aContent, PRInt32 aOffset) +{ + // Is the desired content and offset currently in selection? + // If the double click flag is set then don't continue selection if the + // desired content and offset are currently inside a selection. + // This will stop double click then mouse-drag from undoing the desired + // selecting of a word. + if (!mMaintainRange) + return PR_FALSE; + + nsCOMPtr rangenode; + PRInt32 rangeOffset; + mMaintainRange->GetStartContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetStartOffset(&rangeOffset); + + nsCOMPtr domNode = do_QueryInterface(aContent); + if (domNode) + { + PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); + nsCOMPtr nsrange = do_QueryInterface(mMaintainRange); + if (nsrange) + { + PRBool insideSelection = PR_FALSE; + nsrange->IsPointInRange(domNode, aOffset, &insideSelection); + + // Done when we find a range that we are in + if (insideSelection) + { + mDomSelections[index]->Collapse(rangenode, rangeOffset); + mMaintainRange->GetEndContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetEndOffset(&rangeOffset); + mDomSelections[index]->Extend(rangenode,rangeOffset); + return PR_TRUE; // dragging in selection aborted + } + } + + PRInt32 relativePosition = ComparePoints(rangenode, rangeOffset, domNode, aOffset); + // if == 0 or -1 do nothing if < 0 then we need to swap direction + if (relativePosition > 0 + && (mDomSelections[index]->GetDirection() == eDirNext)) + { + mMaintainRange->GetEndContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetEndOffset(&rangeOffset); + mDomSelections[index]->Collapse(rangenode, rangeOffset); + } + else if (relativePosition < 0 + && (mDomSelections[index]->GetDirection() == eDirPrevious)) + mDomSelections[index]->Collapse(rangenode, rangeOffset); + } + + return PR_FALSE; +} + + NS_IMETHODIMP nsSelection::HandleClick(nsIContent *aNewFocus, PRUint32 aContentOffset, PRUint32 aContentEndOffset, PRBool aContinueSelection, @@ -2545,14 +2637,22 @@ nsSelection::HandleClick(nsIContent *aNewFocus, PRUint32 aContentOffset, { if (!aNewFocus) return NS_ERROR_INVALID_ARG; + InvalidateDesiredX(); - + + if (!aContinueSelection) + mMaintainRange = nsnull; + mHint = HINT(aHint); // Don't take focus when dragging off of a table if (!mDragSelectingCells) { BidiLevelFromClick(aNewFocus, aContentOffset); PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON); + if (aContinueSelection && + AdjustForMaintainedSelection(aNewFocus, aContentOffset)) + return NS_OK; //shift clicked to maintained selection. rejected. + return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, aContinueSelection, aMultipleSelection); } @@ -2569,22 +2669,12 @@ nsSelection::HandleDrag(nsIPresContext *aPresContext, nsIFrame *aFrame, nsPoint& nsIFrame *newFrame = 0; nsPoint newPoint; - result = ConstrainFrameAndPointToAnchorSubtree(aPresContext, aFrame, aPoint, &newFrame, newPoint); - if (NS_FAILED(result)) return result; - if (!newFrame) return NS_ERROR_FAILURE; - nsCOMPtr presShell; - - result = aPresContext->GetShell(getter_AddRefs(presShell)); - - if (NS_FAILED(result)) - return result; - PRInt32 startPos = 0; PRInt32 contentOffsetEnd = 0; PRBool beginOfContent; @@ -2594,57 +2684,9 @@ nsSelection::HandleDrag(nsIPresContext *aPresContext, nsIFrame *aFrame, nsPoint& getter_AddRefs(newContent), startPos, contentOffsetEnd, beginOfContent); - //Is the desired content and offset currently in selection? - //if the double click flag is set then dont continue selection if the desired content and offset - //are currently inside a selection. - //this will stop double click then mouse-drag from undo-ing the desired selecting of a word. - if (mMouseDoubleDownState) - { - nsFrameState frameState; - newFrame->GetFrameState(&frameState); - PRBool insideSelection(PR_FALSE); - if ((frameState & NS_FRAME_SELECTED_CONTENT)) - { - PRBool isCollapsed; - PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); - mDomSelections[index]->GetIsCollapsed(&isCollapsed); - if (!isCollapsed) - { - PRInt32 rangeCount; - result = mDomSelections[index]->GetRangeCount(&rangeCount); - if (NS_FAILED(result)) return result; - - nsCOMPtr domNode; - domNode = do_QueryInterface(newContent); - if (domNode) - { - for (PRInt32 i = 0; i < rangeCount; i++) - { - nsCOMPtr range; - - result = mDomSelections[index]->GetRangeAt(i, getter_AddRefs(range)); - if (NS_FAILED(result) || !range) - continue;//dont bail yet, iterate through them all - - nsCOMPtr nsrange(do_QueryInterface(range)); - if (NS_FAILED(result) || !nsrange) - continue;//dont bail yet, iterate through them all - - nsrange->IsPointInRange(domNode, startPos, &insideSelection); - - // Done when we find a range that we are in - if (insideSelection) - break; - } - } - } - } - - if (!insideSelection) - mMouseDoubleDownState = PR_FALSE; - else - return NS_OK; //dragging in selection aborted - } + if ((newFrame->GetStateBits() & NS_FRAME_SELECTED_CONTENT) && + AdjustForMaintainedSelection(newContent, startPos)) + return NS_OK; // do we have CSS that changes selection behaviour? { diff --git a/layout/base/nsIFrameSelection.h b/layout/base/nsIFrameSelection.h index b73cf6d074e3..bd881d95fcfa 100644 --- a/layout/base/nsIFrameSelection.h +++ b/layout/base/nsIFrameSelection.h @@ -423,6 +423,14 @@ public: */ NS_IMETHOD GetMouseDoubleDown(PRBool *aDoubleDown)=0; + /** + * MaintainSelection will track the current selection as being "sticky". + * Dragging or extending selection will never allow for a subset + * (or the whole) of the maintained selection to become unselected. + * Primary use: double click selecting then dragging on second click + */ + NS_IMETHOD MaintainSelection()=0; + #ifdef IBMBIDI /** GetPrevNextBidiLevels will return the frames and associated Bidi levels of the characters * logically before and after a (collapsed) selection. diff --git a/layout/base/public/nsIFrameSelection.h b/layout/base/public/nsIFrameSelection.h index b73cf6d074e3..bd881d95fcfa 100644 --- a/layout/base/public/nsIFrameSelection.h +++ b/layout/base/public/nsIFrameSelection.h @@ -423,6 +423,14 @@ public: */ NS_IMETHOD GetMouseDoubleDown(PRBool *aDoubleDown)=0; + /** + * MaintainSelection will track the current selection as being "sticky". + * Dragging or extending selection will never allow for a subset + * (or the whole) of the maintained selection to become unselected. + * Primary use: double click selecting then dragging on second click + */ + NS_IMETHOD MaintainSelection()=0; + #ifdef IBMBIDI /** GetPrevNextBidiLevels will return the frames and associated Bidi levels of the characters * logically before and after a (collapsed) selection. diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index 86ebdc67885e..9ef23ccf0d55 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -473,6 +473,7 @@ public: NS_IMETHOD CommonPageMove(PRBool aForward, PRBool aExtend, nsIScrollableView *aScrollableView, nsIFrameSelection *aFrameSel); NS_IMETHOD SetMouseDoubleDown(PRBool aDoubleDown); NS_IMETHOD GetMouseDoubleDown(PRBool *aDoubleDown); + NS_IMETHOD MaintainSelection(); #ifdef IBMBIDI NS_IMETHOD GetPrevNextBidiLevels(nsIPresContext *aPresContext, nsIContent *aNode, @@ -1065,6 +1066,11 @@ NS_IMETHODIMP nsTextInputSelectionImpl::GetMouseDoubleDown(PRBool *aDoubleDown) return mFrameSelection->GetMouseDoubleDown(aDoubleDown); } +NS_IMETHODIMP nsTextInputSelectionImpl::MaintainSelection() +{ + return mFrameSelection->MaintainSelection(); +} + NS_IMETHODIMP nsTextInputSelectionImpl::CommonPageMove(PRBool aForward, PRBool aExtend, nsIScrollableView *aScrollableView, nsIFrameSelection *aFrameSel) { return mFrameSelection->CommonPageMove(aForward, aExtend, aScrollableView, this); diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 08a140819918..08d0a1816ae8 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -1549,6 +1549,9 @@ nsFrame::HandlePress(nsIPresContext* aPresContext, if (NS_FAILED(rv)) return rv; + if (startOffset != endOffset) + frameselection->MaintainSelection(); + if (isEditor && !me->isShift && (endOffset - startOffset) == 1) { // A single node is selected and we aren't extending an existing @@ -1762,7 +1765,17 @@ nsFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack, return rv; } //no release - return NS_OK; + + // maintain selection + nsCOMPtr frameselection = do_QueryInterface(selcon); //this MAY implement + if (!frameselection) + { + rv = aPresContext->GetPresShell()->GetFrameSelection(getter_AddRefs(frameselection)); + if (NS_FAILED(rv) || !frameselection) + return NS_OK; // return NS_OK; we don't care if this fails + } + + return frameselection->MaintainSelection(); } NS_IMETHODIMP nsFrame::HandleDrag(nsIPresContext* aPresContext, diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index 8a493765e398..1769c961cc93 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -373,8 +373,8 @@ public: nsDirection aDirection, PRUint8 aBidiLevel, nsIFrame **aFrameOut); - - /*END nsIFrameSelection interfacse*/ + NS_IMETHOD MaintainSelection(); + /*END nsIFrameSelection interfaces */ @@ -422,6 +422,8 @@ private: nsPeekOffsetStruct aPos); #endif // VISUALSELECTION + PRBool AdjustForMaintainedSelection(nsIContent *aContent, PRInt32 aOffset); + // post and pop reasons for notifications. we may stack these later void PostReason(PRInt16 aReason) { mSelectionChangeReason = aReason; } PRInt16 PopReason() @@ -486,6 +488,9 @@ private: PRInt32 mSelectingTableCellMode; PRInt32 mSelectedCellIndex; + // maintain selection + nsCOMPtr mMaintainRange; + //batching PRInt32 mBatching; @@ -2451,6 +2456,37 @@ NS_IMETHODIMP nsSelection::GetFrameFromLevel(nsIPresContext *aPresContext, return NS_OK; } + +NS_IMETHODIMP +nsSelection::MaintainSelection() +{ + PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); + nsCOMPtr range; + nsresult rv = mDomSelections[index]->GetRangeAt(0, getter_AddRefs(range)); + if (NS_FAILED(rv)) + return rv; + if (!range) + return NS_ERROR_FAILURE; + + nsCOMPtr startNode; + nsCOMPtr endNode; + PRInt32 startOffset; + PRInt32 endOffset; + range->GetStartContainer(getter_AddRefs(startNode)); + range->GetEndContainer(getter_AddRefs(endNode)); + range->GetStartOffset(&startOffset); + range->GetEndOffset(&endOffset); + + mMaintainRange = nsnull; + NS_NewRange(getter_AddRefs(mMaintainRange)); + if (!mMaintainRange) + return NS_ERROR_OUT_OF_MEMORY; + + mMaintainRange->SetStart(startNode, startOffset); + return mMaintainRange->SetEnd(endNode, endOffset); +} + + /** After moving the caret, its Bidi level is set according to the following rules: * * After moving over a character with left/right arrow, set to the Bidi level of the last moved over character. @@ -2538,6 +2574,62 @@ void nsSelection::BidiLevelFromClick(nsIContent *aNode, PRUint32 aContentOffset) shell->SetCaretBidiLevel(frameLevel); } + +PRBool +nsSelection::AdjustForMaintainedSelection(nsIContent *aContent, PRInt32 aOffset) +{ + // Is the desired content and offset currently in selection? + // If the double click flag is set then don't continue selection if the + // desired content and offset are currently inside a selection. + // This will stop double click then mouse-drag from undoing the desired + // selecting of a word. + if (!mMaintainRange) + return PR_FALSE; + + nsCOMPtr rangenode; + PRInt32 rangeOffset; + mMaintainRange->GetStartContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetStartOffset(&rangeOffset); + + nsCOMPtr domNode = do_QueryInterface(aContent); + if (domNode) + { + PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); + nsCOMPtr nsrange = do_QueryInterface(mMaintainRange); + if (nsrange) + { + PRBool insideSelection = PR_FALSE; + nsrange->IsPointInRange(domNode, aOffset, &insideSelection); + + // Done when we find a range that we are in + if (insideSelection) + { + mDomSelections[index]->Collapse(rangenode, rangeOffset); + mMaintainRange->GetEndContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetEndOffset(&rangeOffset); + mDomSelections[index]->Extend(rangenode,rangeOffset); + return PR_TRUE; // dragging in selection aborted + } + } + + PRInt32 relativePosition = ComparePoints(rangenode, rangeOffset, domNode, aOffset); + // if == 0 or -1 do nothing if < 0 then we need to swap direction + if (relativePosition > 0 + && (mDomSelections[index]->GetDirection() == eDirNext)) + { + mMaintainRange->GetEndContainer(getter_AddRefs(rangenode)); + mMaintainRange->GetEndOffset(&rangeOffset); + mDomSelections[index]->Collapse(rangenode, rangeOffset); + } + else if (relativePosition < 0 + && (mDomSelections[index]->GetDirection() == eDirPrevious)) + mDomSelections[index]->Collapse(rangenode, rangeOffset); + } + + return PR_FALSE; +} + + NS_IMETHODIMP nsSelection::HandleClick(nsIContent *aNewFocus, PRUint32 aContentOffset, PRUint32 aContentEndOffset, PRBool aContinueSelection, @@ -2545,14 +2637,22 @@ nsSelection::HandleClick(nsIContent *aNewFocus, PRUint32 aContentOffset, { if (!aNewFocus) return NS_ERROR_INVALID_ARG; + InvalidateDesiredX(); - + + if (!aContinueSelection) + mMaintainRange = nsnull; + mHint = HINT(aHint); // Don't take focus when dragging off of a table if (!mDragSelectingCells) { BidiLevelFromClick(aNewFocus, aContentOffset); PostReason(nsISelectionListener::MOUSEDOWN_REASON + nsISelectionListener::DRAG_REASON); + if (aContinueSelection && + AdjustForMaintainedSelection(aNewFocus, aContentOffset)) + return NS_OK; //shift clicked to maintained selection. rejected. + return TakeFocus(aNewFocus, aContentOffset, aContentEndOffset, aContinueSelection, aMultipleSelection); } @@ -2569,22 +2669,12 @@ nsSelection::HandleDrag(nsIPresContext *aPresContext, nsIFrame *aFrame, nsPoint& nsIFrame *newFrame = 0; nsPoint newPoint; - result = ConstrainFrameAndPointToAnchorSubtree(aPresContext, aFrame, aPoint, &newFrame, newPoint); - if (NS_FAILED(result)) return result; - if (!newFrame) return NS_ERROR_FAILURE; - nsCOMPtr presShell; - - result = aPresContext->GetShell(getter_AddRefs(presShell)); - - if (NS_FAILED(result)) - return result; - PRInt32 startPos = 0; PRInt32 contentOffsetEnd = 0; PRBool beginOfContent; @@ -2594,57 +2684,9 @@ nsSelection::HandleDrag(nsIPresContext *aPresContext, nsIFrame *aFrame, nsPoint& getter_AddRefs(newContent), startPos, contentOffsetEnd, beginOfContent); - //Is the desired content and offset currently in selection? - //if the double click flag is set then dont continue selection if the desired content and offset - //are currently inside a selection. - //this will stop double click then mouse-drag from undo-ing the desired selecting of a word. - if (mMouseDoubleDownState) - { - nsFrameState frameState; - newFrame->GetFrameState(&frameState); - PRBool insideSelection(PR_FALSE); - if ((frameState & NS_FRAME_SELECTED_CONTENT)) - { - PRBool isCollapsed; - PRInt8 index = GetIndexFromSelectionType(nsISelectionController::SELECTION_NORMAL); - mDomSelections[index]->GetIsCollapsed(&isCollapsed); - if (!isCollapsed) - { - PRInt32 rangeCount; - result = mDomSelections[index]->GetRangeCount(&rangeCount); - if (NS_FAILED(result)) return result; - - nsCOMPtr domNode; - domNode = do_QueryInterface(newContent); - if (domNode) - { - for (PRInt32 i = 0; i < rangeCount; i++) - { - nsCOMPtr range; - - result = mDomSelections[index]->GetRangeAt(i, getter_AddRefs(range)); - if (NS_FAILED(result) || !range) - continue;//dont bail yet, iterate through them all - - nsCOMPtr nsrange(do_QueryInterface(range)); - if (NS_FAILED(result) || !nsrange) - continue;//dont bail yet, iterate through them all - - nsrange->IsPointInRange(domNode, startPos, &insideSelection); - - // Done when we find a range that we are in - if (insideSelection) - break; - } - } - } - } - - if (!insideSelection) - mMouseDoubleDownState = PR_FALSE; - else - return NS_OK; //dragging in selection aborted - } + if ((newFrame->GetStateBits() & NS_FRAME_SELECTED_CONTENT) && + AdjustForMaintainedSelection(newContent, startPos)) + return NS_OK; // do we have CSS that changes selection behaviour? { diff --git a/layout/html/base/src/nsFrame.cpp b/layout/html/base/src/nsFrame.cpp index 08a140819918..08d0a1816ae8 100644 --- a/layout/html/base/src/nsFrame.cpp +++ b/layout/html/base/src/nsFrame.cpp @@ -1549,6 +1549,9 @@ nsFrame::HandlePress(nsIPresContext* aPresContext, if (NS_FAILED(rv)) return rv; + if (startOffset != endOffset) + frameselection->MaintainSelection(); + if (isEditor && !me->isShift && (endOffset - startOffset) == 1) { // A single node is selected and we aren't extending an existing @@ -1762,7 +1765,17 @@ nsFrame::PeekBackwardAndForward(nsSelectionAmount aAmountBack, return rv; } //no release - return NS_OK; + + // maintain selection + nsCOMPtr frameselection = do_QueryInterface(selcon); //this MAY implement + if (!frameselection) + { + rv = aPresContext->GetPresShell()->GetFrameSelection(getter_AddRefs(frameselection)); + if (NS_FAILED(rv) || !frameselection) + return NS_OK; // return NS_OK; we don't care if this fails + } + + return frameselection->MaintainSelection(); } NS_IMETHODIMP nsFrame::HandleDrag(nsIPresContext* aPresContext, diff --git a/layout/html/forms/src/nsTextControlFrame.cpp b/layout/html/forms/src/nsTextControlFrame.cpp index 86ebdc67885e..9ef23ccf0d55 100644 --- a/layout/html/forms/src/nsTextControlFrame.cpp +++ b/layout/html/forms/src/nsTextControlFrame.cpp @@ -473,6 +473,7 @@ public: NS_IMETHOD CommonPageMove(PRBool aForward, PRBool aExtend, nsIScrollableView *aScrollableView, nsIFrameSelection *aFrameSel); NS_IMETHOD SetMouseDoubleDown(PRBool aDoubleDown); NS_IMETHOD GetMouseDoubleDown(PRBool *aDoubleDown); + NS_IMETHOD MaintainSelection(); #ifdef IBMBIDI NS_IMETHOD GetPrevNextBidiLevels(nsIPresContext *aPresContext, nsIContent *aNode, @@ -1065,6 +1066,11 @@ NS_IMETHODIMP nsTextInputSelectionImpl::GetMouseDoubleDown(PRBool *aDoubleDown) return mFrameSelection->GetMouseDoubleDown(aDoubleDown); } +NS_IMETHODIMP nsTextInputSelectionImpl::MaintainSelection() +{ + return mFrameSelection->MaintainSelection(); +} + NS_IMETHODIMP nsTextInputSelectionImpl::CommonPageMove(PRBool aForward, PRBool aExtend, nsIScrollableView *aScrollableView, nsIFrameSelection *aFrameSel) { return mFrameSelection->CommonPageMove(aForward, aExtend, aScrollableView, this);