/* -*- 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): */ #include "nsCOMPtr.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMXULDocument.h" #include "nsHTMLContainerFrame.h" #include "nsIFormControlFrame.h" #include "nsHTMLParts.h" #include "nsIForm.h" #include "nsIFormControl.h" #include "nsFormFrame.h" #include "nsIRenderingContext.h" #include "nsIPresContext.h" #include "nsIPresShell.h" #include "nsIStyleContext.h" #include "nsLeafFrame.h" #include "nsCSSRendering.h" #include "nsHTMLIIDs.h" #include "nsISupports.h" #include "nsHTMLAtoms.h" #include "nsIImage.h" #include "nsStyleUtil.h" #include "nsDOMEvent.h" #include "nsIDOMHTMLCollection.h" #include "nsStyleConsts.h" #include "nsIHTMLAttributes.h" #include "nsGenericHTMLElement.h" #include "nsIWidget.h" #include "nsIComponentManager.h" #include "nsIView.h" #include "nsIViewManager.h" #include "nsViewsCID.h" #include "nsColor.h" #include "nsIDocument.h" #include "nsIHTMLDocument.h" //Enumeration of possible mouse states used to detect mouse clicks enum nsMouseState { eMouseNone, eMouseEnter, eMouseExit, eMouseDown, eMouseUp }; static NS_DEFINE_IID(kIFormControlIID, NS_IFORMCONTROL_IID); static NS_DEFINE_IID(kIFormControlFrameIID, NS_IFORMCONTROLFRAME_IID); static NS_DEFINE_IID(kViewCID, NS_VIEW_CID); static NS_DEFINE_IID(kIViewIID, NS_IVIEW_IID); static NS_DEFINE_IID(kIHTMLDocumentIID, NS_IHTMLDOCUMENT_IID); static NS_DEFINE_IID(kIFormIID, NS_IFORM_IID); static NS_DEFINE_IID(kIFrameIID, NS_IFRAME_IID); class nsLabelFrame : public nsHTMLContainerFrame { public: nsLabelFrame(); NS_IMETHOD Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow); NS_IMETHOD Paint(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer); NS_IMETHOD SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList); NS_IMETHOD Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); NS_IMETHOD HandleEvent(nsIPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus); NS_IMETHOD GetFrameForPoint(nsIPresContext* aPresContext, const nsPoint& aPoint, nsIFrame** aFrame); NS_IMETHOD GetFor(nsString& aFor); #ifdef DEBUG NS_IMETHOD GetFrameName(nsString& aResult) const { return MakeFrameName("Label", aResult); } #endif protected: PRBool FindFirstControl(nsIFrame* aParentFrame, nsIFormControlFrame*& aResultFrame); PRBool FindForControl(nsIFormControlFrame*& aResultFrame); void GetTranslatedRect(nsIPresContext* aPresContext, nsRect& aRect); PRIntn GetSkipSides() const; PRBool mInline; nsCursor mPreviousCursor; nsMouseState mLastMouseState; PRBool mControlIsInside; nsIFormControlFrame* mControlFrame; nsRect mTranslatedRect; }; nsresult NS_NewLabelFrame(nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsLabelFrame* it = new nsLabelFrame; if (!it) { return NS_ERROR_OUT_OF_MEMORY; } *aNewFrame = it; return NS_OK; } nsLabelFrame::nsLabelFrame() : nsHTMLContainerFrame() { mInline = PR_TRUE; mLastMouseState = eMouseNone; mPreviousCursor = eCursor_standard; mControlIsInside = PR_FALSE; mControlFrame = nsnull; mTranslatedRect = nsRect(0,0,0,0); } void nsLabelFrame::GetTranslatedRect(nsIPresContext* aPresContext, nsRect& aRect) { nsIView* view; nsPoint viewOffset(0,0); GetOffsetFromView(aPresContext, viewOffset, &view); while (nsnull != view) { nsPoint tempOffset; view->GetPosition(&tempOffset.x, &tempOffset.y); viewOffset += tempOffset; view->GetParent(view); } aRect = nsRect(viewOffset.x, viewOffset.y, mRect.width, mRect.height); } NS_METHOD nsLabelFrame::HandleEvent(nsIPresContext* aPresContext, nsGUIEvent* aEvent, nsEventStatus* aEventStatus) { NS_ENSURE_ARG_POINTER(aEventStatus); // if we don't have a control to send the event to // then what is the point? if (!mControlFrame) { return NS_OK; } // check to see if the label is disabled and if not, // then check to see if the control in the label is disabled if (nsEventStatus_eConsumeNoDefault != *aEventStatus) { if ( nsFormFrame::GetDisabled(this) ) { return NS_OK; } else { nsIFrame * frame; if (NS_OK == mControlFrame->QueryInterface(kIFrameIID, (void**)&frame)) { if ( nsFormFrame::GetDisabled(frame)) { return NS_OK; } } else { // if I can't get the nsIFrame something is really wrong // so leave return NS_OK; } } } // send the mouse click events down into the control *aEventStatus = nsEventStatus_eIgnore; switch (aEvent->message) { case NS_MOUSE_LEFT_BUTTON_DOWN: mControlFrame->SetFocus(PR_TRUE); mLastMouseState = eMouseDown; *aEventStatus = nsEventStatus_eConsumeNoDefault; break; case NS_MOUSE_LEFT_BUTTON_UP: if (eMouseDown == mLastMouseState) { if (nsEventStatus_eConsumeNoDefault != *aEventStatus) { mControlFrame->MouseClicked(aPresContext); } } mLastMouseState = eMouseUp; *aEventStatus = nsEventStatus_eConsumeNoDefault; break; } return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); } NS_IMETHODIMP nsLabelFrame::GetFrameForPoint(nsIPresContext* aPresContext, const nsPoint& aPoint, nsIFrame** aFrame) { nsHTMLContainerFrame::GetFrameForPoint(aPresContext, aPoint, aFrame); if (nsnull != *aFrame) { nsCOMPtr controlFrame = do_QueryInterface(*aFrame); if (!controlFrame) { *aFrame = this; } } return NS_OK; } NS_IMETHODIMP nsLabelFrame::GetFor(nsString& aResult) { nsresult result = NS_FORM_NOTOK; if (mContent) { nsIHTMLContent* htmlContent = nsnull; result = mContent->QueryInterface(kIHTMLContentIID, (void**)&htmlContent); if ((NS_OK == result) && htmlContent) { nsHTMLValue value; result = htmlContent->GetHTMLAttribute(nsHTMLAtoms::_for, value); if (NS_CONTENT_ATTR_HAS_VALUE == result) { if (eHTMLUnit_String == value.GetUnit()) { value.GetStringValue(aResult); } } NS_RELEASE(htmlContent); } } return result; } static PRBool IsButton(PRInt32 aType) { if ((NS_FORM_INPUT_BUTTON == aType) || (NS_FORM_INPUT_RESET == aType) || (NS_FORM_INPUT_SUBMIT == aType) || (NS_FORM_BUTTON_BUTTON == aType) || (NS_FORM_BUTTON_RESET == aType) || (NS_FORM_BUTTON_SUBMIT == aType)) { return PR_TRUE; } else { return PR_FALSE; } } PRBool nsLabelFrame::FindForControl(nsIFormControlFrame*& aResultFrame) { nsAutoString forId; if (NS_CONTENT_ATTR_HAS_VALUE != GetFor(forId)) { return PR_FALSE; } nsCOMPtr iDoc; if (NS_FAILED(mContent->GetDocument(*getter_AddRefs(iDoc)))) return PR_FALSE; nsCOMPtr htmlDoc = do_QueryInterface(iDoc); nsCOMPtr domElement; if (htmlDoc) { if (NS_FAILED(htmlDoc->GetElementById(forId, getter_AddRefs(domElement)))) return PR_FALSE; } #ifdef INCLUDE_XUL else { nsCOMPtr xulDoc = do_QueryInterface(iDoc); if (xulDoc) { if (NS_FAILED(xulDoc->GetElementById(forId, getter_AddRefs(domElement)))) return PR_FALSE; } } #endif // INCLUDE_XUL if (!domElement) return PR_FALSE; nsIPresShell *shell = iDoc->GetShellAt(0); if (nsnull == shell) return PR_FALSE; nsCOMPtr control = do_QueryInterface(domElement); nsCOMPtr controlContent = do_QueryInterface(domElement); // Labels have to be linked to form controls if (!control) return PR_FALSE; // buttons have implicit labels and we don't allow them to have explicit ones PRBool returnValue = PR_FALSE; PRInt32 type; control->GetType(&type); if (!IsButton(type)) { nsIFrame* frame; shell->GetPrimaryFrameFor(controlContent, &frame); if (nsnull != frame) { nsIFormControlFrame* fcFrame = nsnull; nsresult result = frame->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_OK == result) && (nsnull != fcFrame)) { aResultFrame = fcFrame; NS_RELEASE(fcFrame); returnValue = PR_TRUE; } } } NS_RELEASE(shell); return returnValue; } PRBool nsLabelFrame::FindFirstControl(nsIFrame* aParentFrame, nsIFormControlFrame*& aResultFrame) { nsIFrame* child = nsnull; aParentFrame->FirstChild(nsnull, &child); while (nsnull != child) { nsIFormControlFrame* fcFrame = nsnull; nsresult result = child->QueryInterface(kIFormControlFrameIID, (void**)&fcFrame); if ((NS_OK == result) && fcFrame) { PRInt32 type; fcFrame->GetType(&type); // buttons have implicit labels and we don't allow them to have explicit ones if (!IsButton(type)) { aResultFrame = fcFrame; return PR_TRUE; } NS_RELEASE(fcFrame); } else if (FindFirstControl(child, aResultFrame)) { return PR_TRUE; } child->GetNextSibling(&child); } return PR_FALSE; } NS_IMETHODIMP nsLabelFrame::SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList) { // cache our display type const nsStyleDisplay* styleDisplay; GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) styleDisplay); mInline = (NS_STYLE_DISPLAY_BLOCK != styleDisplay->mDisplay); PRUint8 flags = (mInline) ? NS_BLOCK_SHRINK_WRAP : 0; nsIFrame* areaFrame; NS_NewAreaFrame(&areaFrame, flags); mFrames.SetFrames(areaFrame); // Resolve style and initialize the frame nsIStyleContext* styleContext; aPresContext->ResolvePseudoStyleContextFor(mContent, nsHTMLAtoms::labelContentPseudo, mStyleContext, PR_FALSE, &styleContext); mFrames.FirstChild()->Init(aPresContext, mContent, this, styleContext, nsnull); NS_RELEASE(styleContext); // Set the geometric and content parent for each of the child frames for (nsIFrame* frame = aChildList; nsnull != frame; frame->GetNextSibling(&frame)) { frame->SetParent(mFrames.FirstChild()); } // Queue up the frames for the body frame return mFrames.FirstChild()->SetInitialChildList(aPresContext, nsnull, aChildList); } NS_IMETHODIMP nsLabelFrame::Paint(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { return nsHTMLContainerFrame::Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer); } // XXX a hack until the reflow state does this correctly // XXX when it gets fixed, leave in the printf statements or add an assertion static void LabelHack(nsHTMLReflowState& aReflowState, char* aMessage) { if (aReflowState.mComputedWidth == 0) { aReflowState.mComputedWidth = aReflowState.availableWidth; } if ((aReflowState.mComputedWidth != NS_INTRINSICSIZE) && (aReflowState.mComputedWidth > aReflowState.availableWidth) && (aReflowState.availableWidth > 0)) { // printf("BUG - %s has a computed width = %d, available width = %d \n", // aMessage, aReflowState.mComputedWidth, aReflowState.availableWidth); aReflowState.mComputedWidth = aReflowState.availableWidth; } if (aReflowState.mComputedHeight == 0) { aReflowState.mComputedHeight = aReflowState.availableHeight; } if ((aReflowState.mComputedHeight != NS_INTRINSICSIZE) && (aReflowState.mComputedHeight > aReflowState.availableHeight) && (aReflowState.availableHeight > 0)) { // printf("BUG - %s has a computed height = %d, available height = %d \n", // aMessage, aReflowState.mComputedHeight, aReflowState.availableHeight); aReflowState.mComputedHeight = aReflowState.availableHeight; } } NS_IMETHODIMP nsLabelFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { // call our base class nsresult rv = nsHTMLContainerFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); // create our view, we need a view to grab the mouse nsIView* view; GetView(aPresContext, &view); if (!view) { nsresult result = nsComponentManager::CreateInstance(kViewCID, nsnull, kIViewIID, (void **)&view); nsCOMPtr presShell; aPresContext->GetShell(getter_AddRefs(presShell)); nsCOMPtr viewMan; presShell->GetViewManager(getter_AddRefs(viewMan)); nsIFrame* parWithView; nsIView *parView; GetParentWithView(aPresContext, &parWithView); parWithView->GetView(aPresContext, &parView); // the view's size is not know yet, but its size will be kept in synch with our frame. nsRect boundBox(0, 0, 0, 0); result = view->Init(viewMan, boundBox, parView, nsnull); view->SetContentTransparency(PR_TRUE); viewMan->InsertChild(parView, view, 0); SetView(aPresContext, view); } return rv; } NS_IMETHODIMP nsLabelFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { // XXX remove the following when the reflow state is fixed LabelHack((nsHTMLReflowState&)aReflowState, "BUG - label"); if (nsnull == mControlFrame) { // check to see if a form control is referenced via the "for" attribute if (FindForControl(mControlFrame)) { mControlIsInside = PR_FALSE; } else { // find the 1st (and should be only) form control contained within if there is no "for" mControlIsInside = FindFirstControl(this, mControlFrame); } } nsSize availSize(aReflowState.mComputedWidth, aReflowState.mComputedHeight); // get border and padding const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); nsMargin borderPadding; spacing->CalcBorderPaddingFor(this, borderPadding); if (NS_INTRINSICSIZE != availSize.width) { availSize.width -= borderPadding.left + borderPadding.right; availSize.width = PR_MAX(availSize.width,0); } if (NS_AUTOHEIGHT != availSize.height) { availSize.height -= borderPadding.top + borderPadding.bottom; availSize.height = PR_MAX(availSize.height,0); } // reflow the child nsIFrame* firstKid = mFrames.FirstChild(); nsHTMLReflowState reflowState(aPresContext, aReflowState, firstKid, availSize); // XXX remove when reflow state is fixed LabelHack(reflowState, "label's area"); ReflowChild(firstKid, aPresContext, aDesiredSize, reflowState, borderPadding.left, borderPadding.top, 0, aStatus); // Place the child FinishReflowChild(firstKid, aPresContext, aDesiredSize, borderPadding.left, borderPadding.top, 0); // add in our border and padding to the size of the child aDesiredSize.width += borderPadding.left + borderPadding.right; aDesiredSize.height += borderPadding.top + borderPadding.bottom; // adjust our max element size, if necessary if (aDesiredSize.maxElementSize) { aDesiredSize.AddBorderPaddingToMaxElementSize(borderPadding); } aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; aStatus = NS_FRAME_COMPLETE; return NS_OK; } PRIntn nsLabelFrame::GetSkipSides() const { return 0; }