/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape 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/NPL/ * * 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.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Pierre Phaneuf */ #include "nsCOMPtr.h" #include "nsComboboxControlFrame.h" #include "nsFormFrame.h" #include "nsFormControlFrame.h" #include "nsIHTMLContent.h" #include "nsHTMLIIDs.h" #include "nsHTMLAtoms.h" #include "nsHTMLParts.h" #include "nsIFormControl.h" #include "nsINameSpaceManager.h" #include "nsLayoutAtoms.h" #include "nsIDOMElement.h" #include "nsListControlFrame.h" #include "nsIListControlFrame.h" #include "nsIDOMHTMLCollection.h" #include "nsIDOMHTMLSelectElement.h" #include "nsIDOMHTMLOptionElement.h" #include "nsIPresShell.h" #include "nsIPresState.h" #include "nsISupportsArray.h" #include "nsIDeviceContext.h" #include "nsIView.h" #include "nsIScrollableView.h" #include "nsIEventStateManager.h" #include "nsIDOMNode.h" #include "nsIPrivateDOMEvent.h" #include "nsIStatefulFrame.h" #include "nsISupportsArray.h" #include "nsISelectControlFrame.h" #include "nsISupportsPrimitives.h" #include "nsIComponentManager.h" static NS_DEFINE_IID(kIFormControlFrameIID, NS_IFORMCONTROLFRAME_IID); static NS_DEFINE_IID(kIComboboxControlFrameIID, NS_ICOMBOBOXCONTROLFRAME_IID); static NS_DEFINE_IID(kIListControlFrameIID, NS_ILISTCONTROLFRAME_IID); static NS_DEFINE_IID(kIDOMMouseListenerIID, NS_IDOMMOUSELISTENER_IID); static NS_DEFINE_IID(kIFrameIID, NS_IFRAME_IID); static NS_DEFINE_IID(kIAnonymousContentCreatorIID, NS_IANONYMOUS_CONTENT_CREATOR_IID); static NS_DEFINE_IID(kIDOMNodeIID, NS_IDOMNODE_IID); static NS_DEFINE_IID(kIPrivateDOMEventIID, NS_IPRIVATEDOMEVENT_IID); // Drop down list event management. // The combo box uses the following strategy for managing the // drop-down list. // If the combo box or it's arrow button is clicked on the drop-down list is displayed // If mouse exit's the combo box with the drop-down list displayed the drop-down list // is asked to capture events // The drop-down list will capture all events including mouse down and up and will always // return with ListWasSelected method call regardless of whether an item in the list was // actually selected. // The ListWasSelected code will turn off mouse-capture for the drop-down list. // The drop-down list does not explicitly set capture when it is in the drop-down mode. //XXX: This is temporary. It simulates psuedo states by using a attribute selector on const char * kMozDropdownActive = "-moz-dropdown-active"; nsresult NS_NewComboboxControlFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame, PRUint32 aStateFlags) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsComboboxControlFrame* it = new (aPresShell) nsComboboxControlFrame; if (!it) { return NS_ERROR_OUT_OF_MEMORY; } // set the state flags (if any are provided) nsFrameState state; it->GetFrameState( &state ); state |= aStateFlags; it->SetFrameState( state ); *aNewFrame = it; return NS_OK; } nsComboboxControlFrame::nsComboboxControlFrame() : nsAreaFrame() { mPresContext = nsnull; mFormFrame = nsnull; mListControlFrame = nsnull; mTextStr = ""; mDisplayContent = nsnull; mButtonContent = nsnull; mDroppedDown = PR_FALSE; mDisplayFrame = nsnull; mButtonFrame = nsnull; mDropdownFrame = nsnull; mIgnoreFocus = PR_FALSE; mSelectedIndex = -1; mCacheSize.width = -1; mCacheSize.height = -1; mCachedMaxElementSize.width = -1; mCachedMaxElementSize.height = -1; //Shrink the area around it's contents //SetFlags(NS_BLOCK_SHRINK_WRAP); } //-------------------------------------------------------------- nsComboboxControlFrame::~nsComboboxControlFrame() { if (mFormFrame) { mFormFrame->RemoveFormControlFrame(*this); mFormFrame = nsnull; } NS_IF_RELEASE(mPresContext); NS_IF_RELEASE(mDisplayContent); NS_IF_RELEASE(mButtonContent); nsFormControlFrame::RegUnRegAccessKey(mPresContext, NS_STATIC_CAST(nsIFrame*, this), PR_FALSE); } //-------------------------------------------------------------- // Frames are not refcounted, no need to AddRef NS_IMETHODIMP nsComboboxControlFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { NS_PRECONDITION(0 != aInstancePtr, "null ptr"); if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kIComboboxControlFrameIID)) { *aInstancePtr = (void*) ((nsIComboboxControlFrame*) this); return NS_OK; } else if (aIID.Equals(kIFormControlFrameIID)) { *aInstancePtr = (void*) ((nsIFormControlFrame*) this); return NS_OK; } else if (aIID.Equals(kIAnonymousContentCreatorIID)) { *aInstancePtr = (void*)(nsIAnonymousContentCreator*) this; return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsISelectControlFrame))) { *aInstancePtr = (void *)(nsISelectControlFrame*)this; return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsIStatefulFrame))) { *aInstancePtr = (void *)(nsIStatefulFrame*)this; return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsIRollupListener))) { *aInstancePtr = (void*)(nsIRollupListener*)this; return NS_OK; } return nsAreaFrame::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsComboboxControlFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { // Need to hold on the pres context because it is used later in methods // which don't have it passed in. mPresContext = aPresContext; NS_ADDREF(mPresContext); return nsAreaFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); } //-------------------------------------------------------------- PRBool nsComboboxControlFrame::IsSuccessful(nsIFormControlFrame* aSubmitter) { nsAutoString name; return (NS_CONTENT_ATTR_HAS_VALUE == GetName(&name)); } // If nothing is selected, and we have options, select item 0 // This is a UI decision that goes against the HTML 4 spec. // See bugzilla bug 15841 for justification of this deviation. NS_IMETHODIMP nsComboboxControlFrame::MakeSureSomethingIsSelected(nsIPresContext* aPresContext) { nsIFormControlFrame* fcFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if (NS_SUCCEEDED(rv) && fcFrame) { // If nothing selected, and there are options, default selection to item 0 rv = mListControlFrame->GetSelectedIndex(&mSelectedIndex); if (NS_SUCCEEDED(rv) && (mSelectedIndex < 0)) { // Find out if there are any options in the list to select PRInt32 length = 0; mListControlFrame->GetNumberOfOptions(&length); if (length > 0) { // Set listbox selection to first item in the list box rv = fcFrame->SetProperty(aPresContext, nsHTMLAtoms::selectedindex, "0"); mSelectedIndex = 0; } UpdateSelection(PR_FALSE, PR_TRUE, mSelectedIndex); // Needed to reflow when removing last option } // Don't NS_RELEASE fcFrame here as it isn't addRef'd in the QI (???) } return rv; } // Initialize the text string in the combobox using either the current // selection in the list box or the first item item in the list box. void nsComboboxControlFrame::InitTextStr(nsIPresContext* aPresContext, PRBool aUpdate) { MakeSureSomethingIsSelected(aPresContext); // Update the selected text string mListControlFrame->GetSelectedItem(mTextStr); // Update the display by setting the value attribute mDisplayContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::value, mTextStr, aUpdate); //nsIFrame* buttonFrame = GetDisplayFrame(aPresContext); //nsFormControlHelper::ForceDrawFrame(buttonFrame); } //-------------------------------------------------------------- // Reset the combo box back to it original state. void nsComboboxControlFrame::Reset(nsIPresContext* aPresContext) { if (mPresState) { nsIStatefulFrame* sFrame = nsnull; nsresult res = mListControlFrame->QueryInterface(NS_GET_IID(nsIStatefulFrame), (void**)&sFrame); if (NS_SUCCEEDED(res) && sFrame) { res = sFrame->RestoreState(mPresContext, mPresState); NS_RELEASE(sFrame); } mPresState = do_QueryInterface(nsnull); } // Reset the dropdown list to its original state nsIFormControlFrame* fcFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult result = dropdownFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_OK == result) && (nsnull != fcFrame)) { fcFrame->Reset(aPresContext); } // Update the combobox using the text string returned from the dropdown list InitTextStr(aPresContext, PR_TRUE); } void nsComboboxControlFrame::InitializeControl(nsIPresContext* aPresContext) { Reset(aPresContext); } //-------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::GetType(PRInt32* aType) const { *aType = NS_FORM_SELECT; return NS_OK; } //-------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::GetFormContent(nsIContent*& aContent) const { nsIContent* content; nsresult rv; rv = GetContent(&content); aContent = content; return rv; } //-------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::GetFont(nsIPresContext* aPresContext, const nsFont*& aFont) { return nsFormControlHelper::GetFont(this, aPresContext, mStyleContext, aFont); } //-------------------------------------------------------------- nscoord nsComboboxControlFrame::GetVerticalBorderWidth(float aPixToTwip) const { return 0; } //-------------------------------------------------------------- nscoord nsComboboxControlFrame::GetHorizontalBorderWidth(float aPixToTwip) const { return 0; } //-------------------------------------------------------------- nscoord nsComboboxControlFrame::GetVerticalInsidePadding(nsIPresContext* aPresContext, float aPixToTwip, nscoord aInnerHeight) const { return 0; } //-------------------------------------------------------------- nscoord nsComboboxControlFrame::GetHorizontalInsidePadding(nsIPresContext* aPresContext, float aPixToTwip, nscoord aInnerWidth, nscoord aCharWidth) const { return 0; } void nsComboboxControlFrame::SetFocus(PRBool aOn, PRBool aRepaint) { //XXX: TODO Implement focus for combobox. } void nsComboboxControlFrame::ScrollIntoView(nsIPresContext* aPresContext) { if (aPresContext) { nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); presShell->ScrollFrameIntoView(this, NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE,NS_PRESSHELL_SCROLL_IF_NOT_VISIBLE); } } void nsComboboxControlFrame::ShowPopup(PRBool aShowPopup) { //XXX: This is temporary. It simulates a psuedo dropdown states by using a attribute selector // This will not be need if the event state supports active states for use with the dropdown list // Currently, the event state manager will reset the active state to the content which has focus // which causes the active state to be removed from the event state manager immediately after it // it the select is set to the active state. nsCOMPtr activeAtom ( dont_QueryInterface(NS_NewAtom(kMozDropdownActive))); if (PR_TRUE == aShowPopup) { mContent->SetAttribute(kNameSpaceID_None, activeAtom, "", PR_TRUE); } else { mContent->UnsetAttribute(kNameSpaceID_None, activeAtom, PR_TRUE); } } // Show the dropdown list void nsComboboxControlFrame::ShowList(nsIPresContext* aPresContext, PRBool aShowList) { nsCOMPtr widget; // Get parent view nsIFrame * listFrame; if (NS_OK == mListControlFrame->QueryInterface(kIFrameIID, (void **)&listFrame)) { nsIView * view = nsnull; listFrame->GetView(aPresContext, &view); NS_ASSERTION(view != nsnull, "nsComboboxControlFrame view is null"); if (view) { view->GetWidget(*getter_AddRefs(widget)); } } if (PR_TRUE == aShowList) { ShowPopup(PR_TRUE); mDroppedDown = PR_TRUE; // The listcontrol frame will call back to the nsComboboxControlFrame's ListWasSelected // which will stop the capture. mListControlFrame->AboutToDropDown(); mListControlFrame->CaptureMouseEvents(aPresContext, PR_TRUE); } else { ShowPopup(PR_FALSE); mDroppedDown = PR_FALSE; } nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); presShell->FlushPendingNotifications(); if (widget) widget->CaptureRollupEvents((nsIRollupListener *)this, mDroppedDown, PR_TRUE); } //------------------------------------------------------------- // this is in response to the MouseClick from the containing browse button // XXX: TODO still need to get filters from accept attribute void nsComboboxControlFrame::MouseClicked(nsIPresContext* aPresContext) { //ToggleList(aPresContext); } nsresult nsComboboxControlFrame::ReflowComboChildFrame(nsIFrame* aFrame, nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus, nscoord aAvailableWidth, nscoord aAvailableHeight) { // Constrain the child's width and height to aAvailableWidth and aAvailableHeight nsSize availSize(aAvailableWidth, aAvailableHeight); nsHTMLReflowState kidReflowState(aPresContext, aReflowState, aFrame, availSize); kidReflowState.mComputedWidth = aAvailableWidth; kidReflowState.mComputedHeight = aAvailableHeight; // Reflow child nsRect rect; aFrame->GetRect(rect); nsresult rv = ReflowChild(aFrame, aPresContext, aDesiredSize, kidReflowState, rect.x, rect.y, 0, aStatus); // Set the child's width and height to it's desired size FinishReflowChild(aFrame, aPresContext, aDesiredSize, rect.x, rect.y, 0); return rv; } // Suggest a size for the child frame. // Only frames which implement the nsIFormControlFrame interface and // honor the SetSuggestedSize method will be placed and sized correctly. void nsComboboxControlFrame::SetChildFrameSize(nsIFrame* aFrame, nscoord aWidth, nscoord aHeight) { nsIFormControlFrame* fcFrame = nsnull; nsresult result = aFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if (NS_SUCCEEDED(result) && (nsnull != fcFrame)) { fcFrame->SetSuggestedSize(aWidth, aHeight); } } nsresult nsComboboxControlFrame::GetPrimaryComboFrame(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame** aFrame) { nsresult rv = NS_OK; // Get the primary frame from the presentation shell. nsCOMPtr presShell; rv = aPresContext->GetShell(getter_AddRefs(presShell)); if (NS_SUCCEEDED(rv) && presShell) { presShell->GetPrimaryFrameFor(aContent, aFrame); } return rv; } nsIFrame* nsComboboxControlFrame::GetButtonFrame(nsIPresContext* aPresContext) { if (mButtonFrame == nsnull) { NS_ASSERTION(mButtonContent != nsnull, "nsComboboxControlFrame mButtonContent is null"); GetPrimaryComboFrame(aPresContext, mButtonContent, &mButtonFrame); } return mButtonFrame; } nsIFrame* nsComboboxControlFrame::GetDisplayFrame(nsIPresContext* aPresContext) { if (mDisplayFrame == nsnull) { NS_ASSERTION(mDisplayContent != nsnull, "nsComboboxControlFrame mDisplayContent is null"); GetPrimaryComboFrame(aPresContext, mDisplayContent, &mDisplayFrame); } return mDisplayFrame; } nsIFrame* nsComboboxControlFrame::GetDropdownFrame() { return mDropdownFrame; } nsresult nsComboboxControlFrame::GetScreenHeight(nsIPresContext* aPresContext, nscoord& aHeight) { aHeight = 0; nsIDeviceContext* context; aPresContext->GetDeviceContext( &context ); if ( nsnull != context ) { PRInt32 height; PRInt32 width; context->GetDeviceSurfaceDimensions(width, height); float devUnits; context->GetDevUnitsToAppUnits(devUnits); aHeight = NSToIntRound(float( height) / devUnits ); NS_RELEASE( context ); return NS_OK; } return NS_ERROR_FAILURE; } int counter = 0; nsresult nsComboboxControlFrame::PositionDropdown(nsIPresContext* aPresContext, nscoord aHeight, nsRect aAbsoluteTwipsRect, nsRect aAbsolutePixelRect) { // Position the dropdown list. It is positioned below the display frame if there is enough // room on the screen to display the entire list. Otherwise it is placed above the display // frame. // Note: As first glance, it appears that you could simply get the absolute bounding box for the // dropdown list by first getting it's view, then getting the view's nsIWidget, then asking the nsIWidget // for it's AbsoluteBounds. The problem with this approach, is that the dropdown lists y location can // change based on whether the dropdown is placed below or above the display frame. // The approach, taken here is to get use the absolute position of the display frame and use it's location // to determine if the dropdown will go offscreen. // Use the height calculated for the area frame so it includes both // the display and button heights. nsresult rv = NS_OK; nsIFrame* dropdownFrame = GetDropdownFrame(); nscoord dropdownYOffset = aHeight; // XXX: Enable this code to debug popping up above the display frame, rather than below it nsRect dropdownRect; dropdownFrame->GetRect(dropdownRect); nscoord screenHeightInPixels = 0; if (NS_SUCCEEDED(GetScreenHeight(aPresContext, screenHeightInPixels))) { // Get the height of the dropdown list in pixels. float t2p; aPresContext->GetTwipsToPixels(&t2p); nscoord absoluteDropDownHeight = NSTwipsToIntPixels(dropdownRect.height, t2p); // Check to see if the drop-down list will go offscreen if (NS_SUCCEEDED(rv) && ((aAbsolutePixelRect.y + aAbsolutePixelRect.height + absoluteDropDownHeight) > screenHeightInPixels)) { // move the dropdown list up dropdownYOffset = - (dropdownRect.height); } } dropdownRect.x = 0; dropdownRect.y = dropdownYOffset; nsRect currentRect; dropdownFrame->GetRect(currentRect); //if (currentRect != dropdownRect) { dropdownFrame->SetRect(aPresContext, dropdownRect); #ifdef DEBUG_rodsXXXXXX printf("%d Position Dropdown at: %d %d %d %d\n", counter++, dropdownRect.x, dropdownRect.y, dropdownRect.width, dropdownRect.height); #endif //} return rv; } // Calculate a frame's position in screen coordinates nsresult nsComboboxControlFrame::GetAbsoluteFramePosition(nsIPresContext* aPresContext, nsIFrame *aFrame, nsRect& aAbsoluteTwipsRect, nsRect& aAbsolutePixelRect) { //XXX: This code needs to take the view's offset into account when calculating //the absolute coordinate of the frame. nsresult rv = NS_OK; aFrame->GetRect(aAbsoluteTwipsRect); // zero these out, // because the GetOffsetFromView figures them out aAbsoluteTwipsRect.x = 0; aAbsoluteTwipsRect.y = 0; // Get conversions between twips and pixels float t2p; float p2t; aPresContext->GetTwipsToPixels(&t2p); aPresContext->GetPixelsToTwips(&p2t); // Add in frame's offset from it it's containing view nsIView *containingView = nsnull; nsPoint offset; rv = aFrame->GetOffsetFromView(aPresContext, offset, &containingView); if (NS_SUCCEEDED(rv) && (nsnull != containingView)) { aAbsoluteTwipsRect.x += offset.x; aAbsoluteTwipsRect.y += offset.y; nsPoint viewOffset; containingView->GetPosition(&viewOffset.x, &viewOffset.y); nsIView * parent; containingView->GetParent(parent); while (nsnull != parent) { nsPoint po; parent->GetPosition(&po.x, &po.y); viewOffset.x += po.x; viewOffset.y += po.y; nsIScrollableView * scrollView; if (NS_OK == containingView->QueryInterface(NS_GET_IID(nsIScrollableView), (void **)&scrollView)) { nscoord x; nscoord y; scrollView->GetScrollPosition(x, y); viewOffset.x -= x; viewOffset.y -= y; } nsIWidget * widget; parent->GetWidget(widget); if (nsnull != widget) { // Add in the absolute offset of the widget. nsRect absBounds; nsRect lc; widget->WidgetToScreen(lc, absBounds); // Convert widget coordinates to twips aAbsoluteTwipsRect.x += NSIntPixelsToTwips(absBounds.x, p2t); aAbsoluteTwipsRect.y += NSIntPixelsToTwips(absBounds.y, p2t); NS_RELEASE(widget); break; } parent->GetParent(parent); } aAbsoluteTwipsRect.x += viewOffset.x; aAbsoluteTwipsRect.y += viewOffset.y; } // convert to pixel coordinates if (NS_SUCCEEDED(rv)) { aAbsolutePixelRect.x = NSTwipsToIntPixels(aAbsoluteTwipsRect.x, t2p); aAbsolutePixelRect.y = NSTwipsToIntPixels(aAbsoluteTwipsRect.y, t2p); aAbsolutePixelRect.width = NSTwipsToIntPixels(aAbsoluteTwipsRect.width, t2p); aAbsolutePixelRect.height = NSTwipsToIntPixels(aAbsoluteTwipsRect.height, t2p); } return rv; } //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- // Old/Current Way of Doing Reflow //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- #define DO_OLD_REFLOW #ifdef DO_OLD_REFLOW #ifdef DEBUG_rodsXXX static int myCounter = 0; #endif NS_IMETHODIMP nsComboboxControlFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { #ifdef DEBUG_rodsXXX printf("****** nsComboboxControlFrame::Reflow %d Reason: ", myCounter++); switch (aReflowState.reason) { case eReflowReason_Initial: printf("eReflowReason_Initial\n");break; case eReflowReason_Incremental: printf("eReflowReason_Incremental\n");break; case eReflowReason_Resize: printf("eReflowReason_Resize\n"); break; case eReflowReason_StyleChange:printf("eReflowReason_StyleChange\n");break; } #endif #if 0 nsresult skiprv = nsFormControlFrame::SkipResizeReflow(mCacheSize, mCachedMaxElementSize, aPresContext, aDesiredSize, aReflowState, aStatus); if (NS_SUCCEEDED(skiprv)) { return skiprv; } #endif nsresult rv = NS_OK; nsIFrame* buttonFrame = GetButtonFrame(aPresContext); nsIFrame* displayFrame = GetDisplayFrame(aPresContext); nsIFrame* dropdownFrame = GetDropdownFrame(); // Don't try to do any special sizing and positioning unless all of the frames // have been created. if ((nsnull == displayFrame) || (nsnull == buttonFrame) || (nsnull == dropdownFrame)) { // Since combobox frames are missing just do a normal area frame reflow return nsAreaFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); } // size of each part of the combo box nsRect displayRect; nsRect buttonRect; nsRect dropdownRect; if (!mFormFrame && (eReflowReason_Initial == aReflowState.reason)) { nsFormControlFrame::RegUnRegAccessKey(aPresContext, NS_STATIC_CAST(nsIFrame*, this), PR_TRUE); nsFormFrame::AddFormControlFrame(aPresContext, *NS_STATIC_CAST(nsIFrame*,this)); } const nsStyleSpacing* spacing; GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)spacing); nsMargin borderPadding; borderPadding.SizeTo(0, 0, 0, 0); spacing->CalcBorderPaddingFor(this, borderPadding); // Get the current sizes of the combo box child frames displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); dropdownFrame->GetRect(dropdownRect); nsHTMLReflowState firstPassState(aReflowState); // Only reflow the display and button if they are the target of // the incremental reflow, unless they change size. If they do // then everything needs to be reflowed. if (eReflowReason_Incremental == firstPassState.reason) { nsIFrame* targetFrame; firstPassState.reflowCommand->GetTarget(targetFrame); if ((targetFrame == buttonFrame) || (targetFrame == displayFrame)) { nsRect oldDisplayRect = displayRect; nsRect oldButtonRect = buttonRect; rv = nsAreaFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); // nsAreaFrame::Reflow adds in the border and padding so we need to remove it if (NS_UNCONSTRAINEDSIZE != firstPassState.mComputedWidth) { aDesiredSize.width -= borderPadding.left + borderPadding.right; } displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); if ((oldDisplayRect == displayRect) && (oldButtonRect == buttonRect)) { aStatus = NS_FRAME_COMPLETE; return rv; } } nsIReflowCommand::ReflowType type; aReflowState.reflowCommand->GetType(type); firstPassState.reason = eReflowReason_StyleChange; firstPassState.reflowCommand = nsnull; } // the default size of the of scrollbar // that will be the default width of the dropdown button // the height will be the height of the text nscoord scrollbarWidth = -1; nsCOMPtr dx; aPresContext->GetDeviceContext(getter_AddRefs(dx)); if (dx) { // Get the width in Device pixels (in this case screen) SystemAttrStruct info; dx->GetSystemAttribute(eSystemAttr_Size_ScrollbarWidth, &info); // Get the pixels to twips conversion for the current device (screen or printer) float p2t; aPresContext->GetPixelsToTwips(&p2t); // Get the scale factor for mapping from one device (screen) // to another device (screen or printer) // Typically when it is a screen the scale 1.0 // when it is a printer is could be anything float scale; dx->GetCanonicalPixelScale(scale); scrollbarWidth = NSIntPixelsToTwips(info.mSize, p2t*scale); } //Set the desired size for the button and display frame if (NS_UNCONSTRAINEDSIZE == firstPassState.mComputedWidth) { // A width has not been specified for the select so size the display area to // match the width of the longest item in the drop-down list. The dropdown // list has already been reflowed and sized to shrink around its contents above. // Reflow the dropdown shrink-wrapped. PRBool saveMES = aDesiredSize.maxElementSize != nsnull; nsSize * maxElementSize = nsnull; if (saveMES) { maxElementSize = new nsSize(); //maxElementSize = new nsSize(*aDesiredSize.maxElementSize); } nsHTMLReflowMetrics dropdownDesiredSize(maxElementSize); ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); nsSize size; PRInt32 length = 0; mListControlFrame->GetNumberOfOptions(&length); dropdownFrame->GetRect(dropdownRect); const nsStyleSpacing* dropSpacing; dropdownFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)dropSpacing); nsMargin dropBorderPadding; dropBorderPadding.SizeTo(0, 0, 0, 0); dropSpacing->CalcBorderPaddingFor(dropdownFrame, dropBorderPadding); dropdownRect.width -= (dropBorderPadding.left + dropBorderPadding.right); // Get maximum size and height of a option in the dropdown mListControlFrame->GetMaximumSize(size); if (scrollbarWidth > 0) { size.width = scrollbarWidth; } const nsStyleSpacing* dspSpacing; displayFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)dspSpacing); nsMargin dspBorderPadding; dspBorderPadding.SizeTo(0, 0, 0, 0); dspSpacing->CalcBorderPaddingFor(displayFrame, dspBorderPadding); // Set width of display to match width of the drop down SetChildFrameSize(displayFrame, dropdownRect.width-size.width+dspBorderPadding.left+dspBorderPadding.right, size.height+dspBorderPadding.top+dspBorderPadding.bottom); // Size the button SetChildFrameSize(buttonFrame, size.width, size.height); // Reflow display + button nsAreaFrame::Reflow(aPresContext, aDesiredSize, firstPassState, aStatus); displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); buttonRect.y = displayRect.y; buttonRect.height = displayRect.height; buttonFrame->SetRect(aPresContext, buttonRect); // Reflow the dropdown list to match the width of the display + button ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, aDesiredSize.width, NS_UNCONSTRAINEDSIZE); dropdownFrame->GetRect(dropdownRect); if (maxElementSize) { delete maxElementSize; } } else { // A width has been specified for the select. // Make the display frame's width + button frame width = the width specified. nsHTMLReflowMetrics dropdownDesiredSize(aDesiredSize); ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); // Get unconstrained size of the dropdown list. nsSize size; mListControlFrame->GetMaximumSize(size); if (scrollbarWidth > 0) { size.width = scrollbarWidth; } // Size the button to be the same as the scrollbar width SetChildFrameSize(buttonFrame, size.width, size.height); // Compute display width nscoord displayWidth = firstPassState.mComputedWidth - size.width; // nsAreaFrame::Reflow adds in the border and padding so we need to remove it //displayWidth -= borderPadding.left + borderPadding.right; // Set the displayFrame to match the displayWidth computed above SetChildFrameSize(displayFrame, displayWidth, size.height); // Reflow again with the width of the display frame set. nsAreaFrame::Reflow(aPresContext, aDesiredSize, firstPassState, aStatus); // nsAreaFrame::Reflow adds in the border and padding so we need to remove it // XXX rods - this hould not be subtracted in //aDesiredSize.width += borderPadding.left + borderPadding.right; // Reflow the dropdown list to match the width of the display + button ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, aDesiredSize.width, NS_UNCONSTRAINEDSIZE); } // Set the max element size to be the same as the desired element size. if (nsnull != aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = aDesiredSize.width; aDesiredSize.maxElementSize->height = aDesiredSize.height; } aStatus = NS_FRAME_COMPLETE; #if 0 COMPARE_QUIRK_SIZE("nsComboboxControlFrame", 127, 22) #endif nsFormControlFrame::SetupCachedSizes(mCacheSize, mCachedMaxElementSize, aDesiredSize); return rv; } //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- // New Reflow //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- #else // DO_OLD_REFLOW #ifdef DEBUG_rods nsComboboxControlFrame * pCB = nsnull; static int myCounter = 0; #endif NS_IMETHODIMP nsComboboxControlFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { #ifdef DEBUG_rods //if (pCB) { printf("%p ****** nsComboboxControlFrame::Reflow %d Reason: ", this, myCounter++); switch (aReflowState.reason) { case eReflowReason_Initial: printf("eReflowReason_Initial\n");break; case eReflowReason_Incremental: printf("eReflowReason_Incremental\n");break; case eReflowReason_Resize: printf("eReflowReason_Resize\n"); break; case eReflowReason_StyleChange:printf("eReflowReason_StyleChange\n");break; } //} #endif #if 0 nsresult skiprv = nsFormControlFrame::SkipResizeReflow(mCacheSize, mCachedMaxElementSize, aPresContext, aDesiredSize, aReflowState, aStatus); if (NS_SUCCEEDED(skiprv)) { return skiprv; } #endif nsresult rv = NS_OK; nsIFrame* buttonFrame = GetButtonFrame(aPresContext); nsIFrame* displayFrame = GetDisplayFrame(aPresContext); nsIFrame* dropdownFrame = GetDropdownFrame(); // Don't try to do any special sizing and positioning unless all of the frames // have been created. if ((nsnull == displayFrame) || (nsnull == buttonFrame) || (nsnull == dropdownFrame)) { // Since combobox frames are missing just do a normal area frame reflow return nsAreaFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); } // size of each part of the combo box nsRect displayRect; nsRect buttonRect; nsRect dropdownRect; if (!mFormFrame && (eReflowReason_Initial == aReflowState.reason)) { nsFormFrame::AddFormControlFrame(aPresContext, *NS_STATIC_CAST(nsIFrame*,this)); } const nsStyleSpacing* spacing; GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)spacing); nsMargin borderPadding; borderPadding.SizeTo(0, 0, 0, 0); spacing->CalcBorderPaddingFor(this, borderPadding); // Get the current sizes of the combo box child frames displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); dropdownFrame->GetRect(dropdownRect); nsHTMLReflowState firstPassState(aReflowState); // Only reflow the display and button if they are the target of // the incremental reflow, unless they change size. If they do // then everything needs to be reflowed. if (eReflowReason_Incremental == firstPassState.reason) { nsIFrame* targetFrame; firstPassState.reflowCommand->GetTarget(targetFrame); if (targetFrame == this) { // not sure what I should be doing here for an incremental reflow // so I will let it just reflow as normal // but it does seems to go into this code. } else { if (targetFrame != dropdownFrame) { #ifdef DEBUG_rods printf("+++++++++++++++++++++++\n"); #endif rv = nsAreaFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus); displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); buttonRect.y = displayRect.y; buttonRect.height = displayRect.height; buttonFrame->SetRect(aPresContext, buttonRect); aStatus = NS_FRAME_COMPLETE; #ifdef DEBUG_rods printf("--> W: %d H: %d\n", aDesiredSize.width, aDesiredSize.height); #endif return rv; } else { #if 0 nsRect dropdownRect; dropdownFrame->GetRect(dropdownRect); nsHTMLReflowMetrics dropdownDesiredSize(aDesiredSize); ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, dropdownRect.width, dropdownRect.height); nsCOMPtr context; targetFrame->GetStyleContext(getter_AddRefs(context)); const nsStyleDisplay* disp = (const nsStyleDisplay*)context->GetStyleData(eStyleStruct_Display); printf("IsVisible %s\n", disp->mVisible == NS_STYLE_VISIBILITY_VISIBLE?"yes":"No"); //if (disp->mVisible == NS_STYLE_VISIBILITY_VISIBLE) { nsRect rect; GetRect(rect); aDesiredSize.width = rect.width;// + borderPadding.left + borderPadding.right; aDesiredSize.height = rect.height;// + borderPadding.top + borderPadding.bottom; aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; dropdownRect.width = rect.width; dropdownFrame->SetRect(aPresContext, dropdownRect); printf("Skipping out.........................."); aStatus = NS_FRAME_COMPLETE; return NS_OK; //} #endif } } } // the default size of the of scrollbar // that will be the default width of the dropdown button // the height will be the height of the text nscoord scrollbarWidth = -1; nsCOMPtr dx; aPresContext->GetDeviceContext(getter_AddRefs(dx)); if (dx) { // Get the width in Device pixels (in this case screen) SystemAttrStruct info; dx->GetSystemAttribute(eSystemAttr_Size_ScrollbarWidth, &info); // Get the pixels to twips conversion for the current device (screen or printer) float p2t; aPresContext->GetPixelsToTwips(&p2t); // Get the scale factor for mapping from one device (screen) // to another device (screen or printer) // Typically when it is a screen the scale 1.0 // when it is a printer is could be anything float scale; dx->GetCanonicalPixelScale(scale); scrollbarWidth = NSIntPixelsToTwips(info.mSize, p2t*scale); } #ifdef DEBUG_rods printf("UNC %d AV %d \n", firstPassState.mComputedWidth,firstPassState.availableWidth); #endif //Set the desired size for the button and display frame if (NS_UNCONSTRAINEDSIZE == firstPassState.mComputedWidth) { #ifdef DEBUG_rods printf("=====================================\n"); #endif // A width has not been specified for the select so size the display area to // match the width of the longest item in the drop-down list. The dropdown // list has already been reflowed and sized to shrink around its contents above. // Reflow the dropdown shrink-wrapped. PRBool saveMES = aDesiredSize.maxElementSize != nsnull; nsSize * maxElementSize = nsnull; if (saveMES) { maxElementSize = new nsSize(); //maxElementSize = new nsSize(*aDesiredSize.maxElementSize); } nsHTMLReflowMetrics dropdownDesiredSize(maxElementSize); ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); nsSize size; PRInt32 length = 0; mListControlFrame->GetNumberOfOptions(&length); dropdownFrame->GetRect(dropdownRect); const nsStyleSpacing* dropSpacing; dropdownFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)dropSpacing); nsMargin dropBorderPadding; dropBorderPadding.SizeTo(0, 0, 0, 0); dropSpacing->CalcBorderPaddingFor(dropdownFrame, dropBorderPadding); dropdownRect.width -= (dropBorderPadding.left + dropBorderPadding.right); // Get maximum size and height of a option in the dropdown mListControlFrame->GetMaximumSize(size); if (scrollbarWidth > 0) { size.width = scrollbarWidth; } const nsStyleSpacing* dspSpacing; displayFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct *&)dspSpacing); nsMargin dspBorderPadding; dspBorderPadding.SizeTo(0, 0, 0, 0); dspSpacing->CalcBorderPaddingFor(displayFrame, dspBorderPadding); #ifdef DEBUG_rods printf("W: %d\n", dropdownRect.width-size.width+dspBorderPadding.left+dspBorderPadding.right); #endif // Set width of display to match width of the drop down SetChildFrameSize(displayFrame, dropdownRect.width-size.width+dspBorderPadding.left+dspBorderPadding.right, size.height+dspBorderPadding.top+dspBorderPadding.bottom); // Size the button SetChildFrameSize(buttonFrame, size.width, size.height); // Reflow display + button nsAreaFrame::Reflow(aPresContext, aDesiredSize, firstPassState, aStatus); displayFrame->GetRect(displayRect); buttonFrame->GetRect(buttonRect); buttonRect.y = displayRect.y; buttonRect.height = displayRect.height; buttonFrame->SetRect(aPresContext, buttonRect); // Reflow the dropdown list to match the width of the display + button ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, aDesiredSize.width, NS_UNCONSTRAINEDSIZE); dropdownFrame->GetRect(dropdownRect); if (maxElementSize) { delete maxElementSize; } } else { #ifdef DEBUG_rods printf("*************************************\n"); #endif // for debug ------- PRInt32 length = 0; mListControlFrame->GetNumberOfOptions(&length); //------ // A width has been specified for the select. // Make the display frame's width + button frame width = the width specified. nsHTMLReflowMetrics dropdownDesiredSize(aDesiredSize); ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); // Get unconstrained size of the dropdown list. nsSize size; mListControlFrame->GetMaximumSize(size); if (scrollbarWidth > 0) { size.width = scrollbarWidth; } // Size the button to be the same as the scrollbar width SetChildFrameSize(buttonFrame, size.width, size.height); if (firstPassState.mComputedWidth > 0) { // Compute display width nscoord displayWidth = firstPassState.mComputedWidth - size.width; // nsAreaFrame::Reflow adds in the border and padding so we need to remove it //displayWidth -= borderPadding.left + borderPadding.right; #ifdef DEBUG_rods printf("WW: %d\n", displayWidth); #endif // Set the displayFrame to match the displayWidth computed above if (displayWidth >= 0) { SetChildFrameSize(displayFrame, displayWidth, size.height); } // Reflow again with the width of the display frame set. nsAreaFrame::Reflow(aPresContext, aDesiredSize, firstPassState, aStatus); displayFrame->GetRect(displayRect); displayRect.width = displayWidth; buttonFrame->GetRect(buttonRect); buttonRect.x = displayWidth+borderPadding.left; buttonRect.y = displayRect.y; buttonRect.height = displayRect.height; buttonFrame->SetRect(aPresContext, buttonRect); displayFrame->SetRect(aPresContext, displayRect); #ifdef DEBUG_rods printf("DW: %d\n", aDesiredSize.width); #endif // nsAreaFrame::Reflow adds in the border and padding so we need to remove it // XXX rods - this hould not be subtracted in //aDesiredSize.width += borderPadding.left + borderPadding.right; // Reflow the dropdown list to match the width of the display + button ReflowComboChildFrame(dropdownFrame, aPresContext, dropdownDesiredSize, firstPassState, aStatus, aDesiredSize.width, NS_UNCONSTRAINEDSIZE); } aDesiredSize.width = firstPassState.mComputedWidth + borderPadding.left + borderPadding.right; aDesiredSize.height = firstPassState.mComputedHeight + borderPadding.top + borderPadding.bottom; aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; } // Set the max element size to be the same as the desired element size. if (nsnull != aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = aDesiredSize.width; aDesiredSize.maxElementSize->height = aDesiredSize.height; } aStatus = NS_FRAME_COMPLETE; #if 0 COMPARE_QUIRK_SIZE("nsComboboxControlFrame", 127, 22) #endif nsFormControlFrame::SetupCachedSizes(mCacheSize, mCachedMaxElementSize, aDesiredSize); #ifdef DEBUG_rods printf("--> W: %d H: %d\n", aDesiredSize.width, aDesiredSize.height); #endif return rv; } #endif // DO_OLD_REFLOW //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- // Done with New Reflow //------------------------------------------------------------------------------- //------------------------------------------------------------------------------- //-------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::GetName(nsString* aResult) { nsresult result = NS_FORM_NOTOK; if (mContent) { nsIHTMLContent* formControl = nsnull; result = mContent->QueryInterface(kIHTMLContentIID, (void**)&formControl); if ((NS_OK == result) && formControl) { nsHTMLValue value; result = formControl->GetHTMLAttribute(nsHTMLAtoms::name, value); if (NS_CONTENT_ATTR_HAS_VALUE == result) { if (eHTMLUnit_String == value.GetUnit()) { value.GetStringValue(*aResult); } } NS_RELEASE(formControl); } } return result; } PRInt32 nsComboboxControlFrame::GetMaxNumValues() { return 1; } /*XXX-REMOVE PRBool nsComboboxControlFrame::GetNamesValues(PRInt32 aMaxNumValues, PRInt32& aNumValues, nsString* aValues, nsString* aNames) { nsAutoString name; nsresult result = GetName(&name); if ((aMaxNumValues <= 0) || (NS_CONTENT_ATTR_HAS_VALUE != result)) { return PR_FALSE; } // use our name and the text widgets value aNames[0] = name; aValues[0] = mTextStr; aNumValues = 1; nsresult status = PR_TRUE; return status; } */ PRBool nsComboboxControlFrame::GetNamesValues(PRInt32 aMaxNumValues, PRInt32& aNumValues, nsString* aValues, nsString* aNames) { nsIFormControlFrame* fcFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult result = dropdownFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_SUCCEEDED(result)) && (nsnull != fcFrame)) { return fcFrame->GetNamesValues(aMaxNumValues, aNumValues, aValues, aNames); } return PR_FALSE; } NS_IMETHODIMP nsComboboxControlFrame::GetFrameForPoint(nsIPresContext* aPresContext, const nsPoint& aPoint, nsIFrame** aFrame) { if (nsFormFrame::GetDisabled(this)) { *aFrame = this; return NS_OK; } else { return nsHTMLContainerFrame::GetFrameForPoint(aPresContext, aPoint, aFrame); } return NS_OK; } //-------------------------------------------------------------- #ifdef NS_DEBUG NS_IMETHODIMP nsComboboxControlFrame::GetFrameName(nsString& aResult) const { return MakeFrameName("ComboboxControl", aResult); } #endif //---------------------------------------------------------------------- // nsIComboboxControlFrame //---------------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::ShowDropDown(PRBool aDoDropDown) { if (nsFormFrame::GetDisabled(this)) { return NS_OK; } if (!mDroppedDown && aDoDropDown) { // XXX Temporary for Bug 19416 nsIFrame* dropdownFrame = GetDropdownFrame(); nsIView * lstView; dropdownFrame->GetView(mPresContext, &lstView); if (lstView) { lstView->IgnoreSetPosition(PR_FALSE); } if (mListControlFrame) { mListControlFrame->SyncViewWithFrame(mPresContext); } // XXX Temporary for Bug 19416 if (lstView) { lstView->IgnoreSetPosition(PR_TRUE); } ToggleList(mPresContext); return NS_OK; } else if (mDroppedDown && !aDoDropDown) { ToggleList(mPresContext); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsComboboxControlFrame::SetDropDown(nsIFrame* aDropDownFrame) { mDropdownFrame = aDropDownFrame; if (NS_OK != mDropdownFrame->QueryInterface(kIListControlFrameIID, (void**)&mListControlFrame)) { return NS_ERROR_FAILURE; } return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::GetDropDown(nsIFrame** aDropDownFrame) { if (nsnull == aDropDownFrame) { return NS_ERROR_FAILURE; } *aDropDownFrame = mDropdownFrame; return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::ListWasSelected(nsIPresContext* aPresContext) { if (aPresContext == nsnull) { aPresContext = mPresContext; } ShowList(aPresContext, PR_FALSE); mListControlFrame->CaptureMouseEvents(aPresContext, PR_FALSE); PRInt32 indx; mListControlFrame->GetSelectedIndex(&indx); UpdateSelection(PR_TRUE, PR_FALSE, indx); return NS_OK; } // Toggle dropdown list. NS_IMETHODIMP nsComboboxControlFrame::ToggleList(nsIPresContext* aPresContext) { ShowList(aPresContext, (PR_FALSE == mDroppedDown)); return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::UpdateSelection(PRBool aDoDispatchEvent, PRBool aForceUpdate, PRInt32 aNewIndex) { if (mListControlFrame) { // Check to see if the selection changed if (mSelectedIndex != aNewIndex || aForceUpdate) { mListControlFrame->GetSelectedItem(mTextStr); // Update text box mListControlFrame->UpdateSelection(aDoDispatchEvent, aForceUpdate, mContent); } mSelectedIndex = aNewIndex; } return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::AbsolutelyPositionDropDown() { nsRect absoluteTwips; nsRect absolutePixels; // XXX Not used nsIFrame* displayFrame = GetDisplayFrame(mPresContext); nsRect rect; this->GetRect(rect); GetAbsoluteFramePosition(mPresContext, this, absoluteTwips, absolutePixels); PositionDropdown(mPresContext, rect.height, absoluteTwips, absolutePixels); return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::GetAbsoluteRect(nsRect* aRect) { nsRect absoluteTwips; nsRect rect; this->GetRect(rect); GetAbsoluteFramePosition(mPresContext, this, absoluteTwips, *aRect); return NS_OK; } /////////////////////////////////////////////////////////////// NS_IMETHODIMP nsComboboxControlFrame::SelectionChanged() { // Send reflow command because the new text maybe larger nsresult rv = NS_OK; if (mDisplayContent) { nsCOMPtr htmlContent(do_QueryInterface(mDisplayContent, &rv)); if (NS_SUCCEEDED(rv) && htmlContent) { nsHTMLValue value; nsresult result = htmlContent->GetHTMLAttribute(nsHTMLAtoms::value, value); PRBool shouldSetValue = PR_FALSE; if (NS_FAILED(result) || value.GetUnit() == eHTMLUnit_Empty) { shouldSetValue = PR_TRUE; } else if (NS_SUCCEEDED(result) && value.GetUnit() == eHTMLUnit_String) { nsAutoString str; value.GetStringValue(str); shouldSetValue = str != mTextStr; } if (shouldSetValue) { rv = htmlContent->SetHTMLAttribute(nsHTMLAtoms::value, mTextStr, PR_TRUE); #if 1 if (NS_SUCCEEDED(rv)) { nsIFrame* displayFrame = GetDisplayFrame(mPresContext); nsIReflowCommand* cmd; rv = NS_NewHTMLReflowCommand(&cmd, displayFrame, nsIReflowCommand::StyleChanged); if (NS_SUCCEEDED(rv)) { nsCOMPtr shell; rv = mPresContext->GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) { if (NS_SUCCEEDED(shell->EnterReflowLock())) { shell->AppendReflowCommand(cmd); shell->ExitReflowLock(PR_TRUE); } } NS_RELEASE(cmd); } } #else if (mParent) { nsCOMPtr shell; rv = mPresContext->GetShell(getter_AddRefs(shell)); mState |= NS_FRAME_IS_DIRTY; mParent->ReflowDirtyChild(shell, (nsIFrame*) this); } #endif } } } return rv; } //---------------------------------------------------------------------- // nsISelectControlFrame //---------------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::DoneAddingContent(PRBool aIsDone) { nsISelectControlFrame* listFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(NS_GET_IID(nsISelectControlFrame), (void**)&listFrame); if (NS_SUCCEEDED(rv) && listFrame) { rv = listFrame->DoneAddingContent(aIsDone); NS_RELEASE(listFrame); } return rv; } NS_IMETHODIMP nsComboboxControlFrame::AddOption(nsIPresContext* aPresContext, PRInt32 aIndex) { #ifdef DEBUG_rodsXXX if (!pCB) pCB = this; #endif nsISelectControlFrame* listFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(NS_GET_IID(nsISelectControlFrame), (void**)&listFrame); if (NS_SUCCEEDED(rv) && listFrame) { rv = listFrame->AddOption(aPresContext, aIndex); PRInt32 index; mListControlFrame->GetSelectedIndex(&index); UpdateSelection(PR_FALSE, PR_TRUE, index); NS_RELEASE(listFrame); } // If we added the first option, we might need to select it. // We should call MakeSureSomethingIsSelected here, but since it // it changes selection, which currently causes a reframe, and thus // deletes the frame out from under the caller, causing a crash. (Bug 17995) // XXX MakeSureSomethingIsSelected(aPresContext); return rv; } NS_IMETHODIMP nsComboboxControlFrame::RemoveOption(nsIPresContext* aPresContext, PRInt32 aIndex) { nsISelectControlFrame* listFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(NS_GET_IID(nsISelectControlFrame), (void**)&listFrame); if (NS_SUCCEEDED(rv) && listFrame) { rv = listFrame->RemoveOption(aPresContext, aIndex); NS_RELEASE(listFrame); } // If we removed the selected option, nothing is selected any more. // Restore selection to option 0 if there are options left. MakeSureSomethingIsSelected(aPresContext); return rv; } NS_IMETHODIMP nsComboboxControlFrame::SetOptionSelected(PRInt32 aIndex, PRBool aValue) { nsISelectControlFrame* listFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(NS_GET_IID(nsISelectControlFrame), (void**)&listFrame); if (NS_SUCCEEDED(rv) && listFrame) { rv = listFrame->SetOptionSelected(aIndex, aValue); NS_RELEASE(listFrame); } return rv; } NS_IMETHODIMP nsComboboxControlFrame::GetOptionSelected(PRInt32 aIndex, PRBool* aValue) { nsISelectControlFrame* listFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult rv = dropdownFrame->QueryInterface(NS_GET_IID(nsISelectControlFrame), (void**)&listFrame); if (NS_SUCCEEDED(rv) && listFrame) { rv = listFrame->GetOptionSelected(aIndex, aValue); NS_RELEASE(listFrame); } return rv; } NS_IMETHODIMP nsComboboxControlFrame::HandleEvent(nsIPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus) { NS_ENSURE_ARG_POINTER(aEventStatus); if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { return NS_OK; } if (nsFormFrame::GetDisabled(this)) { return NS_OK; } return NS_OK; } nsresult nsComboboxControlFrame::RequiresWidget(PRBool& aRequiresWidget) { aRequiresWidget = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::SetProperty(nsIPresContext* aPresContext, nsIAtom* aName, const nsString& aValue) { nsIFormControlFrame* fcFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult result = dropdownFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_SUCCEEDED(result)) && (nsnull != fcFrame)) { return fcFrame->SetProperty(aPresContext, aName, aValue); } return result; } NS_IMETHODIMP nsComboboxControlFrame::GetProperty(nsIAtom* aName, nsString& aValue) { nsIFormControlFrame* fcFrame = nsnull; nsIFrame* dropdownFrame = GetDropdownFrame(); nsresult result = dropdownFrame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_SUCCEEDED(result)) && (nsnull != fcFrame)) { return fcFrame->GetProperty(aName, aValue); } return result; } NS_IMETHODIMP nsComboboxControlFrame::CreateAnonymousContent(nsIPresContext* aPresContext, nsISupportsArray& aChildList) { // The frames used to display the combo box and the button used to popup the dropdown list // are created through anonymous content. The dropdown list is not created through anonymous // content because it's frame is initialized specifically for the drop-down case and it is placed // a special list referenced through NS_COMBO_FRAME_POPUP_LIST_INDEX to keep separate from the // layout of the display and button. // // Note: The value attribute of the display content is set when an item is selected in the dropdown list. // If the content specified below does not honor the value attribute than nothing will be displayed. // In addition, if the frame created by content below for does not implement the nsIFormControlFrame // interface and honor the SetSuggestedSize method the placement and size of the display area will not // match what is normally desired for a combobox. // For now the content that is created corresponds to two input buttons. It would be better to create the // tag as something other than input, but then there isn't any way to create a button frame since it // isn't possible to set the display type in CSS2 to create a button frame. // create content used for display //nsIAtom* tag = NS_NewAtom("mozcombodisplay"); nsIAtom* tag = NS_NewAtom("input"); NS_NewHTMLInputElement(&mDisplayContent, tag); //NS_ADDREF(mDisplayContent); mDisplayContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::type, nsAutoString("button"), PR_FALSE); //XXX: Do not use nsHTMLAtoms::id use nsHTMLAtoms::kClass instead. There will end up being multiple //ids set to the same value which is illegal. mDisplayContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::id, nsAutoString("-moz-display"), PR_FALSE); // This is mDisplayContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::value, nsAutoString("X"), PR_TRUE); aChildList.AppendElement(mDisplayContent); // create button which drops the list down NS_NewHTMLInputElement(&mButtonContent, tag); //NS_ADDREF(mButtonContent); mButtonContent->SetAttribute(kNameSpaceID_None, nsHTMLAtoms::type, nsAutoString("button"), PR_FALSE); aChildList.AppendElement(mButtonContent); NS_RELEASE(tag); return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::SetSuggestedSize(nscoord aWidth, nscoord aHeight) { return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::Destroy(nsIPresContext* aPresContext) { // Cleanup frames in popup child list mPopupFrames.DestroyFrames(aPresContext); return nsAreaFrame::Destroy(aPresContext); } NS_IMETHODIMP nsComboboxControlFrame::FirstChild(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame** aFirstChild) const { if (nsLayoutAtoms::popupList == aListName) { *aFirstChild = mPopupFrames.FirstChild(); } else { nsAreaFrame::FirstChild(aPresContext, aListName, aFirstChild); } return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList) { nsresult rv = NS_OK; if (nsLayoutAtoms::popupList == aListName) { mPopupFrames.SetFrames(aChildList); } else { rv = nsAreaFrame::SetInitialChildList(aPresContext, aListName, aChildList); InitTextStr(aPresContext, PR_FALSE); } return rv; } NS_IMETHODIMP nsComboboxControlFrame::GetAdditionalChildListName(PRInt32 aIndex, nsIAtom** aListName) const { // Maintain a seperate child list for the dropdown list (i.e. popup listbox) // This is necessary because we don't want the listbox to be included in the layout // of the combox's children because it would take up space, when it is suppose to // be floating above the display. NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer"); if (aIndex <= NS_AREA_FRAME_ABSOLUTE_LIST_INDEX) { return nsAreaFrame::GetAdditionalChildListName(aIndex, aListName); } *aListName = nsnull; if (NS_COMBO_FRAME_POPUP_LIST_INDEX == aIndex) { *aListName = nsLayoutAtoms::popupList; NS_ADDREF(*aListName); } return NS_OK; } PRIntn nsComboboxControlFrame::GetSkipSides() const { // Don't skip any sides during border rendering return 0; } //---------------------------------------------------------------------- //nsIRollupListener //---------------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::Rollup() { if (mDroppedDown) { mListControlFrame->AboutToRollup(); ShowDropDown(PR_FALSE); mListControlFrame->CaptureMouseEvents(mPresContext, PR_FALSE); } return NS_OK; } //---------------------------------------------------------------------- // nsIStatefulFrame // XXX Do we need to implement this here? It is already implemented in // the ListControlFrame, our child... //---------------------------------------------------------------------- NS_IMETHODIMP nsComboboxControlFrame::GetStateType(nsIPresContext* aPresContext, nsIStatefulFrame::StateType* aStateType) { *aStateType = nsIStatefulFrame::eSelectType; return NS_OK; } NS_IMETHODIMP nsComboboxControlFrame::SaveState(nsIPresContext* aPresContext, nsIPresState** aState) { if (!mListControlFrame) return NS_ERROR_UNEXPECTED; nsIStatefulFrame* sFrame = nsnull; nsresult res = mListControlFrame->QueryInterface(NS_GET_IID(nsIStatefulFrame), (void**)&sFrame); if (NS_SUCCEEDED(res) && sFrame) { res = sFrame->SaveState(aPresContext, aState); NS_RELEASE(sFrame); } return res; } NS_IMETHODIMP nsComboboxControlFrame::RestoreState(nsIPresContext* aPresContext, nsIPresState* aState) { if (!mListControlFrame) { mPresState = aState; return NS_OK; } nsIStatefulFrame* sFrame = nsnull; nsresult res = mListControlFrame->QueryInterface(NS_GET_IID(nsIStatefulFrame), (void**)&sFrame); if (NS_SUCCEEDED(res) && sFrame) { res = sFrame->RestoreState(aPresContext, aState); NS_RELEASE(sFrame); } return res; }