Back to index

lightning-sunbird  0.9+nobinonly
nsListControlFrame.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 Communicator client 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 #include "nscore.h"
00041 #include "nsCOMPtr.h"
00042 #include "nsReadableUtils.h"
00043 #include "nsUnicharUtils.h"
00044 #include "nsListControlFrame.h"
00045 #include "nsFormControlFrame.h" // for COMPARE macro
00046 #include "nsFormControlHelper.h"
00047 #include "nsHTMLAtoms.h"
00048 #include "nsIFormControl.h"
00049 #include "nsIDeviceContext.h" 
00050 #include "nsIDocument.h"
00051 #include "nsIDOMHTMLCollection.h" 
00052 #include "nsIDOMHTMLOptionsCollection.h" 
00053 #include "nsIDOMNSHTMLOptionCollectn.h"
00054 #include "nsIDOMHTMLSelectElement.h" 
00055 #include "nsIDOMNSHTMLSelectElement.h" 
00056 #include "nsIDOMHTMLOptionElement.h" 
00057 #include "nsComboboxControlFrame.h"
00058 #include "nsIViewManager.h"
00059 #include "nsIScrollableView.h"
00060 #include "nsIDOMHTMLOptGroupElement.h"
00061 #include "nsWidgetsCID.h"
00062 #include "nsHTMLReflowCommand.h"
00063 #include "nsIPresShell.h"
00064 #include "nsHTMLParts.h"
00065 #include "nsIDOMEventReceiver.h"
00066 #include "nsIEventStateManager.h"
00067 #include "nsIEventListenerManager.h"
00068 #include "nsIDOMKeyEvent.h"
00069 #include "nsIDOMMouseEvent.h"
00070 #include "nsIPrivateDOMEvent.h"
00071 #include "nsXPCOM.h"
00072 #include "nsISupportsPrimitives.h"
00073 #include "nsIComponentManager.h"
00074 #include "nsILookAndFeel.h"
00075 #include "nsLayoutAtoms.h"
00076 #include "nsIFontMetrics.h"
00077 #include "nsVoidArray.h"
00078 #include "nsIScrollableFrame.h"
00079 #include "nsIDOMEventTarget.h"
00080 #include "nsIDOMNSEvent.h"
00081 #include "nsGUIEvent.h"
00082 #include "nsIServiceManager.h"
00083 #include "nsINodeInfo.h"
00084 #ifdef ACCESSIBILITY
00085 #include "nsIAccessibilityService.h"
00086 #endif
00087 #include "nsISelectElement.h"
00088 #include "nsIPrivateDOMEvent.h"
00089 #include "nsCSSRendering.h"
00090 #include "nsReflowPath.h"
00091 #include "nsITheme.h"
00092 #include "nsIDOMMouseListener.h"
00093 #include "nsIDOMMouseMotionListener.h"
00094 #include "nsIDOMKeyListener.h"
00095 #include "nsLayoutUtils.h"
00096 
00097 // Constants
00098 const nscoord kMaxDropDownRows          = 20; // This matches the setting for 4.x browsers
00099 const PRInt32 kDefaultMultiselectHeight = 4; // This is compatible with 4.x browsers
00100 const PRInt32 kNothingSelected          = -1;
00101 const PRInt32 kMaxZ                     = 0x7fffffff; //XXX: Shouldn't there be a define somewhere for MaxInt for PRInt32
00102 const PRInt32 kNoSizeSpecified          = -1;
00103 
00104 
00105 nsListControlFrame * nsListControlFrame::mFocused = nsnull;
00106 
00107 // Using for incremental typing navigation
00108 #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000
00109 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
00110 //  nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
00111 //  need to find a good place to put them together.
00112 //  if someone changes one, please also change the other.
00113 
00114 DOMTimeStamp nsListControlFrame::gLastKeyTime = 0;
00115 
00116 /******************************************************************************
00117  * nsListEventListener
00118  * This class is responsible for propagating events to the nsListControlFrame.
00119  * Frames are not refcounted so they can't be used as event listeners.
00120  *****************************************************************************/
00121 
00122 class nsListEventListener : public nsIDOMKeyListener,
00123                             public nsIDOMMouseListener,
00124                             public nsIDOMMouseMotionListener
00125 {
00126 public:
00127   nsListEventListener(nsListControlFrame *aFrame)
00128     : mFrame(aFrame) { }
00129 
00130   void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
00131 
00132   NS_DECL_ISUPPORTS
00133 
00134   // nsIDOMEventListener
00135   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
00136 
00137   // nsIDOMKeyListener
00138   NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent);
00139   NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent);
00140   NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent);
00141 
00142   // nsIDOMMouseListener
00143   NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent);
00144   NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent);
00145   NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
00146   NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent);
00147   NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent);
00148   NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent);
00149 
00150   // nsIDOMMouseMotionListener
00151   NS_IMETHOD MouseMove(nsIDOMEvent* aMouseEvent);
00152   NS_IMETHOD DragMove(nsIDOMEvent* aMouseEvent);
00153 
00154 private:
00155   nsListControlFrame  *mFrame;
00156 };
00157 
00158 //---------------------------------------------------------
00159 nsresult
00160 NS_NewListControlFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
00161 {
00162   NS_PRECONDITION(aNewFrame, "null OUT ptr");
00163   if (nsnull == aNewFrame) {
00164     return NS_ERROR_NULL_POINTER;
00165   }
00166   nsListControlFrame* it =
00167     new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument());
00168   if (!it) {
00169     return NS_ERROR_OUT_OF_MEMORY;
00170   }
00171   it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
00172 #if 0
00173   // set the state flags (if any are provided)
00174   it->AddStateBits(NS_BLOCK_SPACE_MGR);
00175 #endif
00176   *aNewFrame = it;
00177   return NS_OK;
00178 }
00179 
00180 //-----------------------------------------------------------
00181 // Reflow Debugging Macros
00182 // These let us "see" how many reflow counts are happening
00183 //-----------------------------------------------------------
00184 #ifdef DO_REFLOW_COUNTER
00185 
00186 #define MAX_REFLOW_CNT 1024
00187 static PRInt32 gTotalReqs    = 0;;
00188 static PRInt32 gTotalReflows = 0;;
00189 static PRInt32 gReflowControlCntRQ[MAX_REFLOW_CNT];
00190 static PRInt32 gReflowControlCnt[MAX_REFLOW_CNT];
00191 static PRInt32 gReflowInx = -1;
00192 
00193 #define REFLOW_COUNTER() \
00194   if (mReflowId > -1) \
00195     gReflowControlCnt[mReflowId]++;
00196 
00197 #define REFLOW_COUNTER_REQUEST() \
00198   if (mReflowId > -1) \
00199     gReflowControlCntRQ[mReflowId]++;
00200 
00201 #define REFLOW_COUNTER_DUMP(__desc) \
00202   if (mReflowId > -1) {\
00203     gTotalReqs    += gReflowControlCntRQ[mReflowId];\
00204     gTotalReflows += gReflowControlCnt[mReflowId];\
00205     printf("** Id:%5d %s RF: %d RQ: %d   %d/%d  %5.2f\n", \
00206            mReflowId, (__desc), \
00207            gReflowControlCnt[mReflowId], \
00208            gReflowControlCntRQ[mReflowId],\
00209            gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\
00210   }
00211 
00212 #define REFLOW_COUNTER_INIT() \
00213   if (gReflowInx < MAX_REFLOW_CNT) { \
00214     gReflowInx++; \
00215     mReflowId = gReflowInx; \
00216     gReflowControlCnt[mReflowId] = 0; \
00217     gReflowControlCntRQ[mReflowId] = 0; \
00218   } else { \
00219     mReflowId = -1; \
00220   }
00221 
00222 // reflow messages
00223 #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1))
00224 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
00225 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
00226 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
00227 
00228 #else //-------------
00229 
00230 #define REFLOW_COUNTER_REQUEST() 
00231 #define REFLOW_COUNTER() 
00232 #define REFLOW_COUNTER_DUMP(__desc) 
00233 #define REFLOW_COUNTER_INIT() 
00234 
00235 #define REFLOW_DEBUG_MSG(_msg) 
00236 #define REFLOW_DEBUG_MSG2(_msg1, _msg2) 
00237 #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) 
00238 #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) 
00239 
00240 
00241 #endif
00242 
00243 //------------------------------------------
00244 // This is for being VERY noisy
00245 //------------------------------------------
00246 #ifdef DO_VERY_NOISY
00247 #define REFLOW_NOISY_MSG(_msg1) printf((_msg1))
00248 #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2))
00249 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3))
00250 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4))
00251 #else
00252 #define REFLOW_NOISY_MSG(_msg) 
00253 #define REFLOW_NOISY_MSG2(_msg1, _msg2) 
00254 #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) 
00255 #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) 
00256 #endif
00257 
00258 //------------------------------------------
00259 // Displays value in pixels or twips
00260 //------------------------------------------
00261 #ifdef DO_PIXELS
00262 #define PX(__v) __v / 15
00263 #else
00264 #define PX(__v) __v 
00265 #endif
00266 
00267 //------------------------------------------
00268 // Asserts if we return a desired size that 
00269 // doesn't correctly match the mComputedWidth
00270 //------------------------------------------
00271 #ifdef DO_UNCONSTRAINED_CHECK
00272 #define UNCONSTRAINED_CHECK() \
00273 if (aReflowState.mComputedWidth != NS_UNCONSTRAINEDSIZE) { \
00274   nscoord width = aDesiredSize.width - borderPadding.left - borderPadding.right; \
00275   if (width != aReflowState.mComputedWidth) { \
00276     printf("aDesiredSize.width %d %d != aReflowState.mComputedWidth %d\n", aDesiredSize.width, width, aReflowState.mComputedWidth); \
00277   } \
00278   NS_ASSERTION(width == aReflowState.mComputedWidth, "Returning bad value when constrained!"); \
00279 }
00280 #else
00281 #define UNCONSTRAINED_CHECK()
00282 #endif
00283 //------------------------------------------------------
00284 //-- Done with macros
00285 //------------------------------------------------------
00286 
00287 //---------------------------------------------------------
00288 nsListControlFrame::nsListControlFrame(nsIPresShell* aShell,
00289   nsIDocument* aDocument)
00290   : nsHTMLScrollFrame(aShell, PR_FALSE)
00291 {
00292   mComboboxFrame      = nsnull;
00293   mChangesSinceDragStart = PR_FALSE;
00294   mButtonDown         = PR_FALSE;
00295   mMaxWidth           = 0;
00296   mMaxHeight          = 0;
00297 
00298   mIsAllContentHere   = PR_FALSE;
00299   mIsAllFramesHere    = PR_FALSE;
00300   mHasBeenInitialized = PR_FALSE;
00301   mNeedToReset        = PR_TRUE;
00302   mPostChildrenLoadedReset = PR_FALSE;
00303 
00304   mCacheSize.width             = -1;
00305   mCacheSize.height            = -1;
00306   mCachedMaxElementWidth       = -1;
00307   mCachedAvailableSize.width   = -1;
00308   mCachedAvailableSize.height  = -1;
00309   mCachedUnconstrainedSize.width  = -1;
00310   mCachedUnconstrainedSize.height = -1;
00311   
00312   mOverrideReflowOpt           = PR_FALSE;
00313   mPassId                      = 0;
00314 
00315   mDummyFrame                  = nsnull;
00316 
00317   mControlSelectMode           = PR_FALSE;
00318   REFLOW_COUNTER_INIT()
00319 }
00320 
00321 //---------------------------------------------------------
00322 nsListControlFrame::~nsListControlFrame()
00323 {
00324   REFLOW_COUNTER_DUMP("nsLCF");
00325 
00326   mComboboxFrame = nsnull;
00327 }
00328 
00329 // for Bug 47302 (remove this comment later)
00330 NS_IMETHODIMP
00331 nsListControlFrame::Destroy(nsPresContext *aPresContext)
00332 {
00333   // get the receiver interface from the browser button's content node
00334   nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(mContent));
00335 
00336   // Clear the frame pointer on our event listener, just in case the
00337   // event listener can outlive the frame.
00338 
00339   mEventListener->SetFrame(nsnull);
00340 
00341   receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*,
00342                                                     mEventListener),
00343                                      NS_GET_IID(nsIDOMMouseListener));
00344 
00345   receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseMotionListener*,
00346                                                     mEventListener),
00347                                      NS_GET_IID(nsIDOMMouseMotionListener));
00348 
00349   receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*,
00350                                                     mEventListener),
00351                                      NS_GET_IID(nsIDOMKeyListener));
00352 
00353   nsFormControlFrame::RegUnRegAccessKey(aPresContext, NS_STATIC_CAST(nsIFrame*, this), PR_FALSE);
00354   return nsHTMLScrollFrame::Destroy(aPresContext);
00355 }
00356 
00357 NS_IMETHODIMP
00358 nsListControlFrame::Paint(nsPresContext*      aPresContext,
00359                           nsIRenderingContext& aRenderingContext,
00360                           const nsRect&        aDirtyRect,
00361                           nsFramePaintLayer    aWhichLayer,
00362                           PRUint32             aFlags)
00363 {
00364   if (!GetStyleVisibility()->IsVisible()) {
00365     return PR_FALSE;
00366   }
00367 
00368   // Don't allow painting of list controls when painting is suppressed.
00369   PRBool paintingSuppressed = PR_FALSE;
00370   aPresContext->PresShell()->IsPaintingSuppressed(&paintingSuppressed);
00371   if (paintingSuppressed)
00372     return NS_OK;
00373 
00374   // Start by assuming we are visible and need to be painted
00375   PRBool isVisible = PR_TRUE;
00376 
00377   if (aPresContext->IsPaginated()) {
00378     if (aPresContext->IsRenderingOnlySelection()) {
00379       // Check the quick way first
00380       PRBool isSelected = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;
00381       // if we aren't selected in the mState we could be a container
00382       // so check to see if we are in the selection range
00383       if (!isSelected) {
00384         nsCOMPtr<nsISelectionController> selcon;
00385         selcon = do_QueryInterface(aPresContext->PresShell());
00386         if (selcon) {
00387           nsCOMPtr<nsISelection> selection;
00388           selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
00389           nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
00390           selection->ContainsNode(node, PR_TRUE, &isVisible);
00391         } else {
00392           isVisible = PR_FALSE;
00393         }
00394       }
00395     }
00396   } 
00397 
00398   if (isVisible) {
00399     if (aWhichLayer == NS_FRAME_PAINT_LAYER_BACKGROUND) {
00400       const nsStyleDisplay* displayData = GetStyleDisplay();
00401       if (displayData->mAppearance) {
00402         nsITheme *theme = aPresContext->GetTheme();
00403         nsRect  rect(0, 0, mRect.width, mRect.height);
00404         if (theme && theme->ThemeSupportsWidget(aPresContext, this, displayData->mAppearance))
00405           theme->DrawWidgetBackground(&aRenderingContext, this, 
00406                                       displayData->mAppearance, rect, aDirtyRect); 
00407       }
00408     }
00409 
00410     return nsHTMLScrollFrame::Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer);
00411   }
00412 
00413   DO_GLOBAL_REFLOW_COUNT_DSP("nsListControlFrame", &aRenderingContext);
00414   return NS_OK;
00415 
00416 }
00417 
00418 /* Note: this is called by the SelectsAreaFrame, which is the same
00419    as the frame returned by GetOptionsContainer. It's the frame which is
00420    scrolled by us. */
00421 void nsListControlFrame::PaintFocus(nsIRenderingContext& aRC, nsFramePaintLayer aWhichLayer)
00422 {
00423   if (NS_FRAME_PAINT_LAYER_FOREGROUND != aWhichLayer) return;
00424 
00425   if (mFocused != this) return;
00426 
00427   // The mEndSelectionIndex is what is currently being selected
00428   // use the selected index if this is kNothingSelected
00429   PRInt32 focusedIndex;
00430   if (mEndSelectionIndex == kNothingSelected) {
00431     GetSelectedIndex(&focusedIndex);
00432   } else {
00433     focusedIndex = mEndSelectionIndex;
00434   }
00435 
00436   nsPresContext* presContext = GetPresContext();
00437   if (!GetScrollableView()) return;
00438 
00439   nsIPresShell *presShell = presContext->GetPresShell();
00440   if (!presShell) return;
00441 
00442   nsIFrame* containerFrame;
00443   GetOptionsContainer(presContext, &containerFrame);
00444   if (!containerFrame) return;
00445 
00446   nsIFrame * childframe = nsnull;
00447   nsresult result = NS_ERROR_FAILURE;
00448 
00449   nsCOMPtr<nsIContent> focusedContent;
00450 
00451   nsCOMPtr<nsIDOMNSHTMLSelectElement> selectNSElement(do_QueryInterface(mContent));
00452   NS_ASSERTION(selectNSElement, "Can't be null");
00453 
00454   nsCOMPtr<nsISelectElement> selectElement(do_QueryInterface(mContent));
00455   NS_ASSERTION(selectElement, "Can't be null");
00456 
00457   // If we have a selected index then get that child frame
00458   if (focusedIndex != kNothingSelected) {
00459     focusedContent = GetOptionContent(focusedIndex);
00460     // otherwise we find the focusedContent's frame and scroll to it
00461     if (focusedContent) {
00462       result = presShell->GetPrimaryFrameFor(focusedContent, &childframe);
00463     }
00464   } else {
00465     nsCOMPtr<nsIDOMHTMLSelectElement> selectHTMLElement(do_QueryInterface(mContent));
00466     NS_ASSERTION(selectElement, "Can't be null");
00467 
00468     // Since there isn't a selected item we need to show a focus ring around the first
00469     // non-disabled item and skip all the option group elements (nodes)
00470     nsCOMPtr<nsIDOMNode> node;
00471 
00472     PRUint32 length;
00473     selectHTMLElement->GetLength(&length);
00474     if (length) {
00475       // find the first non-disabled item
00476       PRBool isDisabled = PR_TRUE;
00477       for (PRInt32 i=0;i<PRInt32(length) && isDisabled;i++) {
00478         if (NS_FAILED(selectNSElement->Item(i, getter_AddRefs(node))) || !node) {
00479           break;
00480         }
00481         if (NS_FAILED(selectElement->IsOptionDisabled(i, &isDisabled))) {
00482           break;
00483         }
00484         if (isDisabled) {
00485           node = nsnull;
00486         } else {
00487           break;
00488         }
00489       }
00490       if (!node) {
00491         return;
00492       }
00493     }
00494 
00495     // if we found a node use, if not get the first child (this is for empty selects)
00496     if (node) {
00497       focusedContent = do_QueryInterface(node);
00498       result = presShell->GetPrimaryFrameFor(focusedContent, &childframe);
00499     }
00500     if (!childframe) {
00501       // The only way we can get right here is that there are no options
00502       // and we need to get the dummy frame so it has the focus ring
00503       childframe = containerFrame->GetFirstChild(nsnull);
00504       result = NS_OK;
00505     }
00506   }
00507 
00508   if (NS_FAILED(result) || !childframe) return;
00509 
00510   // get the child rect
00511   nsRect fRect = childframe->GetRect();
00512   
00513   // get it into the coordinates of containerFrame
00514   fRect.MoveBy(childframe->GetParent()->GetOffsetTo(containerFrame));
00515   PRBool lastItemIsSelected = PR_FALSE;
00516   if (focusedIndex != kNothingSelected) {
00517     nsCOMPtr<nsIDOMNode> node;
00518     if (NS_SUCCEEDED(selectNSElement->Item(focusedIndex, getter_AddRefs(node)))) {
00519       nsCOMPtr<nsIDOMHTMLOptionElement> domOpt(do_QueryInterface(node));
00520       NS_ASSERTION(domOpt, "Something has gone seriously awry.  This should be an option element!");
00521       domOpt->GetSelected(&lastItemIsSelected);
00522     }
00523   }
00524 
00525   // set up back stop colors and then ask L&F service for the real colors
00526   nscolor color;
00527   presContext->LookAndFeel()->
00528     GetColor(lastItemIsSelected ?
00529              nsILookAndFeel::eColor_WidgetSelectForeground :
00530              nsILookAndFeel::eColor_WidgetSelectBackground, color);
00531 
00532   nscoord onePixelInTwips = presContext->IntScaledPixelsToTwips(1);
00533 
00534   nsRect dirty;
00535   nscolor colors[] = {color, color, color, color};
00536   PRUint8 borderStyle[] = {NS_STYLE_BORDER_STYLE_DOTTED, NS_STYLE_BORDER_STYLE_DOTTED, NS_STYLE_BORDER_STYLE_DOTTED, NS_STYLE_BORDER_STYLE_DOTTED};
00537   nsRect innerRect = fRect;
00538   innerRect.Deflate(nsSize(onePixelInTwips, onePixelInTwips));
00539   nsCSSRendering::DrawDashedSides(0, aRC, dirty, borderStyle, colors, fRect, innerRect, 0, nsnull);
00540 
00541 }
00542 
00543 //---------------------------------------------------------
00544 // Frames are not refcounted, no need to AddRef
00545 NS_IMETHODIMP
00546 nsListControlFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
00547 {
00548   if (NULL == aInstancePtr) {
00549     return NS_ERROR_NULL_POINTER;
00550   }
00551   if (aIID.Equals(NS_GET_IID(nsIFormControlFrame))) {
00552     *aInstancePtr = (void*) ((nsIFormControlFrame*) this);
00553     return NS_OK;
00554   }
00555   if (aIID.Equals(NS_GET_IID(nsIListControlFrame))) {
00556     *aInstancePtr = (void *)((nsIListControlFrame*)this);
00557     return NS_OK;
00558   }
00559   if (aIID.Equals(NS_GET_IID(nsISelectControlFrame))) {
00560     *aInstancePtr = (void *)((nsISelectControlFrame*)this);
00561     return NS_OK;
00562   }
00563   return nsHTMLScrollFrame::QueryInterface(aIID, aInstancePtr);
00564 }
00565 
00566 #ifdef ACCESSIBILITY
00567 NS_IMETHODIMP nsListControlFrame::GetAccessible(nsIAccessible** aAccessible)
00568 {
00569   nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
00570 
00571   if (accService) {
00572     nsCOMPtr<nsIDOMNode> node = do_QueryInterface(mContent);
00573     return accService->CreateHTMLListboxAccessible(node, GetPresContext(),
00574                                                    aAccessible);
00575   }
00576 
00577   return NS_ERROR_FAILURE;
00578 }
00579 #endif
00580 
00581 //---------------------------------------------------------
00582 // Reflow is overriden to constrain the listbox height to the number of rows and columns
00583 // specified. 
00584 #ifdef DO_REFLOW_DEBUG
00585 static int myCounter = 0;
00586 
00587 static void printSize(char * aDesc, nscoord aSize) 
00588 {
00589   printf(" %s: ", aDesc);
00590   if (aSize == NS_UNCONSTRAINEDSIZE) {
00591     printf("UNC");
00592   } else {
00593     printf("%d", aSize);
00594   }
00595 }
00596 #endif
00597 
00598 static nscoord
00599 GetMaxOptionHeight(nsIFrame* aContainer)
00600 {
00601   nscoord result = 0;
00602   for (nsIFrame* option = aContainer->GetFirstChild(nsnull);
00603        option; option = option->GetNextSibling()) {
00604     nscoord optionHeight;
00605     if (nsCOMPtr<nsIDOMHTMLOptGroupElement>
00606         (do_QueryInterface(option->GetContent()))) {
00607       // an optgroup
00608       optionHeight = GetMaxOptionHeight(option);
00609     } else {
00610       // an option
00611       optionHeight = option->GetSize().height;
00612     }
00613     if (result < optionHeight)
00614       result = optionHeight;
00615   }
00616   return result;
00617 }
00618 
00619 static inline PRBool
00620 IsOptGroup(nsIContent *aContent)
00621 {
00622   nsINodeInfo *ni = aContent->GetNodeInfo();
00623   return (ni && ni->Equals(nsHTMLAtoms::optgroup) &&
00624           aContent->IsContentOfType(nsIContent::eHTML));
00625 }
00626 
00627 static inline PRBool
00628 IsOption(nsIContent *aContent)
00629 {
00630   nsINodeInfo *ni = aContent->GetNodeInfo();
00631   return (ni && ni->Equals(nsHTMLAtoms::option) &&
00632           aContent->IsContentOfType(nsIContent::eHTML));
00633 }
00634 
00635 static PRUint32
00636 GetNumberOfOptionsRecursive(nsIContent* aContent)
00637 {
00638   PRUint32 optionCount = 0;
00639   const PRUint32 childCount = aContent ? aContent->GetChildCount() : 0;
00640   for (PRUint32 index = 0; index < childCount; ++index) {
00641     nsIContent* child = aContent->GetChildAt(index);
00642     if (::IsOption(child)) {
00643       ++optionCount;
00644     }
00645     else if (::IsOptGroup(child)) {
00646       optionCount += ::GetNumberOfOptionsRecursive(child);
00647     }
00648   }
00649   return optionCount;
00650 }
00651 
00652 static nscoord
00653 GetOptGroupLabelsHeight(nsPresContext* aPresContext,
00654                         nsIContent*    aContent,
00655                         nscoord        aRowHeight)
00656 {
00657   nscoord height = 0;
00658   const PRUint32 childCount = aContent ? aContent->GetChildCount() : 0;
00659   for (PRUint32 index = 0; index < childCount; ++index) {
00660     nsIContent* child = aContent->GetChildAt(index);
00661     if (::IsOptGroup(child)) {
00662       PRUint32 numOptions = ::GetNumberOfOptionsRecursive(child);
00663       nscoord optionsHeight = aRowHeight * numOptions;
00664       nsIFrame* frame = nsnull;
00665       aPresContext->GetPresShell()->GetPrimaryFrameFor(child, &frame);
00666       nscoord totalHeight = frame ? frame->GetSize().height : 0;
00667       height += PR_MAX(0, totalHeight - optionsHeight);
00668     }
00669   }
00670   return height;
00671 }
00672 
00673 //-----------------------------------------------------------------
00674 // Main Reflow for ListBox/Dropdown
00675 //-----------------------------------------------------------------
00676 NS_IMETHODIMP 
00677 nsListControlFrame::Reflow(nsPresContext*          aPresContext, 
00678                            nsHTMLReflowMetrics&     aDesiredSize,
00679                            const nsHTMLReflowState& aReflowState, 
00680                            nsReflowStatus&          aStatus)
00681 {
00682   DO_GLOBAL_REFLOW_COUNT("nsListControlFrame", aReflowState.reason);
00683   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
00684   REFLOW_COUNTER_REQUEST();
00685 
00686   aStatus = NS_FRAME_COMPLETE;
00687 
00688 #ifdef DO_REFLOW_DEBUG
00689   printf("%p ** Id: %d nsLCF::Reflow %d R: ", this, mReflowId, myCounter++);
00690   switch (aReflowState.reason) {
00691     case eReflowReason_Initial:
00692       printf("Initia");break;
00693     case eReflowReason_Incremental:
00694       printf("Increm");break;
00695     case eReflowReason_Resize:
00696       printf("Resize");break;
00697     case eReflowReason_StyleChange:
00698       printf("StyleC");break;
00699     case eReflowReason_Dirty:
00700       printf("Dirty ");break;
00701     default:printf("<unknown>%d", aReflowState.reason);break;
00702   }
00703   
00704   printSize("AW", aReflowState.availableWidth);
00705   printSize("AH", aReflowState.availableHeight);
00706   printSize("CW", aReflowState.mComputedWidth);
00707   printSize("CH", aReflowState.mComputedHeight);
00708   printf("\n");
00709 #if 0
00710     {
00711       const nsStyleDisplay* display = GetStyleDisplay();
00712       printf("+++++++++++++++++++++++++++++++++ ");
00713       switch (display->mVisible) {
00714         case NS_STYLE_VISIBILITY_COLLAPSE: printf("NS_STYLE_VISIBILITY_COLLAPSE\n");break;
00715         case NS_STYLE_VISIBILITY_HIDDEN:   printf("NS_STYLE_VISIBILITY_HIDDEN\n");break;
00716         case NS_STYLE_VISIBILITY_VISIBLE:  printf("NS_STYLE_VISIBILITY_VISIBLE\n");break;
00717       }
00718     }
00719 #endif
00720 #endif // DEBUG_rodsXXX
00721 
00722   PRBool bailOnWidth;
00723   PRBool bailOnHeight;
00724   // This ifdef is for turning off the optimization
00725   // so we can check timings against the old version
00726 #if 1
00727 
00728   nsFormControlFrame::SkipResizeReflow(mCacheSize,
00729                                        mCachedAscent,
00730                                        mCachedMaxElementWidth, 
00731                                        mCachedAvailableSize, 
00732                                        aDesiredSize, aReflowState, 
00733                                        aStatus, 
00734                                        bailOnWidth, bailOnHeight);
00735 
00736   // Here we bail if both the height and the width haven't changed
00737   // also we see if we should override the optimization
00738   //
00739   // The optimization can get overridden by the combobox 
00740   // sometime the combobox knows that the list MUST do a full reflow
00741   // no matter what
00742   if (!mOverrideReflowOpt && bailOnWidth && bailOnHeight) {
00743     REFLOW_DEBUG_MSG3("*** Done nsLCF - Bailing on DW: %d  DH: %d ", PX(aDesiredSize.width), PX(aDesiredSize.height));
00744     REFLOW_DEBUG_MSG3("bailOnWidth %d  bailOnHeight %d\n", PX(bailOnWidth), PX(bailOnHeight));
00745     NS_ASSERTION(aDesiredSize.width < 100000, "Width is still NS_UNCONSTRAINEDSIZE");
00746     NS_ASSERTION(aDesiredSize.height < 100000, "Height is still NS_UNCONSTRAINEDSIZE");
00747     return NS_OK;
00748   } else if (mOverrideReflowOpt) {
00749     mOverrideReflowOpt = PR_FALSE;
00750   }
00751 
00752 #else
00753   bailOnWidth  = PR_FALSE;
00754   bailOnHeight = PR_FALSE;
00755 #endif
00756 
00757 #ifdef DEBUG_rodsXXX
00758   // Lists out all the options
00759   {
00760   nsresult rv = NS_ERROR_FAILURE; 
00761   nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
00762     getter_AddRefs(GetOptions(mContent));
00763   if (options) {
00764     PRUint32 numOptions;
00765     options->GetLength(&numOptions);
00766     printf("--- Num of Items %d ---\n", numOptions);
00767     for (PRUint32 i=0;i<numOptions;i++) {
00768       nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = getter_AddRefs(GetOption(options, i));
00769       if (optionElement) {
00770         nsAutoString text;
00771         rv = optionElement->GetLabel(text);
00772         if (NS_CONTENT_ATTR_HAS_VALUE != rv || text.IsEmpty()) {
00773           if (NS_OK != optionElement->GetText(text)) {
00774             text = "No Value";
00775           }
00776         } else {
00777             text = "No Value";
00778         }          
00779         printf("[%d] - %s\n", i, NS_LossyConvertUCS2toASCII(text).get());
00780       }
00781     }
00782   }
00783   }
00784 #endif // DEBUG_rodsXXX
00785 
00786 
00787   // If all the content and frames are here 
00788   // then initialize it before reflow
00789     if (mIsAllContentHere && !mHasBeenInitialized) {
00790       if (PR_FALSE == mIsAllFramesHere) {
00791         CheckIfAllFramesHere();
00792       }
00793       if (mIsAllFramesHere && !mHasBeenInitialized) {
00794         mHasBeenInitialized = PR_TRUE;
00795       }
00796     }
00797 
00798 
00799     if (eReflowReason_Incremental == aReflowState.reason) {
00800       nsHTMLReflowCommand *command = aReflowState.path->mReflowCommand;
00801       if (command) {
00802         // XXX So this may do it too often
00803         // the side effect of this is if the user has scrolled to some other place in the list and
00804         // an incremental reflow comes through the list gets scrolled to the first selected item
00805         // I haven't been able to make it do it, but it will do it
00806         // basically the real solution is to know when all the reframes are there.
00807         PRInt32 selectedIndex = mEndSelectionIndex;
00808         if (selectedIndex == kNothingSelected) {
00809           GetSelectedIndex(&selectedIndex);
00810         }
00811         ScrollToIndex(selectedIndex);
00812       }
00813     }
00814 
00815    // Strategy: Let the inherited reflow happen as though the width and height of the
00816    // ScrollFrame are big enough to allow the listbox to
00817    // shrink to fit the longest option element line in the list.
00818    // The desired width and height returned by the inherited reflow is returned, 
00819    // unless one of the following has been specified.
00820    // 1. A css width has been specified.
00821    // 2. The size has been specified.
00822    // 3. The css height has been specified, but the number of rows has not.
00823    //  The size attribute overrides the height setting but the height setting
00824    // should be honored if there isn't a size specified.
00825 
00826     // Determine the desired width + height for the listbox + 
00827   aDesiredSize.width  = 0;
00828   aDesiredSize.height = 0;
00829 
00830   // Add the list frame as a child of the form
00831   if (eReflowReason_Initial == aReflowState.reason) {
00832     nsFormControlFrame::RegUnRegAccessKey(aPresContext, NS_STATIC_CAST(nsIFrame*, this), PR_TRUE);
00833   }
00834 
00835   //--Calculate a width just big enough for the scrollframe to shrink around the
00836   //longest element in the list
00837   nsHTMLReflowState secondPassState(aReflowState);
00838   nsHTMLReflowState firstPassState(aReflowState);
00839 
00840   //nsHTMLReflowState   firstPassState(aPresContext, nsnull,
00841   //                                   this, aDesiredSize);
00842 
00843    // Get the size of option elements inside the listbox
00844    // Compute the width based on the longest line in the listbox.
00845   
00846   firstPassState.mComputedWidth  = NS_UNCONSTRAINEDSIZE;
00847   firstPassState.mComputedHeight = NS_UNCONSTRAINEDSIZE;
00848   firstPassState.availableWidth  = NS_UNCONSTRAINEDSIZE;
00849   firstPassState.availableHeight = NS_UNCONSTRAINEDSIZE;
00850  
00851   nsHTMLReflowMetrics  scrolledAreaDesiredSize(PR_TRUE);
00852 
00853 
00854   if (eReflowReason_Incremental == aReflowState.reason) {
00855     nsHTMLReflowCommand *command = firstPassState.path->mReflowCommand;
00856     if (command) {
00857       nsReflowType type;
00858       command->GetType(type);
00859       firstPassState.reason = eReflowReason_StyleChange;
00860       firstPassState.path = nsnull;
00861     } else {
00862       nsresult res = nsHTMLScrollFrame::Reflow(aPresContext, 
00863                                                scrolledAreaDesiredSize,
00864                                                aReflowState, 
00865                                                aStatus);
00866       if (NS_FAILED(res)) {
00867         NS_ASSERTION(aDesiredSize.width < 100000, "Width is still NS_UNCONSTRAINEDSIZE");
00868         NS_ASSERTION(aDesiredSize.height < 100000, "Height is still NS_UNCONSTRAINEDSIZE");
00869         return res;
00870       }
00871 
00872       firstPassState.reason = eReflowReason_StyleChange;
00873       firstPassState.path = nsnull;
00874     }
00875   }
00876 
00877   if (mPassId == 0 || mPassId == 1) {
00878     if (mPassId == 0) {
00879       // We will reflow again, so tell the scrollframe not to scroll-to-restored-position
00880       // yet
00881       SetSuppressScrollbarUpdate(PR_TRUE);
00882     }
00883     nsresult res = nsHTMLScrollFrame::Reflow(aPresContext, 
00884                                              scrolledAreaDesiredSize,
00885                                              firstPassState, 
00886                                              aStatus);
00887     if (mPassId == 0) {
00888       SetSuppressScrollbarUpdate(PR_FALSE);
00889     }
00890     
00891     if (NS_FAILED(res)) {
00892       NS_ASSERTION(aDesiredSize.width < 100000, "Width is still NS_UNCONSTRAINEDSIZE");
00893       NS_ASSERTION(aDesiredSize.height < 100000, "Height is still NS_UNCONSTRAINEDSIZE");
00894       return res;
00895     }
00896     mCachedUnconstrainedSize.width  = scrolledAreaDesiredSize.width;
00897     mCachedUnconstrainedSize.height = scrolledAreaDesiredSize.height;
00898     mCachedAscent                   = scrolledAreaDesiredSize.ascent;
00899     mCachedDesiredMEW               = scrolledAreaDesiredSize.mMaxElementWidth;
00900   } else {
00901     scrolledAreaDesiredSize.width  = mCachedUnconstrainedSize.width;
00902     scrolledAreaDesiredSize.height = mCachedUnconstrainedSize.height;
00903     scrolledAreaDesiredSize.mMaxElementWidth = mCachedDesiredMEW;
00904   }
00905 
00906   // Compute the bounding box of the contents of the list using the area 
00907   // calculated by the first reflow as a starting point.
00908   //
00909   // The nsHTMLScrollFrame::Reflow adds border and padding to the
00910   // maxElementWidth, so these need to be subtracted
00911   nscoord scrolledAreaWidth  = scrolledAreaDesiredSize.width -
00912     (aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right);
00913   nscoord scrolledAreaHeight = scrolledAreaDesiredSize.height -
00914     (aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom);
00915 
00916   // Set up max values
00917   mMaxWidth  = scrolledAreaWidth;
00918 
00919   // Now the scrolledAreaWidth and scrolledAreaHeight are exactly 
00920   // wide and high enough to enclose their contents
00921   PRBool isInDropDownMode = IsInDropDownMode();
00922 
00923   nscoord visibleWidth = 0;
00924   if (NS_UNCONSTRAINEDSIZE == aReflowState.mComputedWidth) {
00925     visibleWidth = scrolledAreaWidth;
00926   } else {
00927     visibleWidth = aReflowState.mComputedWidth;
00928   }
00929 
00930    // Determine if a scrollbar will be needed, If so we need to add
00931    // enough the width to allow for the scrollbar.
00932    // The scrollbar will be needed under two conditions:
00933    // (size * heightOfaRow) < scrolledAreaHeight or
00934    // the height set through Style < scrolledAreaHeight.
00935 
00936    // Calculate the height of a single row in the listbox or dropdown
00937    // list by using the tallest of the grandchildren, since there may be
00938    // option groups in addition to option elements, either of which may
00939    // be visible or invisible.
00940   nsIFrame *optionsContainer;
00941   GetOptionsContainer(aPresContext, &optionsContainer);
00942   PRInt32 heightOfARow = GetMaxOptionHeight(optionsContainer);
00943 
00944   // Check to see if we have zero items 
00945   PRInt32 length = 0;
00946   GetNumberOfOptions(&length);
00947 
00948   // If there is only one option and that option's content is empty
00949   // then heightOfARow is zero, so we need to go measure 
00950   // the height of the option as if it had some text.
00951   if (heightOfARow == 0 && length > 0) {
00952     nsCOMPtr<nsIContent> option = GetOptionContent(0);
00953     if (option) {
00954       nsIFrame * optFrame;
00955       nsresult result = GetPresContext()->PresShell()->
00956         GetPrimaryFrameFor(option, &optFrame);
00957       if (NS_SUCCEEDED(result) && optFrame != nsnull) {
00958         nsStyleContext* optStyle = optFrame->GetStyleContext();
00959         if (optStyle) {
00960           const nsStyleFont* styleFont = optStyle->GetStyleFont();
00961           nsCOMPtr<nsIFontMetrics> fontMet;
00962           result = aPresContext->DeviceContext()->
00963             GetMetricsFor(styleFont->mFont, *getter_AddRefs(fontMet));
00964           if (NS_SUCCEEDED(result) && fontMet) {
00965             if (fontMet) {
00966               fontMet->GetHeight(heightOfARow);
00967               mMaxHeight = heightOfARow;
00968             }
00969           }
00970         }
00971       }
00972     }
00973   }
00974   mMaxHeight = heightOfARow;
00975 
00976   // Check to see if we have no width and height
00977   // The following code measures the width and height 
00978   // of a bogus string so the list actually displays
00979   nscoord visibleHeight = 0;
00980   if (isInDropDownMode) {
00981     // Compute the visible height of the drop-down list
00982     // The dropdown list height is the smaller of it's height setting or the height
00983     // of the smallest box that can drawn around it's contents.
00984     visibleHeight = scrolledAreaHeight;
00985 
00986     mNumDisplayRows = kMaxDropDownRows;
00987     if (visibleHeight > (mNumDisplayRows * heightOfARow)) {
00988       visibleHeight = (mNumDisplayRows * heightOfARow);
00989       // This is an adaptive algorithm for figuring out how many rows 
00990       // should be displayed in the drop down. The standard size is 20 rows, 
00991       // but on 640x480 it is typically too big.
00992       // This takes the height of the screen divides it by two and then subtracts off 
00993       // an estimated height of the combobox. I estimate it by taking the max element size
00994       // of the drop down and multiplying it by 2 (this is arbitrary) then subtract off
00995       // the border and padding of the drop down (again rather arbitrary)
00996       // This all breaks down if the font of the combobox is a lot larger then the option items
00997       // or CSS style has set the height of the combobox to be rather large.
00998       // We can fix these cases later if they actually happen.
00999       if (isInDropDownMode) {
01000         nscoord screenHeightInPixels = 0;
01001         if (NS_SUCCEEDED(nsFormControlFrame::GetScreenHeight(aPresContext, screenHeightInPixels))) {
01002           float   p2t;
01003           p2t = aPresContext->PixelsToTwips();
01004           nscoord screenHeight = NSIntPixelsToTwips(screenHeightInPixels, p2t);
01005 
01006           nscoord availDropHgt = (screenHeight / 2) - (heightOfARow*2); // approx half screen minus combo size
01007           availDropHgt -= aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
01008 
01009           nscoord hgt = visibleHeight + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
01010           if (heightOfARow > 0) {
01011             if (hgt > availDropHgt) {
01012               visibleHeight = (availDropHgt / heightOfARow) * heightOfARow;
01013             }
01014             mNumDisplayRows = visibleHeight / heightOfARow;
01015           } else {
01016             // Hmmm, not sure what to do here. Punt, and make both of them one
01017             visibleHeight   = 1;
01018             mNumDisplayRows = 1;
01019           }
01020         }
01021       }
01022     }
01023    
01024   } else {
01025       // Calculate the visible height of the listbox
01026     if (NS_UNCONSTRAINEDSIZE != aReflowState.mComputedHeight) {
01027       visibleHeight = aReflowState.mComputedHeight;
01028     } else {
01029       mNumDisplayRows = 1;
01030       GetSizeAttribute(&mNumDisplayRows);
01031       if (mNumDisplayRows >= 1) {
01032         visibleHeight = mNumDisplayRows * heightOfARow;
01033       } else {
01034         // When SIZE=0 or unspecified we constrain the height to [2..kMaxDropDownRows] rows.
01035         // We add in the height of optgroup labels (within the constraint above), bug 300474.
01036         visibleHeight = ::GetOptGroupLabelsHeight(GetPresContext(), mContent, heightOfARow);
01037 
01038         PRBool multipleSelections = PR_FALSE;
01039         GetMultiple(&multipleSelections);
01040         if (multipleSelections) {
01041           if (length < 2) {
01042             // Add in 1 heightOfARow also when length==0 to match how we calculate the desired size.
01043             visibleHeight = heightOfARow + PR_MAX(heightOfARow, visibleHeight);
01044           } else {
01045             visibleHeight = PR_MIN(kMaxDropDownRows * heightOfARow, length * heightOfARow + visibleHeight);
01046           }
01047         } else {
01048           visibleHeight += 2 * heightOfARow;
01049         }
01050       }
01051     }
01052   }
01053 
01054   // There are no items in the list
01055   // but we want to include space for the scrollbars
01056   // So fake like we will need scrollbars also
01057   if (!isInDropDownMode && 0 == length) {
01058     if (aReflowState.mComputedWidth != 0) {
01059       scrolledAreaHeight = visibleHeight+1;
01060     }
01061   }
01062 
01063   // The visible height is zero, this could be a select with no options
01064   // or a select with a single option that has no content or no label
01065   //
01066   // So this may not be the best solution, but we get the height of the font
01067   // for the list frame and use that as the max/minimum size for the contents
01068   if (visibleHeight == 0) {
01069     if (aReflowState.mComputedHeight != 0) {
01070       nsCOMPtr<nsIFontMetrics> fontMet;
01071       nsresult rvv = nsFormControlHelper::GetFrameFontFM(this, getter_AddRefs(fontMet));
01072       if (NS_SUCCEEDED(rvv) && fontMet) {
01073         aReflowState.rendContext->SetFont(fontMet);
01074         fontMet->GetHeight(visibleHeight);
01075         mMaxHeight = visibleHeight;
01076       }
01077     }
01078   }
01079 
01080   // When in dropdown mode make sure we obey min/max-width and min/max-height
01081   if (!isInDropDownMode) {
01082     nscoord fullWidth = visibleWidth + aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right;
01083     if (fullWidth > aReflowState.mComputedMaxWidth) {
01084       visibleWidth = aReflowState.mComputedMaxWidth - aReflowState.mComputedBorderPadding.left - aReflowState.mComputedBorderPadding.right;
01085     }
01086     if (fullWidth < aReflowState.mComputedMinWidth) {
01087       visibleWidth = aReflowState.mComputedMinWidth - aReflowState.mComputedBorderPadding.left - aReflowState.mComputedBorderPadding.right;
01088     }
01089 
01090     // calculate full height for comparison
01091     // must add in Border & Padding twice because the scrolled area also inherits Border & Padding
01092     nscoord fullHeight = 0;
01093     if (aReflowState.mComputedHeight != 0) {
01094       fullHeight = visibleHeight + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
01095                                          // + aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom;
01096     }
01097     if (fullHeight > aReflowState.mComputedMaxHeight) {
01098       visibleHeight = aReflowState.mComputedMaxHeight - aReflowState.mComputedBorderPadding.top - aReflowState.mComputedBorderPadding.bottom;
01099     }
01100     if (fullHeight < aReflowState.mComputedMinHeight) {
01101       visibleHeight = aReflowState.mComputedMinHeight - aReflowState.mComputedBorderPadding.top - aReflowState.mComputedBorderPadding.bottom;
01102     }
01103   }
01104 
01105    // Do a second reflow with the adjusted width and height settings
01106    // This sets up all of the frames with the correct width and height.
01107   secondPassState.mComputedWidth  = visibleWidth;
01108   secondPassState.mComputedHeight = visibleHeight;
01109   secondPassState.reason = eReflowReason_Resize;
01110 
01111   if (mPassId == 0 || mPassId == 2 || visibleHeight != scrolledAreaHeight ||
01112       visibleWidth != scrolledAreaWidth) {
01113     nsHTMLScrollFrame::Reflow(aPresContext, aDesiredSize, secondPassState, aStatus);
01114     if (aReflowState.mComputedHeight == 0) {
01115       aDesiredSize.ascent  = 0;
01116       aDesiredSize.descent = 0;
01117       aDesiredSize.height  = 0;
01118     }
01119 
01120     // Set the max element size to be the same as the desired element size.
01121   } else {
01122     // aDesiredSize is the desired frame size, so includes border and padding
01123     aDesiredSize.width  = visibleWidth +
01124       (aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right);
01125     aDesiredSize.height = visibleHeight +
01126       (aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom);
01127     aDesiredSize.ascent =
01128       scrolledAreaDesiredSize.ascent + aReflowState.mComputedBorderPadding.top;
01129     aDesiredSize.descent = aDesiredSize.height - aDesiredSize.ascent;
01130   }
01131 
01132   if (aDesiredSize.mComputeMEW) {
01133     aDesiredSize.mMaxElementWidth = aDesiredSize.width;
01134   }
01135 
01136   aStatus = NS_FRAME_COMPLETE;
01137 
01138 #ifdef DEBUG_rodsX
01139   if (!isInDropDownMode) {
01140     PRInt32 numRows = 1;
01141     GetSizeAttribute(&numRows);
01142     printf("%d ", numRows);
01143     if (numRows == 2) {
01144       COMPARE_QUIRK_SIZE("nsListControlFrame", 56, 38) 
01145     } else if (numRows == 3) {
01146       COMPARE_QUIRK_SIZE("nsListControlFrame", 56, 54) 
01147     } else if (numRows == 4) {
01148       COMPARE_QUIRK_SIZE("nsListControlFrame", 56, 70) 
01149     } else {
01150       COMPARE_QUIRK_SIZE("nsListControlFrame", 127, 118) 
01151     }
01152   }
01153 #endif
01154 
01155   if (aReflowState.availableWidth != NS_UNCONSTRAINEDSIZE) {
01156     mCachedAvailableSize.width  = aDesiredSize.width - (aReflowState.mComputedBorderPadding.left + aReflowState.mComputedBorderPadding.right);
01157     REFLOW_DEBUG_MSG2("** nsLCF Caching AW: %d\n", PX(mCachedAvailableSize.width));
01158   }
01159   if (aReflowState.availableHeight != NS_UNCONSTRAINEDSIZE) {
01160     mCachedAvailableSize.height = aDesiredSize.height - (aReflowState.mComputedBorderPadding.top + aReflowState.mComputedBorderPadding.bottom);
01161     REFLOW_DEBUG_MSG2("** nsLCF Caching AH: %d\n", PX(mCachedAvailableSize.height));
01162   }
01163 
01164   //REFLOW_DEBUG_MSG3("** nsLCF Caching AW: %d  AH: %d\n", PX(mCachedAvailableSize.width), PX(mCachedAvailableSize.height));
01165 
01166   nsFormControlFrame::SetupCachedSizes(mCacheSize, mCachedAscent,
01167                                        mCachedMaxElementWidth, aDesiredSize);
01168 
01169   REFLOW_DEBUG_MSG3("** Done nsLCF DW: %d  DH: %d\n\n", PX(aDesiredSize.width), PX(aDesiredSize.height));
01170 
01171   REFLOW_COUNTER();
01172 
01173 #ifdef DO_REFLOW_COUNTER
01174   if (gReflowControlCnt[mReflowId] > 50) {
01175     REFLOW_DEBUG_MSG3("** Id: %d Cnt: %d ", mReflowId, gReflowControlCnt[mReflowId]);
01176     REFLOW_DEBUG_MSG3("Done nsLCF DW: %d  DH: %d\n", PX(aDesiredSize.width), PX(aDesiredSize.height));
01177   }
01178 #endif
01179 
01180   NS_ASSERTION(aDesiredSize.width < 100000, "Width is still NS_UNCONSTRAINEDSIZE");
01181   NS_ASSERTION(aDesiredSize.height < 100000, "Height is still NS_UNCONSTRAINEDSIZE");
01182   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
01183   return NS_OK;
01184 }
01185 
01186 //---------------------------------------------------------
01187 NS_IMETHODIMP
01188 nsListControlFrame::GetFormContent(nsIContent*& aContent) const
01189 {
01190   aContent = GetContent();
01191   NS_IF_ADDREF(aContent);
01192   return NS_OK;
01193 }
01194 
01195 nsGfxScrollFrameInner::ScrollbarStyles
01196 nsListControlFrame::GetScrollbarStyles() const
01197 {
01198   // We can't express this in the style system yet; when we can, this can go away
01199   // and GetScrollbarStyles can be devirtualized
01200   PRInt32 verticalStyle = IsInDropDownMode() ? NS_STYLE_OVERFLOW_AUTO
01201     : NS_STYLE_OVERFLOW_SCROLL;
01202   return nsGfxScrollFrameInner::ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN,
01203                                                 verticalStyle);
01204 }
01205 
01206 //---------------------------------------------------------
01207 PRBool 
01208 nsListControlFrame::IsOptionElement(nsIContent* aContent)
01209 {
01210   PRBool result = PR_FALSE;
01211  
01212   nsCOMPtr<nsIDOMHTMLOptionElement> optElem;
01213   if (NS_SUCCEEDED(aContent->QueryInterface(NS_GET_IID(nsIDOMHTMLOptionElement),(void**) getter_AddRefs(optElem)))) {      
01214     if (optElem != nsnull) {
01215       result = PR_TRUE;
01216     }
01217   }
01218  
01219   return result;
01220 }
01221 
01222 nsIFrame*
01223 nsListControlFrame::GetContentInsertionFrame() {
01224   nsIFrame* frame;
01225   GetOptionsContainer(GetPresContext(), &frame);
01226   return frame->GetContentInsertionFrame();
01227 }
01228 
01229 //---------------------------------------------------------
01230 // Starts at the passed in content object and walks up the 
01231 // parent heierarchy looking for the nsIDOMHTMLOptionElement
01232 //---------------------------------------------------------
01233 nsIContent *
01234 nsListControlFrame::GetOptionFromContent(nsIContent *aContent) 
01235 {
01236   for (nsIContent* content = aContent; content; content = content->GetParent()) {
01237     if (IsOptionElement(content)) {
01238       return content;
01239     }
01240   }
01241 
01242   return nsnull;
01243 }
01244 
01245 //---------------------------------------------------------
01246 // Finds the index of the hit frame's content in the list
01247 // of option elements
01248 //---------------------------------------------------------
01249 PRInt32 
01250 nsListControlFrame::GetIndexFromContent(nsIContent *aContent)
01251 {
01252   nsCOMPtr<nsIDOMHTMLOptionElement> option;
01253   option = do_QueryInterface(aContent);
01254   if (option) {
01255     PRInt32 retval;
01256     option->GetIndex(&retval);
01257     if (retval >= 0) {
01258       return retval;
01259     }
01260   }
01261   return kNothingSelected;
01262 }
01263 
01264 //---------------------------------------------------------
01265 PRBool
01266 nsListControlFrame::ExtendedSelection(PRInt32 aStartIndex,
01267                                       PRInt32 aEndIndex,
01268                                       PRBool aClearAll)
01269 {
01270   return SetOptionsSelectedFromFrame(aStartIndex, aEndIndex,
01271                                      PR_TRUE, aClearAll);
01272 }
01273 
01274 //---------------------------------------------------------
01275 PRBool
01276 nsListControlFrame::SingleSelection(PRInt32 aClickedIndex, PRBool aDoToggle)
01277 {
01278   if (mComboboxFrame) {
01279     PRInt32 selectedIndex;
01280     GetSelectedIndex(&selectedIndex);
01281     mComboboxFrame->UpdateRecentIndex(selectedIndex);
01282   }
01283 
01284   PRBool wasChanged = PR_FALSE;
01285   // Get Current selection
01286   if (aDoToggle) {
01287     wasChanged = ToggleOptionSelectedFromFrame(aClickedIndex);
01288   } else {
01289     wasChanged = SetOptionsSelectedFromFrame(aClickedIndex, aClickedIndex,
01290                                 PR_TRUE, PR_TRUE);
01291   }
01292   ScrollToIndex(aClickedIndex);
01293   mStartSelectionIndex = aClickedIndex;
01294   mEndSelectionIndex = aClickedIndex;
01295   return wasChanged;
01296 }
01297 
01298 void
01299 nsListControlFrame::InitSelectionRange(PRInt32 aClickedIndex)
01300 {
01301   //
01302   // If nothing is selected, set the start selection depending on where
01303   // the user clicked and what the initial selection is:
01304   // - if the user clicked *before* selectedIndex, set the start index to
01305   //   the end of the first contiguous selection.
01306   // - if the user clicked *after* the end of the first contiguous
01307   //   selection, set the start index to selectedIndex.
01308   // - if the user clicked *within* the first contiguous selection, set the
01309   //   start index to selectedIndex.
01310   // The last two rules, of course, boil down to the same thing: if the user
01311   // clicked >= selectedIndex, return selectedIndex.
01312   //
01313   // This makes it so that shift click works properly when you first click
01314   // in a multiple select.
01315   //
01316   PRInt32 selectedIndex;
01317   GetSelectedIndex(&selectedIndex);
01318   if (selectedIndex >= 0) {
01319     // Get the end of the contiguous selection
01320     nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
01321       getter_AddRefs(GetOptions(mContent));
01322     NS_ASSERTION(options, "Collection of options is null!");
01323     PRUint32 numOptions;
01324     options->GetLength(&numOptions);
01325     PRUint32 i;
01326     // Push i to one past the last selected index in the group
01327     for (i=selectedIndex+1; i < numOptions; i++) {
01328       PRBool selected;
01329       GetOption(options, i)->GetSelected(&selected);
01330       if (!selected) {
01331         break;
01332       }
01333     }
01334 
01335     if (aClickedIndex < selectedIndex) {
01336       // User clicked before selection, so start selection at end of
01337       // contiguous selection
01338       mStartSelectionIndex = i-1;
01339       mEndSelectionIndex = selectedIndex;
01340     } else {
01341       // User clicked after selection, so start selection at start of
01342       // contiguous selection
01343       mStartSelectionIndex = selectedIndex;
01344       mEndSelectionIndex = i-1;
01345     }
01346   }
01347 }
01348 
01349 //---------------------------------------------------------
01350 PRBool
01351 nsListControlFrame::PerformSelection(PRInt32 aClickedIndex,
01352                                      PRBool aIsShift,
01353                                      PRBool aIsControl)
01354 {
01355   PRBool wasChanged = PR_FALSE;
01356 
01357   PRBool isMultiple;
01358   GetMultiple(&isMultiple);
01359 
01360   if (aClickedIndex == kNothingSelected) {
01361   } else if (isMultiple) {
01362     if (aIsShift) {
01363       // Make sure shift+click actually does something expected when
01364       // the user has never clicked on the select
01365       if (mStartSelectionIndex == kNothingSelected) {
01366         InitSelectionRange(aClickedIndex);
01367       }
01368 
01369       // Get the range from beginning (low) to end (high)
01370       // Shift *always* works, even if the current option is disabled
01371       PRInt32 startIndex;
01372       PRInt32 endIndex;
01373       if (mStartSelectionIndex == kNothingSelected) {
01374         startIndex = aClickedIndex;
01375         endIndex   = aClickedIndex;
01376       } else if (mStartSelectionIndex <= aClickedIndex) {
01377         startIndex = mStartSelectionIndex;
01378         endIndex   = aClickedIndex;
01379       } else {
01380         startIndex = aClickedIndex;
01381         endIndex   = mStartSelectionIndex;
01382       }
01383 
01384       // Clear only if control was not pressed
01385       wasChanged = ExtendedSelection(startIndex, endIndex, !aIsControl);
01386       ScrollToIndex(aClickedIndex);
01387 
01388       if (mStartSelectionIndex == kNothingSelected) {
01389         mStartSelectionIndex = aClickedIndex;
01390         mEndSelectionIndex = aClickedIndex;
01391       } else {
01392         mEndSelectionIndex = aClickedIndex;
01393       }
01394     } else if (aIsControl) {
01395       wasChanged = SingleSelection(aClickedIndex, PR_TRUE);
01396     } else {
01397       wasChanged = SingleSelection(aClickedIndex, PR_FALSE);
01398     }
01399   } else {
01400     wasChanged = SingleSelection(aClickedIndex, PR_FALSE);
01401   }
01402 
01403   return wasChanged;
01404 }
01405 
01406 //---------------------------------------------------------
01407 PRBool
01408 nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
01409                                         PRInt32 aClickedIndex)
01410 {
01411   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
01412   PRBool isShift;
01413   PRBool isControl;
01414 #if defined(XP_MAC) || defined(XP_MACOSX)
01415   mouseEvent->GetMetaKey(&isControl);
01416 #else
01417   mouseEvent->GetCtrlKey(&isControl);
01418 #endif
01419   mouseEvent->GetShiftKey(&isShift);
01420   return PerformSelection(aClickedIndex, isShift, isControl);
01421 }
01422 
01423 //---------------------------------------------------------
01424 NS_IMETHODIMP
01425 nsListControlFrame::CaptureMouseEvents(nsPresContext* aPresContext, PRBool aGrabMouseEvents)
01426 {
01427   // Currently cocoa widgets use a native popup widget which tracks clicks synchronously,
01428   // so we never want to do mouse capturing. Note that we only bail if the list
01429   // is in drop-down mode, and the caller is requesting capture (we let release capture
01430   // requests go through to ensure that we can release capture requested via other
01431   // code paths, if any exist).
01432   if (aGrabMouseEvents && IsInDropDownMode() && nsComboboxControlFrame::ToolkitHasNativePopup())
01433     return NS_OK;
01434 
01435   nsIView* view = GetScrolledFrame()->GetView();
01436 
01437   NS_ASSERTION(view, "no view???");
01438   NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
01439 
01440   nsIViewManager* viewMan = view->GetViewManager();
01441   if (viewMan) {
01442     PRBool result;
01443     // It's not clear why we don't have the widget capture mouse events here.
01444     if (aGrabMouseEvents) {
01445       viewMan->GrabMouseEvents(view, result);
01446     } else {
01447       nsIView* curGrabber;
01448       viewMan->GetMouseEventGrabber(curGrabber);
01449       PRBool dropDownIsHidden = PR_FALSE;
01450       if (IsInDropDownMode()) {
01451         PRBool isDroppedDown;
01452         mComboboxFrame->IsDroppedDown(&isDroppedDown);
01453         dropDownIsHidden = !isDroppedDown;
01454       }
01455       if (curGrabber == view || dropDownIsHidden) {
01456         // only unset the grabber if *we* are the ones doing the grabbing
01457         // (or if the dropdown is hidden, in which case NO-ONE should be
01458         // grabbing anything
01459         // it could be a scrollbar inside this listbox which is actually grabbing
01460         // This shouldn't be necessary. We should simply ensure that events targeting
01461         // scrollbars are never visible to DOM consumers.
01462         viewMan->GrabMouseEvents(nsnull, result);
01463       }
01464     }
01465   }
01466 
01467   return NS_OK;
01468 }
01469 
01470 //---------------------------------------------------------
01471 NS_IMETHODIMP 
01472 nsListControlFrame::HandleEvent(nsPresContext* aPresContext, 
01473                                        nsGUIEvent*     aEvent,
01474                                        nsEventStatus*  aEventStatus)
01475 {
01476   NS_ENSURE_ARG_POINTER(aEventStatus);
01477   // temp fix until Bug 124990 gets fixed
01478   if (aPresContext->IsPaginated() && NS_IS_MOUSE_EVENT(aEvent)) {
01479     return NS_OK;
01480   }
01481 
01482   /*const char * desc[] = {"NS_MOUSE_MOVE", 
01483                           "NS_MOUSE_LEFT_BUTTON_UP",
01484                           "NS_MOUSE_LEFT_BUTTON_DOWN",
01485                           "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
01486                           "NS_MOUSE_MIDDLE_BUTTON_UP",
01487                           "NS_MOUSE_MIDDLE_BUTTON_DOWN",
01488                           "<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>","<NA>",
01489                           "NS_MOUSE_RIGHT_BUTTON_UP",
01490                           "NS_MOUSE_RIGHT_BUTTON_DOWN",
01491                           "NS_MOUSE_ENTER_SYNTH",
01492                           "NS_MOUSE_EXIT_SYNTH",
01493                           "NS_MOUSE_LEFT_DOUBLECLICK",
01494                           "NS_MOUSE_MIDDLE_DOUBLECLICK",
01495                           "NS_MOUSE_RIGHT_DOUBLECLICK",
01496                           "NS_MOUSE_LEFT_CLICK",
01497                           "NS_MOUSE_MIDDLE_CLICK",
01498                           "NS_MOUSE_RIGHT_CLICK"};
01499   int inx = aEvent->message-NS_MOUSE_MESSAGE_START;
01500   if (inx >= 0 && inx <= (NS_MOUSE_RIGHT_CLICK-NS_MOUSE_MESSAGE_START)) {
01501     printf("Mouse in ListFrame %s [%d]\n", desc[inx], aEvent->message);
01502   } else {
01503     printf("Mouse in ListFrame <UNKNOWN> [%d]\n", aEvent->message);
01504   }*/
01505 
01506   if (nsEventStatus_eConsumeNoDefault == *aEventStatus)
01507     return NS_OK;
01508 
01509   // do we have style that affects how we are selected?
01510   // do we have user-input style?
01511   const nsStyleUserInterface* uiStyle = GetStyleUserInterface();
01512   if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE || uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED)
01513     return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
01514 
01515   if (nsFormControlHelper::GetDisabled(mContent))
01516     return NS_OK;
01517 
01518   return nsHTMLScrollFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
01519 }
01520 
01521 
01522 //---------------------------------------------------------
01523 NS_IMETHODIMP
01524 nsListControlFrame::SetInitialChildList(nsPresContext* aPresContext,
01525                                         nsIAtom*        aListName,
01526                                         nsIFrame*       aChildList)
01527 {
01528   // First check to see if all the content has been added
01529   mIsAllContentHere = mContent->IsDoneAddingChildren();
01530   if (!mIsAllContentHere) {
01531     mIsAllFramesHere    = PR_FALSE;
01532     mHasBeenInitialized = PR_FALSE;
01533   }
01534   nsresult rv = nsHTMLScrollFrame::SetInitialChildList(aPresContext, aListName, aChildList);
01535 
01536   // If all the content is here now check
01537   // to see if all the frames have been created
01538   /*if (mIsAllContentHere) {
01539     // If all content and frames are here
01540     // the reset/initialize
01541     if (CheckIfAllFramesHere()) {
01542       ResetList(aPresContext);
01543       mHasBeenInitialized = PR_TRUE;
01544     }
01545   }*/
01546 
01547   return rv;
01548 }
01549 
01550 //---------------------------------------------------------
01551 nsresult
01552 nsListControlFrame::GetSizeAttribute(PRInt32 *aSize) {
01553   nsresult rv = NS_OK;
01554   nsIDOMHTMLSelectElement* selectElement;
01555   rv = mContent->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement),(void**) &selectElement);
01556   if (mContent && NS_SUCCEEDED(rv)) {
01557     rv = selectElement->GetSize(aSize);
01558     NS_RELEASE(selectElement);
01559   }
01560   return rv;
01561 }
01562 
01563 
01564 //---------------------------------------------------------
01565 NS_IMETHODIMP  
01566 nsListControlFrame::Init(nsPresContext*  aPresContext,
01567                          nsIContent*      aContent,
01568                          nsIFrame*        aParent,
01569                          nsStyleContext*  aContext,
01570                          nsIFrame*        aPrevInFlow)
01571 {
01572   nsresult result = nsHTMLScrollFrame::Init(aPresContext, aContent, aParent, aContext,
01573                                             aPrevInFlow);
01574 
01575   // get the receiver interface from the browser button's content node
01576   nsCOMPtr<nsIDOMEventReceiver> receiver(do_QueryInterface(mContent));
01577 
01578   // we shouldn't have to unregister this listener because when
01579   // our frame goes away all these content node go away as well
01580   // because our frame is the only one who references them.
01581   // we need to hook up our listeners before the editor is initialized
01582   mEventListener = new nsListEventListener(this);
01583   if (!mEventListener) 
01584     return NS_ERROR_OUT_OF_MEMORY;
01585 
01586   receiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*,
01587                                                  mEventListener),
01588                                   NS_GET_IID(nsIDOMMouseListener));
01589 
01590   receiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseMotionListener*,
01591                                                  mEventListener),
01592                                   NS_GET_IID(nsIDOMMouseMotionListener));
01593 
01594   receiver->AddEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*,
01595                                                  mEventListener),
01596                                   NS_GET_IID(nsIDOMKeyListener));
01597 
01598   mStartSelectionIndex = kNothingSelected;
01599   mEndSelectionIndex = kNothingSelected;
01600 
01601   return result;
01602 }
01603 
01604 
01605 //---------------------------------------------------------
01606 nscoord 
01607 nsListControlFrame::GetVerticalInsidePadding(nsPresContext* aPresContext,
01608                                              float aPixToTwip, 
01609                                              nscoord aInnerHeight) const
01610 {
01611    return NSIntPixelsToTwips(0, aPixToTwip); 
01612 }
01613 
01614 
01615 //---------------------------------------------------------
01616 nscoord 
01617 nsListControlFrame::GetHorizontalInsidePadding(nsPresContext* aPresContext,
01618                                                float aPixToTwip, 
01619                                                nscoord aInnerWidth,
01620                                                nscoord aCharWidth) const
01621 {
01622   return GetVerticalInsidePadding(aPresContext, aPixToTwip, aInnerWidth);
01623 }
01624 
01625 
01626 NS_IMETHODIMP 
01627 nsListControlFrame::GetMultiple(PRBool* aMultiple, nsIDOMHTMLSelectElement* aSelect)
01628 {
01629   if (!aSelect) {
01630     nsIDOMHTMLSelectElement* selectElement = nsnull;
01631     nsresult result = mContent->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement),
01632                                                (void**)&selectElement);
01633     if (NS_SUCCEEDED(result) && selectElement) {
01634       result = selectElement->GetMultiple(aMultiple);
01635       NS_RELEASE(selectElement);
01636     } 
01637     return result;
01638   } else {
01639     return aSelect->GetMultiple(aMultiple);
01640   }
01641 }
01642 
01643 
01644 //---------------------------------------------------------
01645 // for a given piece of content it returns nsIDOMHTMLSelectElement object
01646 // or null 
01647 //---------------------------------------------------------
01648 nsIDOMHTMLSelectElement* 
01649 nsListControlFrame::GetSelect(nsIContent * aContent)
01650 {
01651   nsIDOMHTMLSelectElement* selectElement = nsnull;
01652   nsresult result = aContent->QueryInterface(NS_GET_IID(nsIDOMHTMLSelectElement),
01653                                              (void**)&selectElement);
01654   if (NS_SUCCEEDED(result) && selectElement) {
01655     return selectElement;
01656   } else {
01657     return nsnull;
01658   }
01659 }
01660 
01661 already_AddRefed<nsIContent> 
01662 nsListControlFrame::GetOptionAsContent(nsIDOMHTMLOptionsCollection* aCollection, PRInt32 aIndex) 
01663 {
01664   nsIContent * content = nsnull;
01665   nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = getter_AddRefs(GetOption(aCollection, aIndex));
01666 
01667   NS_ASSERTION(optionElement != nsnull, "could not get option element by index!");
01668 
01669   if (optionElement) {
01670     CallQueryInterface(optionElement, &content);
01671   }
01672  
01673   return content;
01674 }
01675 
01676 already_AddRefed<nsIContent> 
01677 nsListControlFrame::GetOptionContent(PRInt32 aIndex)
01678   
01679 {
01680   nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
01681     getter_AddRefs(GetOptions(mContent));
01682   NS_ASSERTION(options.get() != nsnull, "Collection of options is null!");
01683 
01684   if (options) {
01685     return GetOptionAsContent(options, aIndex);
01686   } 
01687   return nsnull;
01688 }
01689 
01690 nsIDOMHTMLOptionsCollection* 
01691 nsListControlFrame::GetOptions(nsIContent * aContent, nsIDOMHTMLSelectElement* aSelect)
01692 {
01693   nsIDOMHTMLOptionsCollection* options = nsnull;
01694   if (!aSelect) {
01695     nsCOMPtr<nsIDOMHTMLSelectElement> selectElement = getter_AddRefs(GetSelect(aContent));
01696     if (selectElement) {
01697       selectElement->GetOptions(&options);  // AddRefs (1)
01698     }
01699   } else {
01700     aSelect->GetOptions(&options); // AddRefs (1)
01701   }
01702 
01703   return options;
01704 }
01705 
01706 nsIDOMHTMLOptionElement*
01707 nsListControlFrame::GetOption(nsIDOMHTMLOptionsCollection* aCollection,
01708                               PRInt32 aIndex)
01709 {
01710   nsCOMPtr<nsIDOMNode> node;
01711   if (NS_SUCCEEDED(aCollection->Item(aIndex, getter_AddRefs(node)))) {
01712     NS_ASSERTION(node,
01713                  "Item was successful, but node from collection was null!");
01714     if (node) {
01715       nsIDOMHTMLOptionElement* option = nsnull;
01716       CallQueryInterface(node, &option);
01717 
01718       return option;
01719     }
01720   } else {
01721     NS_ERROR("Couldn't get option by index from collection!");
01722   }
01723   return nsnull;
01724 }
01725 
01726 PRBool 
01727 nsListControlFrame::IsContentSelected(nsIContent* aContent)
01728 {
01729   PRBool isSelected = PR_FALSE;
01730 
01731   nsCOMPtr<nsIDOMHTMLOptionElement> optEl = do_QueryInterface(aContent);
01732   if (optEl)
01733     optEl->GetSelected(&isSelected);
01734 
01735   return isSelected;
01736 }
01737 
01738 PRBool 
01739 nsListControlFrame::IsContentSelectedByIndex(PRInt32 aIndex) 
01740 {
01741   nsCOMPtr<nsIContent> content = GetOptionContent(aIndex);
01742   NS_ASSERTION(content, "Failed to retrieve option content");
01743 
01744   return IsContentSelected(content);
01745 }
01746 
01747 NS_IMETHODIMP
01748 nsListControlFrame::OnOptionSelected(nsPresContext* aPresContext,
01749                                      PRInt32 aIndex,
01750                                      PRBool aSelected)
01751 {
01752   if (aSelected) {
01753     ScrollToIndex(aIndex);
01754   }
01755   return NS_OK;
01756 }
01757 
01758 PRIntn
01759 nsListControlFrame::GetSkipSides() const
01760 {    
01761     // Don't skip any sides during border rendering
01762   return 0;
01763 }
01764 
01765 NS_IMETHODIMP_(PRInt32)
01766 nsListControlFrame::GetFormControlType() const
01767 {
01768   return NS_FORM_SELECT;
01769 }
01770 
01771 NS_IMETHODIMP
01772 nsListControlFrame::OnContentReset()
01773 {
01774   ResetList(PR_TRUE);
01775   return NS_OK;
01776 }
01777 
01778 void 
01779 nsListControlFrame::ResetList(PRBool aAllowScrolling)
01780 {
01781   REFLOW_DEBUG_MSG("LBX::ResetList\n");
01782 
01783   // if all the frames aren't here 
01784   // don't bother reseting
01785   if (!mIsAllFramesHere) {
01786     return;
01787   }
01788 
01789   if (aAllowScrolling) {
01790     mPostChildrenLoadedReset = PR_TRUE;
01791 
01792     // Scroll to the selected index
01793     PRInt32 indexToSelect = kNothingSelected;
01794 
01795     nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
01796     NS_ASSERTION(selectElement, "No select element!");
01797     if (selectElement) {
01798       selectElement->GetSelectedIndex(&indexToSelect);
01799       ScrollToIndex(indexToSelect);
01800     }
01801   }
01802 
01803   mStartSelectionIndex = kNothingSelected;
01804   mEndSelectionIndex = kNothingSelected;
01805 
01806   // Combobox will redisplay itself with the OnOptionSelected event
01807 } 
01808 
01809 //---------------------------------------------------------
01810 NS_IMETHODIMP
01811 nsListControlFrame::GetName(nsAString* aResult)
01812 {
01813   return nsFormControlHelper::GetName(mContent, aResult);
01814 }
01815  
01816 
01817 void 
01818 nsListControlFrame::SetFocus(PRBool aOn, PRBool aRepaint)
01819 {
01820   if (aOn) {
01821     ComboboxFocusSet();
01822     PRInt32 selectedIndex;
01823     GetSelectedIndex(&selectedIndex);
01824     mFocused = this;
01825   } else {
01826     mFocused = nsnull;
01827   }
01828 
01829   // Make sure the SelectArea frame gets painted
01830   Invalidate(nsRect(0,0,mRect.width,mRect.height), PR_TRUE);
01831 }
01832 
01833 void nsListControlFrame::ComboboxFocusSet()
01834 {
01835   gLastKeyTime = 0;
01836 }
01837 
01838 void 
01839 nsListControlFrame::ScrollIntoView(nsPresContext* aPresContext)
01840 {
01841   if (aPresContext) {
01842     nsIPresShell *presShell = aPresContext->GetPresShell();
01843     if (presShell) {
01844       presShell->ScrollFrameIntoView(this,
01845                    NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE);
01846     }
01847   }
01848 }
01849 
01850 
01851 //---------------------------------------------------------
01852 NS_IMETHODIMP 
01853 nsListControlFrame::SetComboboxFrame(nsIFrame* aComboboxFrame)
01854 {
01855   nsresult rv = NS_OK;
01856   if (nsnull != aComboboxFrame) {
01857     rv = aComboboxFrame->QueryInterface(NS_GET_IID(nsIComboboxControlFrame),(void**) &mComboboxFrame); 
01858   }
01859   return rv;
01860 }
01861 
01862 NS_IMETHODIMP 
01863 nsListControlFrame::GetOptionText(PRInt32 aIndex, nsAString & aStr)
01864 {
01865   aStr.SetLength(0);
01866   nsresult rv = NS_ERROR_FAILURE; 
01867   nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
01868     getter_AddRefs(GetOptions(mContent));
01869 
01870   if (options) {
01871     PRUint32 numOptions;
01872     options->GetLength(&numOptions);
01873 
01874     if (numOptions == 0) {
01875       rv = NS_OK;
01876     } else {
01877       nsCOMPtr<nsIDOMHTMLOptionElement> optionElement(
01878           getter_AddRefs(GetOption(options, aIndex)));
01879       if (optionElement) {
01880 #if 0 // This is for turning off labels Bug 4050
01881         nsAutoString text;
01882         rv = optionElement->GetLabel(text);
01883         // the return value is always NS_OK from DOMElements
01884         // it is meaningless to check for it
01885         if (!text.IsEmpty()) { 
01886           nsAutoString compressText = text;
01887           compressText.CompressWhitespace(PR_TRUE, PR_TRUE);
01888           if (!compressText.IsEmpty()) {
01889             text = compressText;
01890           }
01891         }
01892 
01893         if (text.IsEmpty()) {
01894           // the return value is always NS_OK from DOMElements
01895           // it is meaningless to check for it
01896           optionElement->GetText(text);
01897         }          
01898         aStr = text;
01899 #else
01900         optionElement->GetText(aStr);
01901 #endif
01902         rv = NS_OK;
01903       }
01904     }
01905   }
01906 
01907   return rv;
01908 }
01909 
01910 NS_IMETHODIMP 
01911 nsListControlFrame::GetSelectedIndex(PRInt32 * aIndex)
01912 {
01913   nsCOMPtr<nsIDOMHTMLSelectElement> selectElement(do_QueryInterface(mContent));
01914   return selectElement->GetSelectedIndex(aIndex);
01915 }
01916 
01917 PRBool 
01918 nsListControlFrame::IsInDropDownMode() const
01919 {
01920   return (mComboboxFrame != nsnull);
01921 }
01922 
01923 NS_IMETHODIMP 
01924 nsListControlFrame::GetNumberOfOptions(PRInt32* aNumOptions) 
01925 {
01926   if (mContent != nsnull) {
01927     nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
01928       getter_AddRefs(GetOptions(mContent));
01929 
01930     if (nsnull == options) {
01931       *aNumOptions = 0;
01932     } else {
01933       PRUint32 length = 0;
01934       options->GetLength(&length);
01935       *aNumOptions = (PRInt32)length;
01936     }
01937     return NS_OK;
01938   }
01939   *aNumOptions = 0;
01940   return NS_ERROR_FAILURE;
01941 }
01942 
01943 //----------------------------------------------------------------------
01944 // nsISelectControlFrame
01945 //----------------------------------------------------------------------
01946 PRBool nsListControlFrame::CheckIfAllFramesHere()
01947 {
01948   // Get the number of optgroups and options
01949   //PRInt32 numContentItems = 0;
01950   nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
01951   if (node) {
01952     // XXX Need to find a fail proff way to determine that
01953     // all the frames are there
01954     mIsAllFramesHere = PR_TRUE;//NS_OK == CountAllChild(node, numContentItems);
01955   }
01956   // now make sure we have a frame each piece of content
01957 
01958   return mIsAllFramesHere;
01959 }
01960 
01961 NS_IMETHODIMP
01962 nsListControlFrame::DoneAddingChildren(PRBool aIsDone)
01963 {
01964   mIsAllContentHere = aIsDone;
01965   if (mIsAllContentHere) {
01966     // Here we check to see if all the frames have been created 
01967     // for all the content.
01968     // If so, then we can initialize;
01969     if (mIsAllFramesHere == PR_FALSE) {
01970       // if all the frames are now present we can initalize
01971       if (CheckIfAllFramesHere()) {
01972         mHasBeenInitialized = PR_TRUE;
01973         ResetList(PR_TRUE);
01974       }
01975     }
01976   }
01977   return NS_OK;
01978 }
01979 
01980 NS_IMETHODIMP
01981 nsListControlFrame::AddOption(nsPresContext* aPresContext, PRInt32 aIndex)
01982 {
01983 #ifdef DO_REFLOW_DEBUG
01984   printf("---- Id: %d nsLCF %p Added Option %d\n", mReflowId, this, aIndex);
01985 #endif
01986 
01987   PRInt32 numOptions;
01988   GetNumberOfOptions(&numOptions);
01989 
01990   if (!mIsAllContentHere) {
01991     mIsAllContentHere = mContent->IsDoneAddingChildren();
01992     if (!mIsAllContentHere) {
01993       mIsAllFramesHere    = PR_FALSE;
01994       mHasBeenInitialized = PR_FALSE;
01995     } else {
01996       mIsAllFramesHere = aIndex == numOptions-1;
01997     }
01998   }
01999   
02000   if (!mHasBeenInitialized) {
02001     return NS_OK;
02002   }
02003 
02004   // Make sure we scroll to the selected option as needed
02005   mNeedToReset = PR_TRUE;
02006   mPostChildrenLoadedReset = mIsAllContentHere;
02007   return NS_OK;
02008 }
02009 
02010 NS_IMETHODIMP
02011 nsListControlFrame::RemoveOption(nsPresContext* aPresContext, PRInt32 aIndex)
02012 {
02013   // Need to reset if we're a dropdown
02014   if (IsInDropDownMode()) {
02015     mNeedToReset = PR_TRUE;
02016     mPostChildrenLoadedReset = mIsAllContentHere;
02017   }
02018 
02019   return NS_OK;
02020 }
02021 
02022 //---------------------------------------------------------
02023 // Set the option selected in the DOM.  This method is named
02024 // as it is because it indicates that the frame is the source
02025 // of this event rather than the receiver.
02026 PRBool
02027 nsListControlFrame::SetOptionsSelectedFromFrame(PRInt32 aStartIndex,
02028                                                 PRInt32 aEndIndex,
02029                                                 PRBool aValue,
02030                                                 PRBool aClearAll)
02031 {
02032   nsCOMPtr<nsISelectElement> selectElement(do_QueryInterface(mContent));
02033   PRBool wasChanged = PR_FALSE;
02034   nsresult rv = selectElement->SetOptionsSelectedByIndex(aStartIndex,
02035                                                          aEndIndex,
02036                                                          aValue,
02037                                                          aClearAll,
02038                                                          PR_FALSE,
02039                                                          PR_TRUE,
02040                                                          &wasChanged);
02041   NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
02042   return wasChanged;
02043 }
02044 
02045 PRBool
02046 nsListControlFrame::ToggleOptionSelectedFromFrame(PRInt32 aIndex)
02047 {
02048   nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
02049     getter_AddRefs(GetOptions(mContent));
02050   NS_ASSERTION(options, "No options");
02051   if (!options) {
02052     return PR_FALSE;
02053   }
02054   nsCOMPtr<nsIDOMHTMLOptionElement> option(
02055       getter_AddRefs(GetOption(options, aIndex)));
02056   NS_ASSERTION(option, "No option");
02057   if (!option) {
02058     return PR_FALSE;
02059   }
02060 
02061   PRBool value = PR_FALSE;
02062   nsresult rv = option->GetSelected(&value);
02063 
02064   NS_ASSERTION(NS_SUCCEEDED(rv), "GetSelected failed");
02065   nsCOMPtr<nsISelectElement> selectElement(do_QueryInterface(mContent));
02066   PRBool wasChanged = PR_FALSE;
02067   rv = selectElement->SetOptionsSelectedByIndex(aIndex,
02068                                                 aIndex,
02069                                                 !value,
02070                                                 PR_FALSE,
02071                                                 PR_FALSE,
02072                                                 PR_TRUE,
02073                                                 &wasChanged);
02074 
02075   NS_ASSERTION(NS_SUCCEEDED(rv), "SetSelected failed");
02076 
02077   return wasChanged;
02078 }
02079 
02080 
02081 // Dispatch event and such
02082 PRBool
02083 nsListControlFrame::UpdateSelection()
02084 {
02085   if (mIsAllFramesHere) {
02086     // if it's a combobox, display the new text
02087     if (mComboboxFrame) {
02088       mComboboxFrame->RedisplaySelectedText();
02089     }
02090     // if it's a listbox, fire on change
02091     else if (mIsAllContentHere) {
02092       nsWeakFrame weakFrame(this);
02093       FireOnChange();
02094       return weakFrame.IsAlive();
02095     }
02096   }
02097   return PR_TRUE;
02098 }
02099 
02100 NS_IMETHODIMP
02101 nsListControlFrame::ComboboxFinish(PRInt32 aIndex)
02102 {
02103   gLastKeyTime = 0;
02104 
02105   if (mComboboxFrame) {
02106     PerformSelection(aIndex, PR_FALSE, PR_FALSE);
02107 
02108     PRInt32 displayIndex;
02109     mComboboxFrame->GetIndexOfDisplayArea(&displayIndex);
02110 
02111     if (displayIndex != aIndex) {
02112       mComboboxFrame->RedisplaySelectedText();
02113     }
02114 
02115     mComboboxFrame->RollupFromList(GetPresContext()); // might destroy us
02116   }
02117 
02118   return NS_OK;
02119 }
02120 
02121 NS_IMETHODIMP
02122 nsListControlFrame::GetOptionsContainer(nsPresContext* aPresContext,
02123                                         nsIFrame** aFrame)
02124 {
02125   *aFrame = GetScrolledFrame();
02126   return NS_OK;
02127 }
02128 
02129 // Send out an onchange notification.
02130 NS_IMETHODIMP
02131 nsListControlFrame::FireOnChange()
02132 {
02133   nsresult rv = NS_OK;
02134   
02135   if (mComboboxFrame) {
02136     // Return hit without changing anything
02137     PRInt32 index = mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
02138     if (index == NS_SKIP_NOTIFY_INDEX)
02139       return NS_OK;
02140 
02141     // See if the selection actually changed
02142     PRInt32 selectedIndex;
02143     GetSelectedIndex(&selectedIndex);
02144     if (index == selectedIndex)
02145       return NS_OK;
02146   }
02147 
02148   // Dispatch the NS_FORM_CHANGE event
02149   nsEventStatus status = nsEventStatus_eIgnore;
02150   nsEvent event(PR_TRUE, NS_FORM_CHANGE);
02151 
02152   nsCOMPtr<nsIPresShell> presShell = GetPresContext()->GetPresShell();
02153   if (presShell) {
02154     rv = presShell->HandleEventWithTarget(&event, this, nsnull,
02155                                            NS_EVENT_FLAG_INIT, &status);
02156   }
02157 
02158   return rv;
02159 }
02160 
02161 // Determine if the specified item in the listbox is selected.
02162 NS_IMETHODIMP
02163 nsListControlFrame::GetOptionSelected(PRInt32 aIndex, PRBool* aValue)
02164 {
02165   *aValue = IsContentSelectedByIndex(aIndex);
02166   return NS_OK;
02167 }
02168 
02169 //---------------------------------------------------------
02170 // Used by layout to determine if we have a fake option
02171 NS_IMETHODIMP
02172 nsListControlFrame::GetDummyFrame(nsIFrame** aFrame)
02173 {
02174   (*aFrame) = mDummyFrame;
02175   return NS_OK;
02176 }
02177 
02178 NS_IMETHODIMP
02179 nsListControlFrame::SetDummyFrame(nsIFrame* aFrame)
02180 {
02181   mDummyFrame = aFrame;
02182   return NS_OK;
02183 }
02184 
02185 NS_IMETHODIMP
02186 nsListControlFrame::OnSetSelectedIndex(PRInt32 aOldIndex, PRInt32 aNewIndex)
02187 {
02188   if (mComboboxFrame) {
02189     // UpdateRecentIndex with NS_SKIP_NOTIFY_INDEX, so that we won't fire an onchange
02190     // event for this setting of selectedIndex.
02191     mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
02192   }
02193 
02194   ScrollToIndex(aNewIndex);
02195   mStartSelectionIndex = aNewIndex;
02196   mEndSelectionIndex = aNewIndex;
02197 
02198 #ifdef ACCESSIBILITY
02199   FireMenuItemActiveEvent();
02200 #endif
02201 
02202   return NS_OK;
02203 }
02204 
02205 //----------------------------------------------------------------------
02206 // End nsISelectControlFrame
02207 //----------------------------------------------------------------------
02208 
02209 NS_IMETHODIMP 
02210 nsListControlFrame::SetProperty(nsPresContext* aPresContext, nsIAtom* aName,
02211                                 const nsAString& aValue)
02212 {
02213   if (nsHTMLAtoms::selected == aName) {
02214     return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
02215   } else if (nsHTMLAtoms::selectedindex == aName) {
02216     // You shouldn't be calling me for this!!!
02217     return NS_ERROR_INVALID_ARG;
02218   }
02219 
02220   // We should be told about selectedIndex by the DOM element through
02221   // OnOptionSelected
02222 
02223   return NS_OK;
02224 }
02225 
02226 NS_IMETHODIMP 
02227 nsListControlFrame::GetProperty(nsIAtom* aName, nsAString& aValue)
02228 {
02229   // Get the selected value of option from local cache (optimization vs. widget)
02230   if (nsHTMLAtoms::selected == aName) {
02231     nsAutoString val(aValue);
02232     PRInt32 error = 0;
02233     PRBool selected = PR_FALSE;
02234     PRInt32 indx = val.ToInteger(&error, 10); // Get index from aValue
02235     if (error == 0)
02236        selected = IsContentSelectedByIndex(indx); 
02237   
02238     nsFormControlHelper::GetBoolString(selected, aValue);
02239     
02240   // For selectedIndex, get the value from the widget
02241   } else if (nsHTMLAtoms::selectedindex == aName) {
02242     // You shouldn't be calling me for this!!!
02243     return NS_ERROR_INVALID_ARG;
02244   }
02245 
02246   return NS_OK;
02247 }
02248 
02249 NS_IMETHODIMP 
02250 nsListControlFrame::SyncViewWithFrame()
02251 {
02252     // Resync the view's position with the frame.
02253     // The problem is the dropdown's view is attached directly under
02254     // the root view. This means it's view needs to have it's coordinates calculated
02255     // as if it were in it's normal position in the view hierarchy.
02256   mComboboxFrame->AbsolutelyPositionDropDown();
02257 
02258   nsContainerFrame::PositionFrameView(this);
02259 
02260   return NS_OK;
02261 }
02262 
02263 NS_IMETHODIMP 
02264 nsListControlFrame::AboutToDropDown()
02265 {
02266   if (mIsAllContentHere && mIsAllFramesHere && mHasBeenInitialized) {
02267     PRInt32 selectedIndex;
02268     GetSelectedIndex(&selectedIndex);
02269     ScrollToIndex(selectedIndex);
02270 #ifdef ACCESSIBILITY
02271     FireMenuItemActiveEvent(); // Inform assistive tech what got focus
02272 #endif
02273   }
02274   mItemSelectionStarted = PR_FALSE;
02275 
02276   return NS_OK;
02277 }
02278 
02279 // We are about to be rolledup from the outside (ComboboxFrame)
02280 NS_IMETHODIMP 
02281 nsListControlFrame::AboutToRollup()
02282 {
02283   // We've been updating the combobox with the keyboard up until now, but not
02284   // with the mouse.  The problem is, even with mouse selection, we are
02285   // updating the <select>.  So if the mouse goes over an option just before
02286   // he leaves the box and clicks, that's what the <select> will show.
02287   //
02288   // To deal with this we say "whatever is in the combobox is canonical."
02289   // - IF the combobox is different from the current selected index, we
02290   //   reset the index.
02291 
02292   if (IsInDropDownMode()) {
02293     PRInt32 index;
02294     mComboboxFrame->GetIndexOfDisplayArea(&index);
02295     ComboboxFinish(index); // might destroy us
02296   }
02297   return NS_OK;
02298 }
02299 
02300 NS_IMETHODIMP
02301 nsListControlFrame::DidReflow(nsPresContext*           aPresContext,
02302                               const nsHTMLReflowState*  aReflowState,
02303                               nsDidReflowStatus         aStatus)
02304 {
02305   nsresult rv;
02306   
02307   if (IsInDropDownMode()) 
02308   {
02309     //SyncViewWithFrame();
02310     rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
02311     SyncViewWithFrame();
02312   } else {
02313     rv = nsHTMLScrollFrame::DidReflow(aPresContext, aReflowState, aStatus);
02314   }
02315 
02316   if (mNeedToReset) {
02317     mNeedToReset = PR_FALSE;
02318     // Suppress scrolling to the selected element if we restored
02319     // scroll history state AND the list contents have not changed
02320     // since we loaded all the children AND nothing else forced us
02321     // to scroll by calling ResetList(PR_TRUE). The latter two conditions
02322     // are folded into mPostChildrenLoadedReset.
02323     //
02324     // The idea is that we want scroll history restoration to trump ResetList
02325     // scrolling to the selected element, when the ResetList was probably only
02326     // caused by content loading normally.
02327     ResetList(!DidHistoryRestore() || mPostChildrenLoadedReset);
02328   }
02329 
02330   return rv;
02331 }
02332 
02333 nsIAtom*
02334 nsListControlFrame::GetType() const
02335 {
02336   return nsLayoutAtoms::listControlFrame; 
02337 }
02338 
02339 #ifdef DEBUG
02340 NS_IMETHODIMP
02341 nsListControlFrame::GetFrameName(nsAString& aResult) const
02342 {
02343   return MakeFrameName(NS_LITERAL_STRING("ListControl"), aResult);
02344 }
02345 #endif
02346 
02347 //---------------------------------------------------------
02348 NS_IMETHODIMP 
02349 nsListControlFrame::GetMaximumSize(nsSize &aSize)
02350 {
02351   aSize.width  = mMaxWidth;
02352   aSize.height = mMaxHeight;
02353   return NS_OK;
02354 }
02355 
02356 
02357 NS_IMETHODIMP 
02358 nsListControlFrame::SetSuggestedSize(nscoord aWidth, nscoord aHeight)
02359 {
02360   return NS_OK;
02361 }
02362 
02363 nsresult
02364 nsListControlFrame::IsOptionDisabled(PRInt32 anIndex, PRBool &aIsDisabled)
02365 {
02366   nsCOMPtr<nsISelectElement> sel(do_QueryInterface(mContent));
02367   if (sel) {
02368     sel->IsOptionDisabled(anIndex, &aIsDisabled);
02369     return NS_OK;
02370   }
02371   return NS_ERROR_FAILURE;
02372 }
02373 
02374 //----------------------------------------------------------------------
02375 // helper
02376 //----------------------------------------------------------------------
02377 PRBool
02378 nsListControlFrame::IsLeftButton(nsIDOMEvent* aMouseEvent)
02379 {
02380   // only allow selection with the left button
02381   nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
02382   if (mouseEvent) {
02383     PRUint16 whichButton;
02384     if (NS_SUCCEEDED(mouseEvent->GetButton(&whichButton))) {
02385       return whichButton != 0?PR_FALSE:PR_TRUE;
02386     }
02387   }
02388   return PR_FALSE;
02389 }
02390 
02391 //----------------------------------------------------------------------
02392 // nsIDOMMouseListener
02393 //----------------------------------------------------------------------
02394 nsresult
02395 nsListControlFrame::MouseUp(nsIDOMEvent* aMouseEvent)
02396 {
02397   NS_ASSERTION(aMouseEvent != nsnull, "aMouseEvent is null.");
02398 
02399   UpdateInListState(aMouseEvent);
02400 
02401   REFLOW_DEBUG_MSG("--------------------------- MouseUp ----------------------------\n");
02402 
02403   mButtonDown = PR_FALSE;
02404 
02405   if (nsFormControlHelper::GetDisabled(mContent)) {
02406     return NS_OK;
02407   }
02408 
02409   // only allow selection with the left button
02410   // if a right button click is on the combobox itself
02411   // or on the select when in listbox mode, then let the click through
02412   if (!IsLeftButton(aMouseEvent)) {
02413     if (IsInDropDownMode()) {
02414       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
02415         aMouseEvent->PreventDefault();
02416         aMouseEvent->StopPropagation();
02417       } else {
02418         CaptureMouseEvents(GetPresContext(), PR_FALSE);
02419         return NS_OK;
02420       }
02421       CaptureMouseEvents(GetPresContext(), PR_FALSE);
02422       return NS_ERROR_FAILURE; // means consume event
02423     } else {
02424       CaptureMouseEvents(GetPresContext(), PR_FALSE);
02425       return NS_OK;
02426     }
02427   }
02428 
02429   const nsStyleVisibility* vis = GetStyleVisibility();
02430       
02431   if (!vis->IsVisible()) {
02432     REFLOW_DEBUG_MSG(">>>>>> Select is NOT visible");
02433     return NS_OK;
02434   }
02435 
02436   if (IsInDropDownMode()) {
02437     // XXX This is a bit of a hack, but.....
02438     // But the idea here is to make sure you get an "onclick" event when you mouse
02439     // down on the select and the drag over an option and let go
02440     // And then NOT get an "onclick" event when when you click down on the select
02441     // and then up outside of the select
02442     // the EventStateManager tracks the content of the mouse down and the mouse up
02443     // to make sure they are the same, and the onclick is sent in the PostHandleEvent
02444     // depeneding on whether the clickCount is non-zero.
02445     // So we cheat here by either setting or unsetting the clcikCount in the native event
02446     // so the right thing happens for the onclick event
02447     nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aMouseEvent));
02448     nsMouseEvent * mouseEvent;
02449     privateEvent->GetInternalNSEvent((nsEvent**)&mouseEvent);
02450 
02451     PRInt32 selectedIndex;
02452     if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
02453       REFLOW_DEBUG_MSG2(">>>>>> Found Index: %d", selectedIndex);
02454 
02455       // If it's disabled, disallow the click and leave.
02456       PRBool isDisabled = PR_FALSE;
02457       IsOptionDisabled(selectedIndex, isDisabled);
02458       if (isDisabled) {
02459         aMouseEvent->PreventDefault();
02460         aMouseEvent->StopPropagation();
02461         CaptureMouseEvents(GetPresContext(), PR_FALSE);
02462         return NS_ERROR_FAILURE;
02463       }
02464 
02465       if (kNothingSelected != selectedIndex) {
02466         nsWeakFrame weakFrame(this);
02467         ComboboxFinish(selectedIndex);
02468         if (!weakFrame.IsAlive())
02469           return NS_OK;
02470         FireOnChange();
02471       }
02472 
02473       mouseEvent->clickCount = 1;
02474     } else {
02475       // the click was out side of the select or its dropdown
02476       mouseEvent->clickCount = IgnoreMouseEventForSelection(aMouseEvent) ? 1 : 0;
02477     }
02478   } else {
02479     REFLOW_DEBUG_MSG(">>>>>> Didn't find");
02480     CaptureMouseEvents(GetPresContext(), PR_FALSE);
02481     // Notify
02482     if (mChangesSinceDragStart) {
02483       // reset this so that future MouseUps without a prior MouseDown
02484       // won't fire onchange
02485       mChangesSinceDragStart = PR_FALSE;
02486       FireOnChange();
02487     }
02488   }
02489 
02490   return NS_OK;
02491 }
02492 
02493 void
02494 nsListControlFrame::UpdateInListState(nsIDOMEvent* aEvent)
02495 {
02496   if (!mComboboxFrame)
02497     return;
02498 
02499   PRBool isDroppedDown;
02500   mComboboxFrame->IsDroppedDown(&isDroppedDown);
02501   if (!isDroppedDown)
02502     return;
02503 
02504   nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aEvent, this);
02505   nsRect borderInnerEdge = GetScrollableView()->View()->GetBounds();
02506   if (pt.y >= borderInnerEdge.y && pt.y < borderInnerEdge.YMost()) {
02507     mItemSelectionStarted = PR_TRUE;
02508   }
02509 }
02510 
02511 PRBool nsListControlFrame::IgnoreMouseEventForSelection(nsIDOMEvent* aEvent)
02512 {
02513   if (!mComboboxFrame)
02514     return PR_FALSE;
02515 
02516   // Our DOM listener does get called when the dropdown is not
02517   // showing, because it listens to events on the SELECT element
02518   PRBool isDroppedDown;
02519   mComboboxFrame->IsDroppedDown(&isDroppedDown);
02520   if (!isDroppedDown)
02521     return PR_TRUE;
02522 
02523   return !mItemSelectionStarted;
02524 }
02525 
02526 #ifdef ACCESSIBILITY
02527 void
02528 nsListControlFrame::FireMenuItemActiveEvent()
02529 {
02530   if (mFocused != this && !IsInDropDownMode()) {
02531     return;
02532   }
02533 
02534   // The mEndSelectionIndex is what is currently being selected
02535   // use the selected index if this is kNothingSelected
02536   PRInt32 focusedIndex;
02537   if (mEndSelectionIndex == kNothingSelected) {
02538     GetSelectedIndex(&focusedIndex);
02539   } else {
02540     focusedIndex = mEndSelectionIndex;
02541   }
02542   if (focusedIndex == kNothingSelected) {
02543     return;
02544   }
02545 
02546   nsCOMPtr<nsIContent> optionContent = GetOptionContent(focusedIndex);
02547   if (!optionContent) {
02548     return;
02549   }
02550 
02551   FireDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), optionContent);
02552 }
02553 #endif
02554 
02555 nsresult
02556 nsListControlFrame::GetIndexFromDOMEvent(nsIDOMEvent* aMouseEvent, 
02557                                          PRInt32&     aCurIndex)
02558 {
02559   if (IgnoreMouseEventForSelection(aMouseEvent)) {
02560     return NS_ERROR_FAILURE;
02561   }
02562 
02563   nsIView* view = GetScrolledFrame()->GetView();
02564   nsIViewManager* viewMan = view->GetViewManager();
02565   nsIView* curGrabber;
02566   viewMan->GetMouseEventGrabber(curGrabber);
02567   if (curGrabber != view) {
02568     // If we're not capturing, then ignore movement in the border
02569     nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
02570     nsRect borderInnerEdge = GetScrollableView()->View()->GetBounds();
02571     if (!borderInnerEdge.Contains(pt)) {
02572       return NS_ERROR_FAILURE;
02573     }
02574   }
02575 
02576   nsCOMPtr<nsIContent> content;
02577   GetPresContext()->EventStateManager()->
02578     GetEventTargetContent(nsnull, getter_AddRefs(content));
02579 
02580   nsCOMPtr<nsIContent> optionContent = GetOptionFromContent(content);
02581   if (optionContent) {
02582     aCurIndex = GetIndexFromContent(optionContent);
02583     return NS_OK;
02584   }
02585 
02586   nsIPresShell *presShell = GetPresContext()->PresShell();
02587   PRInt32 numOptions;
02588   GetNumberOfOptions(&numOptions);
02589   if (numOptions < 1)
02590     return NS_ERROR_FAILURE;
02591 
02592   nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(aMouseEvent, this);
02593 
02594   // If the event coordinate is above the first option frame, then target the
02595   // first option frame
02596   nsCOMPtr<nsIContent> firstOption = GetOptionContent(0);
02597   NS_ASSERTION(firstOption, "Can't find first option that's supposed to be there");
02598   nsIFrame* optionFrame;
02599   nsresult rv = presShell->GetPrimaryFrameFor(firstOption, &optionFrame);
02600   if (NS_SUCCEEDED(rv) && optionFrame) {
02601     nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
02602     if (ptInOptionFrame.y < 0 && ptInOptionFrame.x >= 0 &&
02603         ptInOptionFrame.x < optionFrame->GetSize().width) {
02604       aCurIndex = 0;
02605       return NS_OK;
02606     }
02607   }
02608 
02609   nsCOMPtr<nsIContent> lastOption = GetOptionContent(numOptions - 1);
02610   // If the event coordinate is below the last option frame, then target the
02611   // last option frame
02612   NS_ASSERTION(lastOption, "Can't find last option that's supposed to be there");
02613   rv = presShell->GetPrimaryFrameFor(lastOption, &optionFrame);
02614   if (NS_SUCCEEDED(rv) && optionFrame) {
02615     nsPoint ptInOptionFrame = pt - optionFrame->GetOffsetTo(this);
02616     if (ptInOptionFrame.y >= optionFrame->GetSize().height && ptInOptionFrame.x >= 0 &&
02617         ptInOptionFrame.x < optionFrame->GetSize().width) {
02618       aCurIndex = numOptions - 1;
02619       return NS_OK;
02620     }
02621   }
02622 
02623   return NS_ERROR_FAILURE;
02624 }
02625 
02626 nsresult
02627 nsListControlFrame::MouseDown(nsIDOMEvent* aMouseEvent)
02628 {
02629   NS_ASSERTION(aMouseEvent != nsnull, "aMouseEvent is null.");
02630 
02631   UpdateInListState(aMouseEvent);
02632 
02633   REFLOW_DEBUG_MSG("--------------------------- MouseDown ----------------------------\n");
02634 
02635   mButtonDown = PR_TRUE;
02636 
02637   if (nsFormControlHelper::GetDisabled(mContent)) {
02638     return NS_OK;
02639   }
02640 
02641   // only allow selection with the left button
02642   // if a right button click is on the combobox itself
02643   // or on the select when in listbox mode, then let the click through
02644   if (!IsLeftButton(aMouseEvent)) {
02645     if (IsInDropDownMode()) {
02646       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
02647         aMouseEvent->PreventDefault();
02648         aMouseEvent->StopPropagation();
02649       } else {
02650         return NS_OK;
02651       }
02652       return NS_ERROR_FAILURE; // means consume event
02653     } else {
02654       return NS_OK;
02655     }
02656   }
02657 
02658   PRInt32 selectedIndex;
02659   if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
02660     // Handle Like List
02661     CaptureMouseEvents(GetPresContext(), PR_TRUE);
02662     mChangesSinceDragStart = HandleListSelection(aMouseEvent, selectedIndex);
02663 #ifdef ACCESSIBILITY
02664     if (mChangesSinceDragStart) {
02665       FireMenuItemActiveEvent();
02666     }
02667 #endif
02668   } else {
02669     // NOTE: the combo box is responsible for dropping it down
02670     if (mComboboxFrame) {
02671       if (!IgnoreMouseEventForSelection(aMouseEvent)) {
02672         return NS_OK;
02673       }
02674 
02675       if (!nsComboboxControlFrame::ToolkitHasNativePopup())
02676       {
02677         PRBool isDroppedDown;
02678         mComboboxFrame->IsDroppedDown(&isDroppedDown);
02679         nsIFrame* comboFrame;
02680         CallQueryInterface(mComboboxFrame, &comboFrame);
02681         nsWeakFrame weakFrame(comboFrame);
02682         mComboboxFrame->ShowDropDown(!isDroppedDown);
02683         if (!weakFrame.IsAlive())
02684           return NS_OK;
02685         if (isDroppedDown) {
02686           CaptureMouseEvents(GetPresContext(), PR_FALSE);
02687         }
02688       }
02689     }
02690   }
02691 
02692   return NS_OK;
02693 }
02694 
02695 //----------------------------------------------------------------------
02696 // nsIDOMMouseMotionListener
02697 //----------------------------------------------------------------------
02698 nsresult
02699 nsListControlFrame::MouseMove(nsIDOMEvent* aMouseEvent)
02700 {
02701   NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
02702   //REFLOW_DEBUG_MSG("MouseMove\n");
02703 
02704   UpdateInListState(aMouseEvent);
02705 
02706   if (IsInDropDownMode()) { 
02707     PRBool isDroppedDown = PR_FALSE;
02708     mComboboxFrame->IsDroppedDown(&isDroppedDown);
02709     if (isDroppedDown) {
02710       PRInt32 selectedIndex;
02711       if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
02712         PerformSelection(selectedIndex, PR_FALSE, PR_FALSE);
02713       }
02714 
02715       // Make sure the SelectArea frame gets painted
02716       // XXX this shouldn't be needed, but other places in this code do it
02717       // and if we don't do this, invalidation doesn't happen when we move out
02718       // of the top-level window. We should track this down and fix it --- roc
02719       Invalidate(nsRect(0,0,mRect.width,mRect.height), PR_TRUE);
02720     }
02721   } else {// XXX - temporary until we get drag events
02722     if (mButtonDown) {
02723       return DragMove(aMouseEvent);
02724     }
02725   }
02726   return NS_OK;
02727 }
02728 
02729 nsresult
02730 nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
02731 {
02732   NS_ASSERTION(aMouseEvent, "aMouseEvent is null.");
02733   //REFLOW_DEBUG_MSG("DragMove\n");
02734 
02735   UpdateInListState(aMouseEvent);
02736 
02737   if (!IsInDropDownMode()) { 
02738     PRInt32 selectedIndex;
02739     if (NS_SUCCEEDED(GetIndexFromDOMEvent(aMouseEvent, selectedIndex))) {
02740       // Don't waste cycles if we already dragged over this item
02741       if (selectedIndex == mEndSelectionIndex) {
02742         return NS_OK;
02743       }
02744       nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
02745       NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
02746       PRBool isControl;
02747 #if defined(XP_MAC) || defined(XP_MACOSX)
02748       mouseEvent->GetMetaKey(&isControl);
02749 #else
02750       mouseEvent->GetCtrlKey(&isControl);
02751 #endif
02752       // Turn SHIFT on when you are dragging, unless control is on.
02753       PRBool wasChanged = PerformSelection(selectedIndex,
02754                                            !isControl, isControl);
02755       mChangesSinceDragStart = mChangesSinceDragStart || wasChanged;
02756     }
02757   }
02758   return NS_OK;
02759 }
02760 
02761 //----------------------------------------------------------------------
02762 // Scroll helpers.
02763 //----------------------------------------------------------------------
02764 nsresult
02765 nsListControlFrame::ScrollToIndex(PRInt32 aIndex)
02766 {
02767   if (aIndex < 0) {
02768     // XXX shouldn't we just do nothing if we're asked to scroll to
02769     // kNothingSelected?
02770     return ScrollToFrame(nsnull);
02771   } else {
02772     nsCOMPtr<nsIContent> content = GetOptionContent(aIndex);
02773     if (content) {
02774       return ScrollToFrame(content);
02775     }
02776   }
02777 
02778   return NS_ERROR_FAILURE;
02779 }
02780 
02781 nsresult
02782 nsListControlFrame::ScrollToFrame(nsIContent* aOptElement)
02783 {
02784   nsIScrollableView* scrollableView = GetScrollableView();
02785 
02786   if (scrollableView) {
02787     // if null is passed in we scroll to 0,0
02788     if (nsnull == aOptElement) {
02789       scrollableView->ScrollTo(0, 0, PR_TRUE);
02790       return NS_OK;
02791     }
02792   
02793     // otherwise we find the content's frame and scroll to it
02794     nsIPresShell *presShell = GetPresContext()->PresShell();
02795     nsIFrame * childframe;
02796     nsresult result;
02797     if (aOptElement) {
02798       result = presShell->GetPrimaryFrameFor(aOptElement, &childframe);
02799     } else {
02800       return NS_ERROR_FAILURE;
02801     }
02802 
02803     if (NS_SUCCEEDED(result) && childframe) {
02804       if (NS_SUCCEEDED(result) && scrollableView) {
02805         nscoord x;
02806         nscoord y;
02807         scrollableView->GetScrollPosition(x,y);
02808         // get the clipped rect
02809         nsRect rect = scrollableView->View()->GetBounds();
02810         // now move it by the offset of the scroll position
02811         rect.x = x;
02812         rect.y = y;
02813 
02814         // get the child
02815         nsRect fRect = childframe->GetRect();
02816         nsPoint pnt;
02817         nsIView * view;
02818         childframe->GetOffsetFromView(pnt, &view);
02819 
02820         // This change for 33421 (remove this comment later)
02821 
02822         // options can be a child of an optgroup
02823         // this checks to see the parent is an optgroup
02824         // and then adds in the parent's y coord
02825         // XXX this assume only one level of nesting of optgroups
02826         //   which is all the spec specifies at the moment.
02827         nsCOMPtr<nsIContent> parentContent = aOptElement->GetParent();
02828         nsCOMPtr<nsIDOMHTMLOptGroupElement> optGroup(do_QueryInterface(parentContent));
02829         nsRect optRect(0,0,0,0);
02830         if (optGroup) {
02831           nsIFrame * optFrame;
02832           result = presShell->GetPrimaryFrameFor(parentContent, &optFrame);
02833           if (NS_SUCCEEDED(result) && optFrame) {
02834             optRect = optFrame->GetRect();
02835           }
02836         }
02837         fRect.y += optRect.y;
02838 
02839         // See if the selected frame (fRect) is inside the scrolled
02840         // area (rect). Check only the vertical dimension. Don't
02841         // scroll just because there's horizontal overflow.
02842         if (!(rect.y <= fRect.y && fRect.YMost() <= rect.YMost())) {
02843           // figure out which direction we are going
02844           if (fRect.YMost() > rect.YMost()) {
02845             y = fRect.y-(rect.height-fRect.height);
02846           } else {
02847             y = fRect.y;
02848           }
02849           scrollableView->ScrollTo(pnt.x, y, PR_TRUE);
02850         }
02851 
02852       }
02853     }
02854   }
02855   return NS_OK;
02856 }
02857 
02858 //---------------------------------------------------------------------
02859 // Ok, the entire idea of this routine is to move to the next item that 
02860 // is suppose to be selected. If the item is disabled then we search in 
02861 // the same direction looking for the next item to select. If we run off 
02862 // the end of the list then we start at the end of the list and search 
02863 // backwards until we get back to the original item or an enabled option
02864 // 
02865 // aStartIndex - the index to start searching from
02866 // aNewIndex - will get set to the new index if it finds one
02867 // aNumOptions - the total number of options in the list
02868 // aDoAdjustInc - the initial increment 1-n
02869 // aDoAdjustIncNext - the increment used to search for the next enabled option
02870 //
02871 // the aDoAdjustInc could be a "1" for a single item or
02872 // any number greater representing a page of items
02873 //
02874 void
02875 nsListControlFrame::AdjustIndexForDisabledOpt(PRInt32 aStartIndex,
02876                                               PRInt32 &aNewIndex,
02877                                               PRInt32 aNumOptions,
02878                                               PRInt32 aDoAdjustInc,
02879                                               PRInt32 aDoAdjustIncNext)
02880 {
02881   // Cannot select anything if there is nothing to select
02882   if (aNumOptions == 0) {
02883     aNewIndex = kNothingSelected;
02884     return;
02885   }
02886 
02887   // means we reached the end of the list and now we are searching backwards
02888   PRBool doingReverse = PR_FALSE;
02889   // lowest index in the search range
02890   PRInt32 bottom      = 0;
02891   // highest index in the search range
02892   PRInt32 top         = aNumOptions;
02893 
02894   // Start off keyboard options at selectedIndex if nothing else is defaulted to
02895   //
02896   // XXX Perhaps this should happen for mouse too, to start off shift click
02897   // automatically in multiple ... to do this, we'd need to override
02898   // OnOptionSelected and set mStartSelectedIndex if nothing is selected.  Not
02899   // sure of the effects, though, so I'm not doing it just yet.
02900   PRInt32 startIndex = aStartIndex;
02901   if (startIndex < bottom) {
02902     GetSelectedIndex(&startIndex);
02903   }
02904   PRInt32 newIndex    = startIndex + aDoAdjustInc;
02905 
02906   // make sure we start off in the range
02907   if (newIndex < bottom) {
02908     newIndex = 0;
02909   } else if (newIndex >= top) {
02910     newIndex = aNumOptions-1;
02911   }
02912 
02913   while (1) {
02914     // if the newIndex isn't disabled, we are golden, bail out
02915     PRBool isDisabled = PR_TRUE;
02916     if (NS_SUCCEEDED(IsOptionDisabled(newIndex, isDisabled)) && !isDisabled) {
02917       break;
02918     }
02919 
02920     // it WAS disabled, so sart looking ahead for the next enabled option
02921     newIndex += aDoAdjustIncNext;
02922 
02923     // well, if we reach end reverse the search
02924     if (newIndex < bottom) {
02925       if (doingReverse) {
02926         return; // if we are in reverse mode and reach the end bail out
02927       } else {
02928         // reset the newIndex to the end of the list we hit
02929         // reverse the incrementer
02930         // set the other end of the list to our original starting index
02931         newIndex         = bottom;
02932         aDoAdjustIncNext = 1;
02933         doingReverse     = PR_TRUE;
02934         top              = startIndex;
02935       }
02936     } else  if (newIndex >= top) {
02937       if (doingReverse) {
02938         return;        // if we are in reverse mode and reach the end bail out
02939       } else {
02940         // reset the newIndex to the end of the list we hit
02941         // reverse the incrementer
02942         // set the other end of the list to our original starting index
02943         newIndex = top - 1;
02944         aDoAdjustIncNext = -1;
02945         doingReverse     = PR_TRUE;
02946         bottom           = startIndex;
02947       }
02948     }
02949   }
02950 
02951   // Looks like we found one
02952   aNewIndex     = newIndex;
02953 }
02954 
02955 nsAString& 
02956 nsListControlFrame::GetIncrementalString()
02957 { 
02958   static nsString incrementalString;
02959   return incrementalString; 
02960 }
02961 
02962 void
02963 nsListControlFrame::DropDownToggleKey(nsIDOMEvent* aKeyEvent)
02964 {
02965   // Cocoa widgets do native popups, so don't try to show
02966   // dropdowns there.
02967   if (IsInDropDownMode() && !nsComboboxControlFrame::ToolkitHasNativePopup()) {
02968     PRBool isDroppedDown;
02969     mComboboxFrame->IsDroppedDown(&isDroppedDown);
02970     aKeyEvent->PreventDefault();
02971     nsIFrame* comboFrame;
02972     CallQueryInterface(mComboboxFrame, &comboFrame);
02973     nsWeakFrame weakFrame(comboFrame);
02974     mComboboxFrame->ShowDropDown(!isDroppedDown);
02975     if (!weakFrame.IsAlive())
02976       return;
02977     mComboboxFrame->RedisplaySelectedText();
02978   }
02979 }
02980 
02981 nsresult
02982 nsListControlFrame::KeyPress(nsIDOMEvent* aKeyEvent)
02983 {
02984   NS_ASSERTION(aKeyEvent, "keyEvent is null.");
02985 
02986   if (nsFormControlHelper::GetDisabled(mContent))
02987     return NS_OK;
02988 
02989   // Start by making sure we can query for a key event
02990   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
02991   NS_ENSURE_TRUE(keyEvent, NS_ERROR_FAILURE);
02992 
02993   PRUint32 keycode = 0;
02994   PRUint32 charcode = 0;
02995   keyEvent->GetKeyCode(&keycode);
02996   keyEvent->GetCharCode(&charcode);
02997 #ifdef DO_REFLOW_DEBUG
02998   if (code >= 32) {
02999     REFLOW_DEBUG_MSG3("KeyCode: %c %d\n", code, code);
03000   }
03001 #endif
03002 
03003   PRBool isAlt = PR_FALSE;
03004 
03005   keyEvent->GetAltKey(&isAlt);
03006   if (isAlt) {
03007     if (keycode == nsIDOMKeyEvent::DOM_VK_UP || keycode == nsIDOMKeyEvent::DOM_VK_DOWN) {
03008       DropDownToggleKey(aKeyEvent);
03009     }
03010     return NS_OK;
03011   }
03012 
03013   // Get control / shift modifiers
03014   PRBool isControl = PR_FALSE;
03015   PRBool isShift   = PR_FALSE;
03016   keyEvent->GetCtrlKey(&isControl);
03017   if (!isControl) {
03018     keyEvent->GetMetaKey(&isControl);
03019   }
03020   keyEvent->GetShiftKey(&isShift);
03021 
03022   // now make sure there are options or we are wasting our time
03023   nsCOMPtr<nsIDOMHTMLOptionsCollection> options =
03024     getter_AddRefs(GetOptions(mContent));
03025   NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
03026 
03027   PRUint32 numOptions = 0;
03028   options->GetLength(&numOptions);
03029 
03030   // Whether we did an incremental search or another action
03031   PRBool didIncrementalSearch = PR_FALSE;
03032   
03033   // this is the new index to set
03034   // DOM_VK_RETURN & DOM_VK_ESCAPE will not set this
03035   PRInt32 newIndex = kNothingSelected;
03036 
03037   // set up the old and new selected index and process it
03038   // DOM_VK_RETURN selects the item
03039   // DOM_VK_ESCAPE cancels the selection
03040   // default processing checks to see if the pressed the first 
03041   //   letter of an item in the list and advances to it
03042   
03043   if (isControl && (keycode == nsIDOMKeyEvent::DOM_VK_UP ||
03044                     keycode == nsIDOMKeyEvent::DOM_VK_LEFT ||
03045                     keycode == nsIDOMKeyEvent::DOM_VK_DOWN ||
03046                     keycode == nsIDOMKeyEvent::DOM_VK_RIGHT)) {
03047     PRBool isMultiple;
03048     GetMultiple(&isMultiple);
03049     // Don't go into multiple select mode unless this list can handle it
03050     mControlSelectMode = isMultiple;
03051     isControl = isMultiple;
03052   } else if (charcode != ' ') {
03053     mControlSelectMode = PR_FALSE;
03054   }
03055   switch (keycode) {
03056 
03057     case nsIDOMKeyEvent::DOM_VK_UP:
03058     case nsIDOMKeyEvent::DOM_VK_LEFT: {
03059       REFLOW_DEBUG_MSG2("DOM_VK_UP   mEndSelectionIndex: %d ",
03060                         mEndSelectionIndex);
03061       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
03062                                 (PRInt32)numOptions,
03063                                 -1, -1);
03064       REFLOW_DEBUG_MSG2("  After: %d\n", newIndex);
03065       } break;
03066     
03067     case nsIDOMKeyEvent::DOM_VK_DOWN:
03068     case nsIDOMKeyEvent::DOM_VK_RIGHT: {
03069       REFLOW_DEBUG_MSG2("DOM_VK_DOWN mEndSelectionIndex: %d ",
03070                         mEndSelectionIndex);
03071 
03072       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
03073                                 (PRInt32)numOptions,
03074                                 1, 1);
03075       REFLOW_DEBUG_MSG2("  After: %d\n", newIndex);
03076       } break;
03077 
03078     case nsIDOMKeyEvent::DOM_VK_RETURN: {
03079       if (mComboboxFrame != nsnull) {
03080         PRBool droppedDown = PR_FALSE;
03081         mComboboxFrame->IsDroppedDown(&droppedDown);
03082         if (droppedDown) {
03083           nsWeakFrame weakFrame(this);
03084           ComboboxFinish(mEndSelectionIndex);
03085           if (!weakFrame.IsAlive())
03086             return NS_OK;
03087         }
03088         FireOnChange();
03089         return NS_OK;
03090       } else {
03091         newIndex = mEndSelectionIndex;
03092       }
03093       } break;
03094 
03095     case nsIDOMKeyEvent::DOM_VK_ESCAPE: {
03096       nsWeakFrame weakFrame(this);
03097       AboutToRollup();
03098       if (!weakFrame.IsAlive()) {
03099         aKeyEvent->PreventDefault(); // since we won't reach the one below
03100         return NS_OK;
03101       }
03102     } break;
03103 
03104     case nsIDOMKeyEvent::DOM_VK_PAGE_UP: {
03105       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
03106                                 (PRInt32)numOptions,
03107                                 -(mNumDisplayRows-1), -1);
03108       } break;
03109 
03110     case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN: {
03111       AdjustIndexForDisabledOpt(mEndSelectionIndex, newIndex,
03112                                 (PRInt32)numOptions,
03113                                 (mNumDisplayRows-1), 1);
03114       } break;
03115 
03116     case nsIDOMKeyEvent::DOM_VK_HOME: {
03117       AdjustIndexForDisabledOpt(0, newIndex,
03118                                 (PRInt32)numOptions,
03119                                 0, 1);
03120       } break;
03121 
03122     case nsIDOMKeyEvent::DOM_VK_END: {
03123       AdjustIndexForDisabledOpt(numOptions-1, newIndex,
03124                                 (PRInt32)numOptions,
03125                                 0, -1);
03126       } break;
03127 
03128 #if defined(XP_WIN) || defined(XP_OS2)
03129     case nsIDOMKeyEvent::DOM_VK_F4: {
03130       DropDownToggleKey(aKeyEvent);
03131       return NS_OK;
03132     } break;
03133 #endif
03134 
03135     case nsIDOMKeyEvent::DOM_VK_TAB: {
03136       return NS_OK;
03137     }
03138 
03139     default: { // Select option with this as the first character
03140                // XXX Not I18N compliant
03141       
03142       if (isControl && charcode != ' ') {
03143         return NS_OK;
03144       }
03145 
03146       didIncrementalSearch = PR_TRUE;
03147       if (charcode == 0) {
03148         // Backspace key will delete the last char in the string
03149         if (keycode == NS_VK_BACK && !GetIncrementalString().IsEmpty()) {
03150           GetIncrementalString().Truncate(GetIncrementalString().Length() - 1);
03151           aKeyEvent->PreventDefault();
03152         }
03153         return NS_OK;
03154       }
03155       
03156       DOMTimeStamp keyTime;
03157       aKeyEvent->GetTimeStamp(&keyTime);
03158 
03159       // Incremental Search: if time elapsed is below
03160       // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
03161       // string we will use to find options and start searching at the current
03162       // keystroke.  Otherwise, Truncate the string if it's been a long time
03163       // since our last keypress.
03164       if (keyTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
03165         // If this is ' ' and we are at the beginning of the string, treat it as
03166         // "select this option" (bug 191543)
03167         if (charcode == ' ') {
03168           newIndex = mEndSelectionIndex;
03169           break;
03170         }
03171         GetIncrementalString().Truncate();
03172       }
03173       gLastKeyTime = keyTime;
03174 
03175       // Append this keystroke to the search string. 
03176       PRUnichar uniChar = ToLowerCase(NS_STATIC_CAST(PRUnichar, charcode));
03177       GetIncrementalString().Append(uniChar);
03178 
03179       // See bug 188199, if all letters in incremental string are same, just try to match the first one
03180       nsAutoString incrementalString(GetIncrementalString());
03181       PRUint32 charIndex = 1, stringLength = incrementalString.Length();
03182       while (charIndex < stringLength && incrementalString[charIndex] == incrementalString[charIndex - 1]) {
03183         charIndex++;
03184       }
03185       if (charIndex == stringLength) {
03186         incrementalString.Truncate(1);
03187         stringLength = 1;
03188       }
03189 
03190       // Determine where we're going to start reading the string
03191       // If we have multiple characters to look for, we start looking *at* the
03192       // current option.  If we have only one character to look for, we start
03193       // looking *after* the current option.     
03194       // Exception: if there is no option selected to start at, we always start
03195       // *at* 0.
03196       PRInt32 startIndex;
03197       GetSelectedIndex(&startIndex);
03198       if (startIndex == kNothingSelected) {
03199         startIndex = 0;
03200       } else if (stringLength == 1) {
03201         startIndex++;
03202       }
03203 
03204       PRUint32 i;
03205       for (i = 0; i < numOptions; i++) {
03206         PRUint32 index = (i + startIndex) % numOptions;
03207         nsCOMPtr<nsIDOMHTMLOptionElement> optionElement(getter_AddRefs(GetOption(options, index)));
03208         if (optionElement) {
03209           nsAutoString text;
03210           if (NS_OK == optionElement->GetText(text)) {
03211             if (StringBeginsWith(text, incrementalString,
03212                                  nsCaseInsensitiveStringComparator())) {
03213               PRBool wasChanged = PerformSelection(index, isShift, isControl);
03214               if (wasChanged) {
03215                 // dispatch event, update combobox, etc.
03216                 if (!UpdateSelection()) {
03217                   return NS_OK;
03218                 }
03219               }
03220               break;
03221             }
03222           }
03223         }
03224       } // for
03225 
03226     } break;//case
03227   } // switch
03228 
03229   // We ate the key if we got this far.
03230   aKeyEvent->PreventDefault();
03231 
03232   // If we didn't do an incremental search, clear the string
03233   if (!didIncrementalSearch) {
03234     GetIncrementalString().Truncate();
03235   }
03236 
03237   // Actually process the new index and let the selection code
03238   // do the scrolling for us
03239   if (newIndex != kNothingSelected) {
03240     // If you hold control, no key will actually do anything except space.
03241     PRBool wasChanged = PR_FALSE;
03242     if (isControl && charcode != ' ') {
03243       mStartSelectionIndex = newIndex;
03244       mEndSelectionIndex = newIndex;
03245       ScrollToIndex(newIndex);
03246     } else if (mControlSelectMode && charcode == ' ') {
03247       wasChanged = SingleSelection(newIndex, PR_TRUE);
03248     } else {
03249       wasChanged = PerformSelection(newIndex, isShift, isControl);
03250     }
03251     if (wasChanged) {
03252        // dispatch event, update combobox, etc.
03253       if (!UpdateSelection()) {
03254         return NS_OK;
03255       }
03256     }
03257 #ifdef ACCESSIBILITY
03258     if (charcode != ' ') {
03259       FireMenuItemActiveEvent();
03260     }
03261 #endif
03262 
03263     // XXX - Are we cover up a problem here???
03264     // Why aren't they getting flushed each time?
03265     // because this isn't needed for Gfx
03266     if (IsInDropDownMode()) {
03267       // Don't flush anything but reflows lest it destroy us
03268       GetPresContext()->PresShell()->
03269         GetDocument()->FlushPendingNotifications(Flush_OnlyReflow);
03270     }
03271     REFLOW_DEBUG_MSG2("  After: %d\n", newIndex);
03272 
03273     // Make sure the SelectArea frame gets painted
03274     Invalidate(nsRect(0,0,mRect.width,mRect.height), PR_TRUE);
03275 
03276   } else {
03277     REFLOW_DEBUG_MSG("  After: SKIPPED it\n");
03278   }
03279 
03280   return NS_OK;
03281 }
03282 
03283 
03284 /******************************************************************************
03285  * nsListEventListener
03286  *****************************************************************************/
03287 
03288 NS_IMPL_ADDREF(nsListEventListener)
03289 NS_IMPL_RELEASE(nsListEventListener)
03290 NS_INTERFACE_MAP_BEGIN(nsListEventListener)
03291   NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener)
03292   NS_INTERFACE_MAP_ENTRY(nsIDOMMouseMotionListener)
03293   NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener)
03294   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMMouseListener)
03295   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMouseListener)
03296 NS_INTERFACE_MAP_END
03297 
03298 #define FORWARD_EVENT(_event) \
03299 NS_IMETHODIMP \
03300 nsListEventListener::_event(nsIDOMEvent* aEvent) \
03301 { \
03302   if (mFrame) \
03303     return mFrame->nsListControlFrame::_event(aEvent); \
03304   return NS_OK; \
03305 }
03306 
03307 #define IGNORE_EVENT(_event) \
03308 NS_IMETHODIMP \
03309 nsListEventListener::_event(nsIDOMEvent* aEvent) \
03310 { return NS_OK; }
03311 
03312 IGNORE_EVENT(HandleEvent)
03313 
03314 /*================== nsIDOMKeyListener =========================*/
03315 
03316 IGNORE_EVENT(KeyDown)
03317 IGNORE_EVENT(KeyUp)
03318 FORWARD_EVENT(KeyPress)
03319 
03320 /*=============== nsIDOMMouseListener ======================*/
03321 
03322 FORWARD_EVENT(MouseDown)
03323 FORWARD_EVENT(MouseUp)
03324 IGNORE_EVENT(MouseClick)
03325 IGNORE_EVENT(MouseDblClick)
03326 IGNORE_EVENT(MouseOver)
03327 IGNORE_EVENT(MouseOut)
03328 
03329 /*=============== nsIDOMMouseMotionListener ======================*/
03330 
03331 FORWARD_EVENT(MouseMove)
03332 // XXXbryner does anyone call this, ever?
03333 IGNORE_EVENT(DragMove)
03334 
03335 #undef FORWARD_EVENT
03336 #undef IGNORE_EVENT
03337