/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * Mats Palmgren * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nscore.h" #include "nsCOMPtr.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsListControlFrame.h" #include "nsFormControlFrame.h" // for COMPARE macro #include "nsHTMLAtoms.h" #include "nsIFormControl.h" #include "nsIDeviceContext.h" #include "nsIDocument.h" #include "nsIDOMHTMLCollection.h" #include "nsIDOMHTMLOptionsCollection.h" #include "nsIDOMNSHTMLOptionCollectn.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIDOMNSHTMLSelectElement.h" #include "nsIDOMHTMLOptionElement.h" #include "nsComboboxControlFrame.h" #include "nsIViewManager.h" #include "nsIScrollableView.h" #include "nsIDOMHTMLOptGroupElement.h" #include "nsWidgetsCID.h" #include "nsHTMLReflowCommand.h" #include "nsIPresShell.h" #include "nsHTMLParts.h" #include "nsIDOMEventReceiver.h" #include "nsEventDispatcher.h" #include "nsIEventStateManager.h" #include "nsIEventListenerManager.h" #include "nsIDOMKeyEvent.h" #include "nsIDOMMouseEvent.h" #include "nsIPrivateDOMEvent.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsIComponentManager.h" #include "nsILookAndFeel.h" #include "nsLayoutAtoms.h" #include "nsIFontMetrics.h" #include "nsIScrollableFrame.h" #include "nsIDOMEventTarget.h" #include "nsIDOMNSEvent.h" #include "nsGUIEvent.h" #include "nsIServiceManager.h" #include "nsINodeInfo.h" #ifdef ACCESSIBILITY #include "nsIAccessibilityService.h" #endif #include "nsISelectElement.h" #include "nsIPrivateDOMEvent.h" #include "nsCSSRendering.h" #include "nsReflowPath.h" #include "nsITheme.h" #include "nsIDOMMouseListener.h" #include "nsIDOMMouseMotionListener.h" #include "nsIDOMKeyListener.h" #include "nsLayoutUtils.h" #include "nsDisplayList.h" // Constants const nscoord kMaxDropDownRows = 20; // This matches the setting for 4.x browsers const PRInt32 kNothingSelected = -1; nsListControlFrame * nsListControlFrame::mFocused = nsnull; // Using for incremental typing navigation #define INCREMENTAL_SEARCH_KEYPRESS_TIME 1000 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: // nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml // need to find a good place to put them together. // if someone changes one, please also change the other. DOMTimeStamp nsListControlFrame::gLastKeyTime = 0; /****************************************************************************** * nsListEventListener * This class is responsible for propagating events to the nsListControlFrame. * Frames are not refcounted so they can't be used as event listeners. *****************************************************************************/ class nsListEventListener : public nsIDOMKeyListener, public nsIDOMMouseListener, public nsIDOMMouseMotionListener { public: nsListEventListener(nsListControlFrame *aFrame) : mFrame(aFrame) { } void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; } NS_DECL_ISUPPORTS // nsIDOMEventListener NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent); // nsIDOMKeyListener NS_IMETHOD KeyDown(nsIDOMEvent* aKeyEvent); NS_IMETHOD KeyUp(nsIDOMEvent* aKeyEvent); NS_IMETHOD KeyPress(nsIDOMEvent* aKeyEvent); // nsIDOMMouseListener NS_IMETHOD MouseDown(nsIDOMEvent* aMouseEvent); NS_IMETHOD MouseUp(nsIDOMEvent* aMouseEvent); NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent); NS_IMETHOD MouseDblClick(nsIDOMEvent* aMouseEvent); NS_IMETHOD MouseOver(nsIDOMEvent* aMouseEvent); NS_IMETHOD MouseOut(nsIDOMEvent* aMouseEvent); // nsIDOMMouseMotionListener NS_IMETHOD MouseMove(nsIDOMEvent* aMouseEvent); NS_IMETHOD DragMove(nsIDOMEvent* aMouseEvent); private: nsListControlFrame *mFrame; }; //--------------------------------------------------------- nsIFrame* NS_NewListControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { nsListControlFrame* it = new (aPresShell) nsListControlFrame(aPresShell, aPresShell->GetDocument(), aContext); if (it) { it->AddStateBits(NS_FRAME_INDEPENDENT_SELECTION); } return it; } //----------------------------------------------------------- // Reflow Debugging Macros // These let us "see" how many reflow counts are happening //----------------------------------------------------------- #ifdef DO_REFLOW_COUNTER #define MAX_REFLOW_CNT 1024 static PRInt32 gTotalReqs = 0;; static PRInt32 gTotalReflows = 0;; static PRInt32 gReflowControlCntRQ[MAX_REFLOW_CNT]; static PRInt32 gReflowControlCnt[MAX_REFLOW_CNT]; static PRInt32 gReflowInx = -1; #define REFLOW_COUNTER() \ if (mReflowId > -1) \ gReflowControlCnt[mReflowId]++; #define REFLOW_COUNTER_REQUEST() \ if (mReflowId > -1) \ gReflowControlCntRQ[mReflowId]++; #define REFLOW_COUNTER_DUMP(__desc) \ if (mReflowId > -1) {\ gTotalReqs += gReflowControlCntRQ[mReflowId];\ gTotalReflows += gReflowControlCnt[mReflowId];\ printf("** Id:%5d %s RF: %d RQ: %d %d/%d %5.2f\n", \ mReflowId, (__desc), \ gReflowControlCnt[mReflowId], \ gReflowControlCntRQ[mReflowId],\ gTotalReflows, gTotalReqs, float(gTotalReflows)/float(gTotalReqs)*100.0f);\ } #define REFLOW_COUNTER_INIT() \ if (gReflowInx < MAX_REFLOW_CNT) { \ gReflowInx++; \ mReflowId = gReflowInx; \ gReflowControlCnt[mReflowId] = 0; \ gReflowControlCntRQ[mReflowId] = 0; \ } else { \ mReflowId = -1; \ } // reflow messages #define REFLOW_DEBUG_MSG(_msg1) printf((_msg1)) #define REFLOW_DEBUG_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) #else //------------- #define REFLOW_COUNTER_REQUEST() #define REFLOW_COUNTER() #define REFLOW_COUNTER_DUMP(__desc) #define REFLOW_COUNTER_INIT() #define REFLOW_DEBUG_MSG(_msg) #define REFLOW_DEBUG_MSG2(_msg1, _msg2) #define REFLOW_DEBUG_MSG3(_msg1, _msg2, _msg3) #define REFLOW_DEBUG_MSG4(_msg1, _msg2, _msg3, _msg4) #endif //------------------------------------------ // This is for being VERY noisy //------------------------------------------ #ifdef DO_VERY_NOISY #define REFLOW_NOISY_MSG(_msg1) printf((_msg1)) #define REFLOW_NOISY_MSG2(_msg1, _msg2) printf((_msg1), (_msg2)) #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) printf((_msg1), (_msg2), (_msg3)) #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) printf((_msg1), (_msg2), (_msg3), (_msg4)) #else #define REFLOW_NOISY_MSG(_msg) #define REFLOW_NOISY_MSG2(_msg1, _msg2) #define REFLOW_NOISY_MSG3(_msg1, _msg2, _msg3) #define REFLOW_NOISY_MSG4(_msg1, _msg2, _msg3, _msg4) #endif //------------------------------------------ // Displays value in pixels or twips //------------------------------------------ #ifdef DO_PIXELS #define PX(__v) __v / 15 #else #define PX(__v) __v #endif //------------------------------------------ // Asserts if we return a desired size that // doesn't correctly match the mComputedWidth //------------------------------------------ #ifdef DO_UNCONSTRAINED_CHECK #define UNCONSTRAINED_CHECK() \ if (aReflowState.mComputedWidth != NS_UNCONSTRAINEDSIZE) { \ nscoord width = aDesiredSize.width - borderPadding.left - borderPadding.right; \ if (width != aReflowState.mComputedWidth) { \ printf("aDesiredSize.width %d %d != aReflowState.mComputedWidth %d\n", aDesiredSize.width, width, aReflowState.mComputedWidth); \ } \ NS_ASSERTION(width == aReflowState.mComputedWidth, "Returning bad value when constrained!"); \ } #else #define UNCONSTRAINED_CHECK() #endif //------------------------------------------------------ //-- Done with macros //------------------------------------------------------ //--------------------------------------------------------- nsListControlFrame::nsListControlFrame( nsIPresShell* aShell, nsIDocument* aDocument, nsStyleContext* aContext) : nsHTMLScrollFrame(aShell, aContext, PR_FALSE) { mComboboxFrame = nsnull; mChangesSinceDragStart = PR_FALSE; mButtonDown = PR_FALSE; mMaxWidth = 0; mMaxHeight = 0; mIsAllContentHere = PR_FALSE; mIsAllFramesHere = PR_FALSE; mHasBeenInitialized = PR_FALSE; mNeedToReset = PR_TRUE; mPostChildrenLoadedReset = PR_FALSE; mCacheSize.width = -1; mCacheSize.height = -1; mCachedMaxElementWidth = -1; mCachedAvailableSize.width = -1; mCachedAvailableSize.height = -1; mCachedUnconstrainedSize.width = -1; mCachedUnconstrainedSize.height = -1; mOverrideReflowOpt = PR_FALSE; mPassId = 0; mControlSelectMode = PR_FALSE; REFLOW_COUNTER_INIT() } //--------------------------------------------------------- nsListControlFrame::~nsListControlFrame() { REFLOW_COUNTER_DUMP("nsLCF"); mComboboxFrame = nsnull; } // for Bug 47302 (remove this comment later) void nsListControlFrame::Destroy() { // get the receiver interface from the browser button's content node nsCOMPtr receiver(do_QueryInterface(mContent)); // Clear the frame pointer on our event listener, just in case the // event listener can outlive the frame. mEventListener->SetFrame(nsnull); receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseListener*, mEventListener), NS_GET_IID(nsIDOMMouseListener)); receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMMouseMotionListener*, mEventListener), NS_GET_IID(nsIDOMMouseMotionListener)); receiver->RemoveEventListenerByIID(NS_STATIC_CAST(nsIDOMKeyListener*, mEventListener), NS_GET_IID(nsIDOMKeyListener)); nsFormControlFrame::RegUnRegAccessKey(NS_STATIC_CAST(nsIFrame*, this), PR_FALSE); nsHTMLScrollFrame::Destroy(); } NS_IMETHODIMP nsListControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { // We allow visibility:hidden . So if the mouse goes over an option just before // he leaves the box and clicks, that's what the