Back to index

lightning-sunbird  0.9+nobinonly
nsCaret.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *   Mats Palmgren <mats.palmgren@bredband.net>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 
00041 #include "nsCOMPtr.h"
00042 
00043 #include "nsITimer.h"
00044 
00045 #include "nsIComponentManager.h"
00046 #include "nsIServiceManager.h"
00047 #include "nsIFrameSelection.h"
00048 #include "nsIFrame.h"
00049 #include "nsIDOMNode.h"
00050 #include "nsIDOMRange.h"
00051 #include "nsIFontMetrics.h"
00052 #include "nsISelection.h"
00053 #include "nsISelectionPrivate.h"
00054 #include "nsIDOMCharacterData.h"
00055 #include "nsIContent.h"
00056 #include "nsIPresShell.h"
00057 #include "nsIRenderingContext.h"
00058 #include "nsIDeviceContext.h"
00059 #include "nsIView.h"
00060 #include "nsIScrollableView.h"
00061 #include "nsIViewManager.h"
00062 #include "nsPresContext.h"
00063 #include "nsILookAndFeel.h"
00064 #include "nsBlockFrame.h"
00065 #include "nsISelectionController.h"
00066 
00067 #include "nsCaret.h"
00068 
00069 // The bidi indicator hangs off the caret to one side, to show which
00070 // direction the typing is in. It needs to be at least 2x2 to avoid looking like 
00071 // an insignificant dot
00072 static const PRUint32 kMinBidiIndicatorPixels = 2;
00073 
00074 #if !defined(MOZ_WIDGET_GTK2)
00075 // Because of drawing issues, we currently always make a new RC. See bug 28068
00076 // Before removing this, stuff will need to be fixed and tested on all platforms.
00077 // For example, turning this off on Mac right now causes drawing problems on pages
00078 // with form elements.
00079 // Also turning this off caused problems on GTK1. See bug 254049.
00080 #define DONT_REUSE_RENDERING_CONTEXT
00081 #endif
00082 
00083 #ifdef IBMBIDI
00084 //-------------------------------IBM BIDI--------------------------------------
00085 // Mamdouh : Modifiaction of the caret to work with Bidi in the LTR and RTL
00086 #include "nsLayoutAtoms.h"
00087 //------------------------------END OF IBM BIDI--------------------------------
00088 #endif //IBMBIDI
00089 
00090 //-----------------------------------------------------------------------------
00091 
00092 nsCaret::nsCaret()
00093 : mPresShell(nsnull)
00094 , mBlinkRate(500)
00095 , mVisible(PR_FALSE)
00096 , mDrawn(PR_FALSE)
00097 , mReadOnly(PR_FALSE)
00098 , mShowDuringSelection(PR_FALSE)
00099 , mLastCaretView(nsnull)
00100 , mLastContentOffset(0)
00101 , mLastHint(nsIFrameSelection::HINTLEFT)
00102 #ifdef IBMBIDI
00103 , mLastBidiLevel(0)
00104 , mKeyboardRTL(PR_FALSE)
00105 #endif
00106 {
00107 }
00108 
00109 
00110 //-----------------------------------------------------------------------------
00111 nsCaret::~nsCaret()
00112 {
00113   KillTimer();
00114 }
00115 
00116 //-----------------------------------------------------------------------------
00117 NS_IMETHODIMP nsCaret::Init(nsIPresShell *inPresShell)
00118 {
00119   NS_ENSURE_ARG(inPresShell);
00120   
00121   mPresShell = do_GetWeakReference(inPresShell);    // the presshell owns us, so no addref
00122   NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
00123 
00124   // get nsILookAndFeel from the pres context, which has one cached.
00125   nsILookAndFeel *lookAndFeel = nsnull;
00126   nsPresContext *presContext = inPresShell->GetPresContext();
00127   
00128   PRInt32 caretPixelsWidth = 1;
00129   if (presContext && (lookAndFeel = presContext->LookAndFeel())) {
00130     PRInt32 tempInt;
00131     if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_CaretWidth, tempInt)))
00132       caretPixelsWidth = (nscoord)tempInt;
00133     if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_CaretBlinkTime, tempInt)))
00134       mBlinkRate = (PRUint32)tempInt;
00135     if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ShowCaretDuringSelection, tempInt)))
00136       mShowDuringSelection = tempInt ? PR_TRUE : PR_FALSE;
00137   }
00138   
00139   float tDevUnitsToTwips;
00140   tDevUnitsToTwips = presContext->DeviceContext()->DevUnitsToTwips();
00141   mCaretTwipsWidth = (nscoord)(tDevUnitsToTwips * (float)caretPixelsWidth);
00142   mBidiIndicatorTwipsSize = (nscoord)(tDevUnitsToTwips * (float)kMinBidiIndicatorPixels);
00143   if (mBidiIndicatorTwipsSize < mCaretTwipsWidth) {
00144     mBidiIndicatorTwipsSize = mCaretTwipsWidth;
00145   }
00146 
00147   // get the selection from the pres shell, and set ourselves up as a selection
00148   // listener
00149 
00150   nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
00151   if (!selCon)
00152     return NS_ERROR_FAILURE;
00153 
00154   nsCOMPtr<nsISelection> domSelection;
00155   nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSelection));
00156   if (NS_FAILED(rv))
00157     return rv;
00158   if (!domSelection)
00159     return NS_ERROR_FAILURE;
00160 
00161   nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
00162   if (privateSelection)
00163     privateSelection->AddSelectionListener(this);
00164   mDomSelectionWeak = do_GetWeakReference(domSelection);
00165   
00166   // set up the blink timer
00167   if (mVisible)
00168   {
00169     rv = StartBlinking();
00170     if (NS_FAILED(rv))
00171       return rv;
00172   }
00173 
00174 #ifdef IBMBIDI
00175   PRBool isRTL = PR_FALSE;
00176   mBidiKeyboard = do_GetService("@mozilla.org/widget/bidikeyboard;1");
00177   if (mBidiKeyboard)
00178        mBidiKeyboard->IsLangRTL(&isRTL);
00179   mKeyboardRTL = isRTL;
00180 #endif
00181   
00182   return NS_OK;
00183 }
00184 
00185 
00186 //-----------------------------------------------------------------------------
00187 NS_IMETHODIMP nsCaret::Terminate()
00188 {
00189   // this doesn't erase the caret if it's drawn. Should it? We might not have a good
00190   // drawing environment during teardown.
00191   
00192   KillTimer();
00193   mBlinkTimer = nsnull;
00194   
00195   mRendContext = nsnull;
00196 
00197   // unregiser ourselves as a selection listener
00198   nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
00199   nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
00200   if (privateSelection)
00201     privateSelection->RemoveSelectionListener(this);
00202   mDomSelectionWeak = nsnull;
00203   mPresShell = nsnull;
00204 
00205   mLastContent = nsnull;
00206   mLastCaretView = nsnull;
00207   
00208 #ifdef IBMBIDI
00209   mBidiKeyboard = nsnull;
00210 #endif
00211 
00212   return NS_OK;
00213 }
00214 
00215 
00216 //-----------------------------------------------------------------------------
00217 NS_IMPL_ISUPPORTS2(nsCaret, nsICaret, nsISelectionListener)
00218 
00219 //-----------------------------------------------------------------------------
00220 NS_IMETHODIMP nsCaret::GetCaretDOMSelection(nsISelection **aDOMSel)
00221 {
00222   nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
00223   
00224   NS_IF_ADDREF(*aDOMSel = sel);
00225 
00226   return NS_OK;
00227 }
00228 
00229 
00230 //-----------------------------------------------------------------------------
00231 NS_IMETHODIMP nsCaret::SetCaretDOMSelection(nsISelection *aDOMSel)
00232 {
00233   NS_ENSURE_ARG_POINTER(aDOMSel);
00234   mDomSelectionWeak = do_GetWeakReference(aDOMSel);   // weak reference to pres shell
00235   if (mVisible)
00236   {
00237     // Stop the caret from blinking in its previous location.
00238     StopBlinking();
00239     // Start the caret blinking in the new location.
00240     StartBlinking();
00241   }
00242   return NS_OK;
00243 }
00244 
00245 
00246 //-----------------------------------------------------------------------------
00247 NS_IMETHODIMP nsCaret::SetCaretVisible(PRBool inMakeVisible)
00248 {
00249   mVisible = inMakeVisible;
00250   nsresult  err = NS_OK;
00251   if (mVisible)
00252     err = StartBlinking();
00253   else
00254     err = StopBlinking();
00255     
00256   return err;
00257 }
00258 
00259 
00260 //-----------------------------------------------------------------------------
00261 NS_IMETHODIMP nsCaret::GetCaretVisible(PRBool *outMakeVisible)
00262 {
00263   NS_ENSURE_ARG_POINTER(outMakeVisible);
00264   *outMakeVisible = mVisible;
00265   return NS_OK;
00266 }
00267 
00268 
00269 //-----------------------------------------------------------------------------
00270 NS_IMETHODIMP nsCaret::SetCaretReadOnly(PRBool inMakeReadonly)
00271 {
00272   mReadOnly = inMakeReadonly;
00273   return NS_OK;
00274 }
00275 
00276 
00277 //-----------------------------------------------------------------------------
00278 NS_IMETHODIMP nsCaret::GetCaretCoordinates(EViewCoordinates aRelativeToType, nsISelection *aDOMSel, nsRect *outCoordinates, PRBool *outIsCollapsed, nsIView **outView)
00279 {
00280   if (!mPresShell)
00281     return NS_ERROR_NOT_INITIALIZED;
00282   if (!outCoordinates || !outIsCollapsed)
00283     return NS_ERROR_NULL_POINTER;
00284 
00285   nsCOMPtr<nsISelection> domSelection = aDOMSel;
00286   nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
00287   if (!privateSelection)
00288     return NS_ERROR_NOT_INITIALIZED;    // no selection
00289 
00290   if (outView)
00291     *outView = nsnull;
00292 
00293   // fill in defaults for failure
00294   outCoordinates->x = -1;
00295   outCoordinates->y = -1;
00296   outCoordinates->width = -1;
00297   outCoordinates->height = -1;
00298   *outIsCollapsed = PR_FALSE;
00299   
00300   nsresult err = domSelection->GetIsCollapsed(outIsCollapsed);
00301   if (NS_FAILED(err)) 
00302     return err;
00303     
00304   nsCOMPtr<nsIDOMNode>  focusNode;
00305   
00306   err = domSelection->GetFocusNode(getter_AddRefs(focusNode));
00307   if (NS_FAILED(err))
00308     return err;
00309   if (!focusNode)
00310     return NS_ERROR_FAILURE;
00311   
00312   PRInt32 focusOffset;
00313   err = domSelection->GetFocusOffset(&focusOffset);
00314   if (NS_FAILED(err))
00315     return err;
00316     
00317 /*
00318   // is this a text node?
00319   nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(focusNode);
00320   // note that we only work with text nodes here, unlike when drawing the caret.
00321   // this is because this routine is intended for IME support, which only cares about text.
00322   if (!nodeAsText)
00323     return NS_ERROR_UNEXPECTED;
00324 */  
00325   nsCOMPtr<nsIContent>contentNode = do_QueryInterface(focusNode);
00326   if (!contentNode)
00327     return NS_ERROR_FAILURE;
00328 
00329   // find the frame that contains the content node that has focus
00330   nsIFrame*       theFrame = nsnull;
00331   PRInt32         theFrameOffset = 0;
00332 
00333   nsCOMPtr<nsIFrameSelection> frameSelection;
00334   privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
00335 
00336   nsIFrameSelection::HINT hint;
00337   frameSelection->GetHint(&hint);
00338 
00339   PRUint8 bidiLevel;
00340   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00341   if (!presShell)
00342     return NS_ERROR_FAILURE;
00343   presShell->GetCaretBidiLevel(&bidiLevel);
00344   
00345   err = GetCaretFrameForNodeOffset(contentNode,
00346                                    focusOffset, hint,
00347                                    bidiLevel,
00348                                    &theFrame,
00349                                    &theFrameOffset);
00350   if (NS_FAILED(err) || !theFrame)
00351     return err;
00352   
00353   nsPoint   viewOffset(0, 0);
00354   nsRect    clipRect;
00355   nsIView   *drawingView;     // views are not refcounted
00356 
00357   GetViewForRendering(theFrame, aRelativeToType, viewOffset, clipRect, &drawingView, outView);
00358   if (!drawingView)
00359     return NS_ERROR_UNEXPECTED;
00360   // ramp up to make a rendering context for measuring text.
00361   // First, we get the pres context ...
00362   nsPresContext *presContext = presShell->GetPresContext();
00363 
00364   // ... then tell it to make a rendering context
00365   nsCOMPtr<nsIRenderingContext> rendContext;  
00366   err = presContext->DeviceContext()->
00367     CreateRenderingContext(drawingView, *getter_AddRefs(rendContext));
00368   if (NS_FAILED(err))
00369     return err;
00370   if (!rendContext)
00371     return NS_ERROR_UNEXPECTED;
00372 
00373   // now we can measure the offset into the frame.
00374   nsPoint   framePos(0, 0);
00375   theFrame->GetPointFromOffset(presContext, rendContext, theFrameOffset, &framePos);
00376 
00377   // we don't need drawingView anymore so reuse that; reset viewOffset values for our purposes
00378   if (aRelativeToType == eClosestViewCoordinates)
00379   {
00380     theFrame->GetOffsetFromView(viewOffset, &drawingView);
00381     if (outView)
00382       *outView = drawingView;
00383   }
00384   // now add the frame offset to the view offset, and we're done
00385   viewOffset += framePos;
00386   outCoordinates->x = viewOffset.x;
00387   outCoordinates->y = viewOffset.y;
00388   outCoordinates->height = theFrame->GetSize().height;
00389   outCoordinates->width  = mCaretTwipsWidth;
00390   
00391   return NS_OK;
00392 }
00393 
00394 NS_IMETHODIMP nsCaret::EraseCaret()
00395 {
00396   if (mDrawn)
00397     DrawCaret();
00398   return NS_OK;
00399 }
00400 
00401 NS_IMETHODIMP nsCaret::SetVisibilityDuringSelection(PRBool aVisibility) 
00402 {
00403   mShowDuringSelection = aVisibility;
00404   return NS_OK;
00405 }
00406 
00407 NS_IMETHODIMP nsCaret::DrawAtPosition(nsIDOMNode* aNode, PRInt32 aOffset)
00408 {
00409   NS_ENSURE_ARG(aNode);
00410 
00411   PRUint8 bidiLevel;
00412   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00413   if (!presShell)
00414     return NS_ERROR_FAILURE;
00415   presShell->GetCaretBidiLevel(&bidiLevel);
00416   
00417   // XXX we need to do more work here to get the correct hint.
00418   return DrawAtPositionWithHint(aNode, aOffset, nsIFrameSelection::HINTLEFT, bidiLevel) ?
00419     NS_OK : NS_ERROR_FAILURE;
00420 }
00421 
00422 
00423 #ifdef XP_MAC
00424 #pragma mark -
00425 #endif
00426 
00427 //-----------------------------------------------------------------------------
00428 NS_IMETHODIMP nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, PRInt16 aReason)
00429 {
00430   if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
00431     return NS_OK;
00432 
00433   nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
00434 
00435   // The same caret is shared amongst the document and any text widgets it
00436   // may contain. This means that the caret could get notifications from
00437   // multiple selections.
00438   //
00439   // If this notification is for a selection that is not the one the
00440   // the caret is currently interested in (mDomSelectionWeak), then there
00441   // is nothing to do!
00442 
00443   if (domSel != aDomSel)
00444     return NS_OK;
00445 
00446   if (mVisible)
00447   {
00448     // Stop the caret from blinking in its previous location.
00449     StopBlinking();
00450 
00451     // Start the caret blinking in the new location.
00452     StartBlinking();
00453   }
00454 
00455   return NS_OK;
00456 }
00457 
00458 #ifdef XP_MAC
00459 #pragma mark -
00460 #endif
00461 
00462 //-----------------------------------------------------------------------------
00463 void nsCaret::KillTimer()
00464 {
00465   if (mBlinkTimer)
00466   {
00467     mBlinkTimer->Cancel();
00468   }
00469 }
00470 
00471 
00472 //-----------------------------------------------------------------------------
00473 nsresult nsCaret::PrimeTimer()
00474 {
00475   // set up the blink timer
00476   if (!mReadOnly && mBlinkRate > 0)
00477   {
00478     if (!mBlinkTimer) {
00479       nsresult  err;
00480       mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);    
00481       if (NS_FAILED(err))
00482         return err;
00483     }    
00484 
00485     mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate,
00486                                       nsITimer::TYPE_REPEATING_SLACK);
00487   }
00488 
00489   return NS_OK;
00490 }
00491 
00492 
00493 //-----------------------------------------------------------------------------
00494 nsresult nsCaret::StartBlinking()
00495 {
00496   PrimeTimer();
00497 
00498   //NS_ASSERTION(!mDrawn, "Caret should not be drawn here");
00499   DrawCaret();    // draw it right away
00500   
00501   return NS_OK;
00502 }
00503 
00504 
00505 //-----------------------------------------------------------------------------
00506 nsresult nsCaret::StopBlinking()
00507 {
00508   if (mDrawn)     // erase the caret if necessary
00509     DrawCaret();
00510     
00511   KillTimer();
00512   
00513   return NS_OK;
00514 }
00515 
00516 PRBool
00517 nsCaret::DrawAtPositionWithHint(nsIDOMNode*             aNode,
00518                                 PRInt32                 aOffset,
00519                                 nsIFrameSelection::HINT aFrameHint,
00520                                 PRUint8                 aBidiLevel)
00521 {  
00522   nsCOMPtr<nsIContent> contentNode = do_QueryInterface(aNode);
00523   if (!contentNode)
00524     return PR_FALSE;
00525       
00526   nsIFrame* theFrame = nsnull;
00527   PRInt32   theFrameOffset = 0;
00528 
00529   nsresult rv = GetCaretFrameForNodeOffset(contentNode, aOffset, aFrameHint, aBidiLevel,
00530                                            &theFrame, &theFrameOffset);
00531   if (NS_FAILED(rv) || !theFrame)
00532     return PR_FALSE;
00533   
00534   // now we have a frame, check whether it's appropriate to show the caret here
00535   const nsStyleUserInterface* userinterface = theFrame->GetStyleUserInterface();
00536   if (
00537 #ifdef SUPPORT_USER_MODIFY
00538         // editable content still defaults to NS_STYLE_USER_MODIFY_READ_ONLY at present. See bug 15284
00539       (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
00540 #endif          
00541       (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) ||
00542       (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED))
00543   {
00544     return PR_FALSE;
00545   }  
00546 
00547   if (!mDrawn)
00548   {
00549     // save stuff so we can erase the caret later
00550     mLastContent = contentNode;
00551     mLastContentOffset = aOffset;
00552     mLastHint = aFrameHint;
00553     mLastBidiLevel = aBidiLevel;
00554 
00555     // If there has been a reflow, set the caret Bidi level to the level of the current frame
00556     nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00557     if (!presShell)
00558       return PR_FALSE;
00559     if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
00560       presShell->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
00561   }
00562 
00563   GetCaretRectAndInvert(theFrame, theFrameOffset);
00564 
00565   return PR_TRUE;
00566 }
00567 
00568 NS_IMETHODIMP 
00569 nsCaret::GetCaretFrameForNodeOffset (nsIContent*             aContentNode,
00570                                      PRInt32                 aOffset,
00571                                      nsIFrameSelection::HINT aFrameHint,
00572                                      PRUint8                 aBidiLevel,
00573                                      nsIFrame**              aReturnFrame,
00574                                      PRInt32*                aReturnOffset)
00575 {
00576 
00577   //get frame selection and find out what frame to use...
00578   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00579   if (!presShell)
00580     return NS_ERROR_FAILURE;
00581 
00582   nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryReferent(mDomSelectionWeak));
00583   if (!privateSelection)
00584     return NS_ERROR_FAILURE;
00585 
00586   nsCOMPtr<nsIFrameSelection> frameSelection;
00587   privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
00588 
00589   nsIFrame* theFrame = nsnull;
00590   PRInt32   theFrameOffset = 0;
00591 
00592   nsresult rv = frameSelection->GetFrameForNodeOffset(aContentNode, aOffset, aFrameHint, &theFrame, &theFrameOffset);
00593   if (NS_FAILED(rv) || !theFrame)
00594     return NS_ERROR_FAILURE;
00595 
00596   // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
00597   //
00598   // Direction Style from this->GetStyleData()
00599   // now in (visibility->mDirection)
00600   // ------------------
00601   // NS_STYLE_DIRECTION_LTR : LTR or Default
00602   // NS_STYLE_DIRECTION_RTL
00603   // NS_STYLE_DIRECTION_INHERIT
00604   nsPresContext *presContext = presShell->GetPresContext();
00605   if (presContext && presContext->BidiEnabled())
00606   {
00607     // If there has been a reflow, take the caret Bidi level to be the level of the current frame
00608     if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
00609       aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
00610 
00611     PRInt32 start;
00612     PRInt32 end;
00613     nsIFrame* frameBefore;
00614     nsIFrame* frameAfter;
00615     PRUint8 levelBefore;     // Bidi level of the character before the caret
00616     PRUint8 levelAfter;      // Bidi level of the character after the caret
00617 
00618     theFrame->GetOffsets(start, end);
00619     if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
00620     {
00621       /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
00622       if (NS_SUCCEEDED(frameSelection->GetPrevNextBidiLevels(presContext, aContentNode, aOffset,
00623                                                              &frameBefore, &frameAfter,
00624                                                              &levelBefore, &levelAfter)))
00625       {
00626         if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
00627         {
00628           aBidiLevel = PR_MAX(aBidiLevel, PR_MIN(levelBefore, levelAfter));                                  // rule c3
00629           aBidiLevel = PR_MIN(aBidiLevel, PR_MAX(levelBefore, levelAfter));                                  // rule c4
00630           if (aBidiLevel == levelBefore                                                                      // rule c1
00631               || aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)    // rule c5
00632               || aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))   // rule c9
00633           {
00634             if (theFrame != frameBefore)
00635             {
00636               if (frameBefore) // if there is a frameBefore, move into it
00637               {
00638                 theFrame = frameBefore;
00639                 theFrame->GetOffsets(start, end);
00640                 theFrameOffset = end;
00641               }
00642               else 
00643               {
00644                 // if there is no frameBefore, we must be at the beginning of the line
00645                 // so we stay with the current frame.
00646                 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
00647                 // real frame for the caret to be in. We have to find the first frame whose level is the same as the
00648                 // paragraph level, and put the caret at the end of the frame before that.
00649                 PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameAfter);
00650                 if (baseLevel != levelAfter)
00651                 {
00652                   if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(presContext, frameAfter, eDirNext, baseLevel, &theFrame)))
00653                   {
00654                     theFrame->GetOffsets(start, end);
00655                     levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
00656                     if (baseLevel & 1) // RTL paragraph: caret to the right of the rightmost character
00657                       theFrameOffset = (levelAfter & 1) ? start : end;
00658                     else               // LTR paragraph: caret to the left of the leftmost character
00659                       theFrameOffset = (levelAfter & 1) ? end : start;
00660                   }
00661                 }
00662               }
00663             }
00664           }
00665           else if (aBidiLevel == levelAfter                                                                     // rule c2
00666                    || aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)   // rule c6  
00667                    || aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))  // rule c10
00668           {
00669             if (theFrame != frameAfter)
00670             {
00671               if (frameAfter)
00672               {
00673                 // if there is a frameAfter, move into it
00674                 theFrame = frameAfter;
00675                 theFrame->GetOffsets(start, end);
00676                 theFrameOffset = start;
00677               }
00678               else 
00679               {
00680                 // if there is no frameAfter, we must be at the end of the line
00681                 // so we stay with the current frame.
00682                 //
00683                 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
00684                 // real frame for the caret to be in. We have to find the last frame whose level is the same as the
00685                 // paragraph level, and put the caret at the end of the frame after that.
00686 
00687                 PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameBefore);
00688                 if (baseLevel != levelBefore)
00689                 {
00690                   if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(presContext, frameBefore, eDirPrevious, baseLevel, &theFrame)))
00691                   {
00692                     theFrame->GetOffsets(start, end);
00693                     levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
00694                     if (baseLevel & 1) // RTL paragraph: caret to the left of the leftmost character
00695                       theFrameOffset = (levelBefore & 1) ? end : start;
00696                     else               // RTL paragraph: caret to the right of the rightmost character
00697                       theFrameOffset = (levelBefore & 1) ? start : end;
00698                   }
00699                 }
00700               }
00701             }
00702           }
00703           else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter  // rule c7/8
00704                    && !((levelBefore ^ levelAfter) & 1)                 // before and after have the same parity
00705                    && ((aBidiLevel ^ levelAfter) & 1))                  // caret has different parity
00706           {
00707             if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(presContext, frameAfter, eDirNext, aBidiLevel, &theFrame)))
00708             {
00709               theFrame->GetOffsets(start, end);
00710               levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
00711               if (aBidiLevel & 1) // c8: caret to the right of the rightmost character
00712                 theFrameOffset = (levelAfter & 1) ? start : end;
00713               else               // c7: caret to the left of the leftmost character
00714                 theFrameOffset = (levelAfter & 1) ? end : start;
00715             }
00716           }
00717           else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter  // rule c11/12
00718                    && !((levelBefore ^ levelAfter) & 1)                 // before and after have the same parity
00719                    && ((aBidiLevel ^ levelAfter) & 1))                  // caret has different parity
00720           {
00721             if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(presContext, frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
00722             {
00723               theFrame->GetOffsets(start, end);
00724               levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
00725               if (aBidiLevel & 1) // c12: caret to the left of the leftmost character
00726                 theFrameOffset = (levelBefore & 1) ? end : start;
00727               else               // c11: caret to the right of the rightmost character
00728                 theFrameOffset = (levelBefore & 1) ? start : end;
00729             }
00730           }   
00731         }
00732       }
00733     }
00734   }
00735   *aReturnFrame = theFrame;
00736   *aReturnOffset = theFrameOffset;
00737   return NS_OK;
00738 }
00739 
00740 
00741 //-----------------------------------------------------------------------------
00742 void nsCaret::GetViewForRendering(nsIFrame *caretFrame, EViewCoordinates coordType, nsPoint &viewOffset, nsRect& outClipRect, nsIView **outRenderingView, nsIView **outRelativeView)
00743 {
00744 
00745   if (!caretFrame || !outRenderingView)
00746     return;
00747 
00748   // XXX by Masayuki Nakano:
00749   // Our this code is not good. This is adhoc approach.
00750   // Our best approach is to use the event fired widget related view.
00751   // But if we do so, we need large change for editor and this.
00752   if (coordType == eIMECoordinates)
00753 #if defined(XP_MAC) || defined(XP_MACOSX) || defined(XP_WIN)
00754    // #59405 and #313918, on Mac and Windows, the coordinate for IME need to be
00755    // root view related.
00756    coordType = eTopLevelWindowCoordinates; 
00757 #else
00758    // #59405, on unix, the coordinate for IME need to be view
00759    // (nearest native window) related.
00760    coordType = eRenderingViewCoordinates; 
00761 #endif
00762 
00763   *outRenderingView = nsnull;
00764   if (outRelativeView)
00765     *outRelativeView = nsnull;
00766   
00767   NS_ASSERTION(caretFrame, "Should have frame here");
00768  
00769   viewOffset.x = 0;
00770   viewOffset.y = 0;
00771   
00772   nsPoint   withinViewOffset(0, 0);
00773   // get the offset of this frame from its parent view (walks up frame hierarchy)
00774   nsIView* theView = nsnull;
00775   caretFrame->GetOffsetFromView(withinViewOffset, &theView);
00776   if (theView == nsnull) return;
00777 
00778   if (outRelativeView && coordType == eClosestViewCoordinates)
00779     *outRelativeView = theView;
00780 
00781   nsIView*    returnView = nsnull;    // views are not refcounted
00782   
00783   // coorinates relative to the view we are going to use for drawing
00784   if (coordType == eRenderingViewCoordinates)
00785   {
00786     nsIScrollableView*  scrollableView = nsnull;
00787   
00788     nsPoint             drawViewOffset(0, 0);         // offset to the view we are using to draw
00789     
00790     // walk up to the first view with a widget
00791     do {
00792       //is this a scrollable view?
00793       if (!scrollableView)
00794         scrollableView = theView->ToScrollableView();
00795 
00796       if (theView->HasWidget())
00797       {
00798         returnView = theView;
00799         // account for the view's origin not lining up with the widget's (bug 190290)
00800         drawViewOffset += theView->GetPosition() - theView->GetBounds().TopLeft();
00801         break;
00802       }
00803       drawViewOffset += theView->GetPosition();
00804       theView = theView->GetParent();
00805     } while (theView);
00806     
00807     viewOffset = withinViewOffset;
00808     viewOffset += drawViewOffset;
00809     
00810     if (scrollableView)
00811     {
00812       nsRect  bounds = scrollableView->View()->GetBounds();
00813       scrollableView->GetScrollPosition(bounds.x, bounds.y);
00814       
00815       bounds += drawViewOffset;   // offset to coords of returned view
00816       outClipRect = bounds;
00817     }
00818     else
00819     {
00820       NS_ASSERTION(returnView, "bulletproofing, see bug #24329");
00821       if (returnView)
00822         outClipRect = returnView->GetBounds();
00823     }
00824 
00825     if (outRelativeView)
00826       *outRelativeView = returnView;
00827   }
00828   else
00829   {
00830     // window-relative coordinates (walk right to the top of the view hierarchy)
00831     // we don't do anything with clipping here
00832     viewOffset = withinViewOffset;
00833 
00834     do {
00835       if (!returnView && theView->HasWidget())
00836         returnView = theView;
00837       // is this right?
00838       viewOffset += theView->GetPosition();
00839       
00840       if (outRelativeView && coordType == eTopLevelWindowCoordinates)
00841         *outRelativeView = theView;
00842 
00843       theView = theView->GetParent();
00844     } while (theView);
00845   }
00846   
00847   *outRenderingView = returnView;
00848 }
00849 
00850 
00851 /*-----------------------------------------------------------------------------
00852 
00853   MustDrawCaret
00854   
00855   FInd out if we need to do any caret drawing. This returns true if
00856   either a) or b)
00857   a) caret has been drawn, and we need to erase it.
00858   b) caret is not drawn, and selection is collapsed
00859   
00860 ----------------------------------------------------------------------------- */
00861 PRBool nsCaret::MustDrawCaret()
00862 {
00863   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00864   if (presShell) {
00865     PRBool isPaintingSuppressed;
00866     presShell->IsPaintingSuppressed(&isPaintingSuppressed);
00867     if (isPaintingSuppressed)
00868       return PR_FALSE;
00869   }
00870 
00871   if (mDrawn)
00872     return PR_TRUE;
00873 
00874   nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
00875   if (!domSelection)
00876     return PR_FALSE;
00877   PRBool isCollapsed;
00878 
00879   if (NS_FAILED(domSelection->GetIsCollapsed(&isCollapsed)))
00880     return PR_FALSE;
00881 
00882   if (mShowDuringSelection)
00883     return PR_TRUE;      // show the caret even in selections
00884 
00885   return isCollapsed;
00886 }
00887 
00888 
00889 /*-----------------------------------------------------------------------------
00890 
00891   DrawCaret
00892     
00893 ----------------------------------------------------------------------------- */
00894 
00895 void nsCaret::DrawCaret()
00896 {
00897   // do we need to draw the caret at all?
00898   if (!MustDrawCaret())
00899     return;
00900   
00901   nsCOMPtr<nsIDOMNode> node;
00902   PRInt32 offset;
00903   nsIFrameSelection::HINT hint;
00904   PRUint8 bidiLevel;
00905 
00906   if (!mDrawn)
00907   {
00908     nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
00909     nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
00910     if (!privateSelection) return;
00911     
00912     PRBool isCollapsed = PR_FALSE;
00913     domSelection->GetIsCollapsed(&isCollapsed);
00914     if (!mShowDuringSelection && !isCollapsed)
00915       return;
00916 
00917     PRBool hintRight;
00918     privateSelection->GetInterlinePosition(&hintRight);//translate hint.
00919     hint = hintRight ? nsIFrameSelection::HINTRIGHT : nsIFrameSelection::HINTLEFT;
00920 
00921     // get the node and offset, which is where we want the caret to draw
00922     domSelection->GetFocusNode(getter_AddRefs(node));
00923     if (!node)
00924       return;
00925     
00926     if (NS_FAILED(domSelection->GetFocusOffset(&offset)))
00927       return;
00928 
00929     nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00930     if (!presShell)
00931       return;
00932     presShell->GetCaretBidiLevel(&bidiLevel);
00933   }
00934   else
00935   {
00936     if (!mLastContent)
00937     {
00938       mDrawn = PR_FALSE;
00939       return;
00940     }
00941     if (!mLastContent->IsInDoc())
00942     {
00943       mLastContent = nsnull;
00944       mDrawn = PR_FALSE;
00945       return;
00946     }
00947     node = do_QueryInterface(mLastContent);
00948     offset = mLastContentOffset;
00949     hint = mLastHint;
00950     bidiLevel = mLastBidiLevel;
00951   }
00952 
00953   DrawAtPositionWithHint(node, offset, hint, bidiLevel);
00954 }
00955 
00956 void nsCaret::GetCaretRectAndInvert(nsIFrame* aFrame, PRInt32 aFrameOffset)
00957 {
00958   NS_ASSERTION(aFrame, "Should have a frame here");
00959 
00960   nsRect frameRect = aFrame->GetRect();
00961   frameRect.x = 0;      // the origin is accounted for in GetViewForRendering()
00962   frameRect.y = 0;
00963   
00964   nsPoint   viewOffset(0, 0);
00965   nsRect    clipRect;
00966   nsIView   *drawingView;
00967   GetViewForRendering(aFrame, eRenderingViewCoordinates, viewOffset, clipRect, &drawingView, nsnull);
00968   
00969   if (!drawingView)
00970     return;
00971   
00972   frameRect += viewOffset;
00973 
00974   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
00975   if (!presShell) return;
00976   
00977   nsPresContext *presContext = presShell->GetPresContext();
00978 
00979   // if the view changed, or we don't have a rendering context, make one
00980   // because of drawing issues, always make a new RC at the moment. See bug 28068
00981   if (
00982 #ifdef DONT_REUSE_RENDERING_CONTEXT
00983       PR_TRUE ||
00984 #endif
00985       (mLastCaretView != drawingView) || !mRendContext)
00986   {
00987     mRendContext = nsnull;    // free existing one if we have one
00988     
00989     nsresult rv = presContext->DeviceContext()->
00990       CreateRenderingContext(drawingView, *getter_AddRefs(mRendContext));
00991 
00992     if (NS_FAILED(rv) || !mRendContext)
00993       return;      
00994   }
00995 
00996   // push a known good state
00997   mRendContext->PushState();
00998 
00999   // if we got a zero-height frame, it's probably a BR frame at the end of a non-empty line
01000   // (see BRFrame::Reflow). In that case, figure out a height. We have to do this
01001   // after we've got an RC.
01002   if (frameRect.height == 0)
01003   {
01004       const nsStyleFont* fontStyle = aFrame->GetStyleFont();
01005       const nsStyleVisibility* vis = aFrame->GetStyleVisibility();
01006       mRendContext->SetFont(fontStyle->mFont, vis->mLangGroup);
01007 
01008       nsCOMPtr<nsIFontMetrics> fm;
01009       mRendContext->GetFontMetrics(*getter_AddRefs(fm));
01010       if (fm)
01011       {
01012         nscoord ascent, descent;
01013         fm->GetMaxAscent(ascent);
01014         fm->GetMaxDescent(descent);
01015         frameRect.height = ascent + descent;
01016         frameRect.y -= ascent; // BR frames sit on the baseline of the text, so we need to subtract
01017                                // the ascent to account for the frame height.
01018       }
01019   }
01020   
01021   // views are not refcounted
01022   mLastCaretView = drawingView;
01023 
01024   if (!mDrawn)
01025   {
01026     nsPoint   framePos(0, 0);
01027     nsRect    caretRect = frameRect;
01028     nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
01029     nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
01030 
01031     // if cache in selection is available, apply it, else refresh it
01032     privateSelection->GetCachedFrameOffset(aFrame, aFrameOffset, framePos);
01033 
01034     caretRect += framePos;
01035 
01036     caretRect.width = mCaretTwipsWidth;
01037 
01038     // Check if the caret intersects with the right edge
01039     // of the frame. If it does, and the frame's right edge
01040     // is close to the right edge of the clipRect, we may
01041     // need to adjust the caret's x position so that it
01042     // remains visible.
01043 
01044     nscoord caretXMost = caretRect.XMost();
01045     nscoord frameXMost = frameRect.XMost();
01046 
01047     if (caretXMost > frameXMost)
01048     {
01049       nscoord clipXMost  = clipRect.XMost();
01050 
01051       if (caretRect.x == frameRect.x && caretRect.x <= clipXMost &&
01052           caretXMost > clipXMost)
01053       {
01054         // The left side of the caret is attached to the left edge of
01055         // the frame, and it is wider than the frame itself. It also
01056         // overlaps the right edge of the clipRect so we need to nudge
01057         // it to the left so that it remains visible.
01058         //
01059         // We usually hit this case when the caret is attached to a
01060         // br frame (which is about 1 twip in width) that is positioned
01061         // at the right edge of the content area because it is right aligned
01062         // or the right margin pushed it beyond the width of the view port.
01063 
01064         caretRect.x = clipXMost - caretRect.width;
01065       }
01066       else if (caretRect.x == frameXMost && frameXMost == clipXMost)
01067       {
01068         // The left side of the caret is attached to the right edge of
01069         // the frame, but it's going to get clipped because it's positioned
01070         // on the  right edge of the clipRect, so nudge it to the
01071         // left so it remains visible.
01072         //
01073         // We usually hit this case when the caret is after the last
01074         // character on the line, and the line exceeds the width of the
01075         // view port.
01076 
01077         caretRect.x = clipXMost - caretRect.width;
01078       }
01079     }
01080 
01081     mCaretRect.IntersectRect(clipRect, caretRect);
01082 #ifdef IBMBIDI
01083     // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
01084     PRBool bidiEnabled;
01085     nsRect hookRect;
01086     PRBool isCaretRTL=PR_FALSE;
01087     if (mBidiKeyboard)
01088       mBidiKeyboard->IsLangRTL(&isCaretRTL);
01089     if (isCaretRTL)
01090     {
01091       bidiEnabled = PR_TRUE;
01092       presContext->SetBidiEnabled(bidiEnabled);
01093     }
01094     else
01095       bidiEnabled = presContext->BidiEnabled();
01096     if (bidiEnabled)
01097     {
01098       if (isCaretRTL != mKeyboardRTL)
01099       {
01100         /* if the caret bidi level and the keyboard language direction are not in
01101          * synch, the keyboard language must have been changed by the
01102          * user, and if the caret is in a boundary condition (between left-to-right and
01103          * right-to-left characters) it may have to change position to
01104          * reflect the location in which the next character typed will
01105          * appear. We will call |SelectionLanguageChange| and exit
01106          * without drawing the caret in the old position.
01107          */ 
01108         mKeyboardRTL = isCaretRTL;
01109         nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
01110         if (domSelection)
01111         {
01112           if (NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL)))
01113           {
01114             mRendContext->PopState();
01115 #ifdef DONT_REUSE_RENDERING_CONTEXT
01116             mRendContext = nsnull;
01117 #endif
01118             return;
01119           }
01120         }
01121       }
01122       // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
01123       // The height of the hook rectangle is the same as the width of the caret
01124       // rectangle.
01125       hookRect.SetRect(caretRect.x + ((isCaretRTL) ?
01126                                        mBidiIndicatorTwipsSize * -1 :
01127                                        caretRect.width),
01128                        caretRect.y + mBidiIndicatorTwipsSize,
01129                        mBidiIndicatorTwipsSize,
01130                        caretRect.width);
01131       mHookRect.IntersectRect(clipRect, hookRect);
01132     }
01133 #endif //IBMBIDI
01134   }
01135   
01136   if (mReadOnly)
01137     mRendContext->SetColor(NS_RGB(85, 85, 85));   // we are drawing it; gray
01138   else
01139     mRendContext->SetColor(NS_RGB(255,255,255));
01140 
01141   mRendContext->InvertRect(mCaretRect);
01142 
01143   // Ensure the buffer is flushed (Cocoa needs this), since we're drawing
01144   // outside the normal painting process.
01145   mRendContext->FlushRect(mCaretRect);
01146 
01147 #ifdef IBMBIDI
01148   if (!mHookRect.IsEmpty()) // if Bidi support is disabled, the rectangle remains empty and won't be drawn
01149     mRendContext->InvertRect(mHookRect);
01150 #endif
01151 
01152   mRendContext->PopState();
01153   
01154   ToggleDrawnStatus();
01155 
01156   if (mDrawn) {
01157     aFrame->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
01158   }
01159 
01160 #ifdef DONT_REUSE_RENDERING_CONTEXT
01161   mRendContext = nsnull;
01162 #endif
01163 }
01164 
01165 #ifdef XP_MAC
01166 #pragma mark -
01167 #endif
01168 
01169 //-----------------------------------------------------------------------------
01170 /* static */
01171 void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure)
01172 {
01173   nsCaret   *theCaret = NS_REINTERPRET_CAST(nsCaret*, aClosure);
01174   if (!theCaret) return;
01175   
01176   theCaret->DrawCaret();
01177 }
01178 
01179 
01180 //-----------------------------------------------------------------------------
01181 nsresult NS_NewCaret(nsICaret** aInstancePtrResult)
01182 {
01183   NS_PRECONDITION(aInstancePtrResult, "null ptr");
01184   
01185   nsCaret* caret = new nsCaret();
01186   if (nsnull == caret)
01187       return NS_ERROR_OUT_OF_MEMORY;
01188       
01189   return caret->QueryInterface(NS_GET_IID(nsICaret), (void**) aInstancePtrResult);
01190 }
01191