/* -*- 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.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsInput.h" #include "nsInputFrame.h" #include "nsHTMLParts.h" #include "nsHTMLContainer.h" #include "nsIRenderingContext.h" #include "nsIPresShell.h" #include "nsIPresContext.h" #include "nsIStyleContext.h" #include "nsLeafFrame.h" #include "nsCSSRendering.h" #include "nsHTMLIIDs.h" #include "nsIView.h" #include "nsIViewManager.h" #include "nsCoord.h" #include "nsWidgetsCID.h" #include "nsViewsCID.h" #include "nsRepository.h" #include "nsGUIEvent.h" #include "nsDOMEvent.h" #include "nsIFontCache.h" #include "nsIFontMetrics.h" #include "nsIFormManager.h" #include "nsIDeviceContext.h" #include "nsHTMLAtoms.h" #include "nsIButton.h" // remove this when GetCID is pure virtual #include "nsICheckButton.h" //remove this #include "nsITextWidget.h" //remove this #include "nsISupports.h" #include "nsHTMLForms.h" #include "nsStyleConsts.h" #include "nsUnitConversion.h" #include "nsCSSLayout.h" #include "nsStyleUtil.h" static NS_DEFINE_IID(kIWidgetIID, NS_IWIDGET_IID); nsInputFrame::nsInputFrame(nsIContent* aContent, nsIFrame* aParentFrame) : nsInputFrameSuper(aContent, aParentFrame) { mLastMouseState = eMouseNone; mDidInit = PR_FALSE; } nsInputFrame::~nsInputFrame() { //printf("nsInputFrame::~nsInputFrame \n"); } NS_METHOD nsInputFrame::SetRect(const nsRect& aRect) { return nsInputFrameSuper::SetRect(aRect); } nsFormRenderingMode nsInputFrame::GetMode() const { nsInput* content = (nsInput *)mContent; nsIFormManager* formMan = content->GetFormManager(); if (formMan) { nsFormRenderingMode mode = formMan->GetMode(); NS_RELEASE(formMan); return mode; } else { return kBackwardMode; } } nscoord nsInputFrame::GetScrollbarWidth(float aPixToTwip) { return NSIntPixelsToTwips(19, aPixToTwip); // XXX this is windows } nscoord nsInputFrame::GetVerticalBorderWidth(float aPixToTwip) const { return NSIntPixelsToTwips(3, aPixToTwip); } nscoord nsInputFrame::GetHorizontalBorderWidth(float aPixToTwip) const { return GetVerticalBorderWidth(aPixToTwip); } nscoord nsInputFrame::GetVerticalInsidePadding(float aPixToTwip, nscoord aInnerHeight) const { return NSIntPixelsToTwips(3, aPixToTwip); } nscoord nsInputFrame::GetHorizontalInsidePadding(float aPixToTwip, nscoord aInnerWidth, nscoord aCharWidth) const { return GetVerticalInsidePadding(aPixToTwip, aInnerWidth); } // XXX it would be cool if form element used our rendering sw, then // they could be blended, and bordered, and so on... NS_METHOD nsInputFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); if (disp->mVisible) { // Make sure the widget is visible if it isn't currently visible nsIView* view = nsnull; GetView(view); if (nsnull != view) { if (PR_FALSE == mDidInit) { nsIFormManager* formMan = ((nsInput*)mContent)->GetFormManager(); if (formMan) { formMan->Init(PR_FALSE); NS_RELEASE(formMan); } PostCreateWidget(&aPresContext, view); mDidInit = PR_TRUE; } // SetViewVisiblity(&aPresContext, PR_TRUE); } // Point borders/padding if any return nsInputFrameSuper::Paint(aPresContext, aRenderingContext, aDirtyRect); } return NS_OK; } //don't call this. MMP void nsInputFrame::SetViewVisiblity(nsIPresContext* aPresContext, PRBool aShow) { nsIView* view = nsnull; GetView(view); if (nsnull != view) { nsIWidget* widget; nsresult result = GetWidget(view, &widget); if (NS_OK == result) { // initially the widget was created as hidden nsViewVisibility newVisibility = aShow ? nsViewVisibility_kShow : nsViewVisibility_kHide; nsViewVisibility currentVisibility; view->GetVisibility(currentVisibility); if (newVisibility != currentVisibility) { // this only inits the 1st time // XXX kipp says: this is yucky; init on first visibility seems lame // XXX is this even called nsIFormManager* formMan = ((nsInput*)mContent)->GetFormManager(); if (formMan) { formMan->Init(PR_FALSE); NS_RELEASE(formMan); } view->SetVisibility(newVisibility); PostCreateWidget(aPresContext, view); } NS_IF_RELEASE(widget); } } } PRBool nsInputFrame::BoundsAreSet() { if ((0 != mRect.width) || (0 != mRect.height)) { return PR_TRUE; } else { return PR_FALSE; } } PRBool nsInputFrame::IsHidden() { nsInput* content = (nsInput *)mContent; return content->IsHidden(); } void nsInputFrame::GetDesiredSize(nsIPresContext* aPresContext, const nsReflowState& aReflowState, nsReflowMetrics& aDesiredLayoutSize, nsSize& aDesiredWidgetSize) { // get the css size and let the frame use or override it nsSize styleSize; GetStyleSize(*aPresContext, aReflowState, styleSize); // subclasses should always override this method, but if not and no css, make it small aDesiredLayoutSize.width = (styleSize.width > CSS_NOTSET) ? styleSize.width : 144; aDesiredLayoutSize.height = (styleSize.height > CSS_NOTSET) ? styleSize.height : 144; aDesiredLayoutSize.ascent = aDesiredLayoutSize.height; aDesiredLayoutSize.descent = 0; aDesiredWidgetSize.width = aDesiredLayoutSize.width; aDesiredWidgetSize.height = aDesiredLayoutSize.height; } void nsInputFrame::GetDesiredSize(nsIPresContext* aPresContext, const nsReflowState& aReflowState, nsReflowMetrics& aDesiredSize) { nsSize ignore; GetDesiredSize(aPresContext, aReflowState, aDesiredSize, ignore); } NS_IMETHODIMP nsInputFrame::DidReflow(nsIPresContext& aPresContext, nsDidReflowStatus aStatus) { nsresult rv = nsInputFrameSuper::DidReflow(aPresContext, aStatus); // The view is created hidden; once we have reflowed it and it has been // positioned then we show it. if (NS_FRAME_REFLOW_FINISHED == aStatus) { nsIView* view = nsnull; GetView(view); if (nsnull != view) { view->SetVisibility(nsViewVisibility_kShow); } } return rv; } NS_METHOD nsInputFrame::Reflow(nsIPresContext& aPresContext, nsReflowMetrics& aDesiredSize, const nsReflowState& aReflowState, nsReflowStatus& aStatus) { nsIView* view = nsnull; GetView(view); if (nsnull == view) { static NS_DEFINE_IID(kViewCID, NS_VIEW_CID); static NS_DEFINE_IID(kIViewIID, NS_IVIEW_IID); // make sure the style context is set if (nsnull == mStyleContext) { GetStyleContext(&aPresContext, mStyleContext); } nsresult result = nsRepository::CreateInstance(kViewCID, nsnull, kIViewIID, (void **)&view); if (NS_OK != result) { NS_ASSERTION(0, "Could not create view for button"); aStatus = NS_FRAME_NOT_COMPLETE; return result; } nsIPresShell *presShell = aPresContext.GetShell(); // need to release nsIViewManager *viewMan = presShell->GetViewManager(); // need to release NS_RELEASE(presShell); GetDesiredSize(&aPresContext, aReflowState, aDesiredSize, mWidgetSize); //nsRect boundBox(0, 0, mWidgetSize.width, mWidgetSize.height); nsRect boundBox(0, 0, aDesiredSize.width, aDesiredSize.height); nsIFrame* parWithView; nsIView *parView; GetParentWithView(parWithView); parWithView->GetView(parView); const nsIID& id = GetCID(); nsWidgetInitData* initData = GetWidgetInitData(aPresContext); // needs to be deleted // initialize the view as hidden since we don't know the (x,y) until Paint result = view->Init(viewMan, boundBox, parView, &id, initData, nsnull, 0, nsnull, 1.0f, nsViewVisibility_kHide); if (nsnull != initData) { delete(initData); } if (NS_OK != result) { NS_ASSERTION(0, "widget initialization failed"); aStatus = NS_FRAME_NOT_COMPLETE; return NS_OK; } // set the content's widget, so it can get content modified by the widget nsIWidget *widget; result = GetWidget(view, &widget); if (NS_OK == result) { nsInput* content = (nsInput *)mContent; // change this cast to QueryInterface content->SetWidget(widget); NS_IF_RELEASE(widget); } else { NS_ASSERTION(0, "could not get widget"); } viewMan->InsertChild(parView, view, 0); SetView(view); NS_IF_RELEASE(viewMan); } else { GetDesiredSize(&aPresContext, aReflowState, aDesiredSize, mWidgetSize); // If we are being reflowed and have a view, hide the view until // we are told to paint (which is when our location will have // stabilized). // SetViewVisiblity(aPresContext, PR_FALSE); } aDesiredSize.ascent = aDesiredSize.height; aDesiredSize.descent = 0; if (nsnull != aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = aDesiredSize.width; aDesiredSize.maxElementSize->height = aDesiredSize.height; } aStatus = NS_FRAME_COMPLETE; return NS_OK; } nsWidgetInitData* nsInputFrame::GetWidgetInitData(nsIPresContext& aPresContext) { return nsnull; } void nsInputFrame::PostCreateWidget(nsIPresContext* aPresContext, nsIView *aView) { } nsresult nsInputFrame::GetWidget(nsIView* aView, nsIWidget** aWidget) { nsIWidget* widget; aView->GetWidget(widget); nsresult result; if (nsnull == widget) { aWidget = nsnull; result = NS_ERROR_FAILURE; } else { const nsIID id = GetIID(); result = widget->QueryInterface(kIWidgetIID, (void**)aWidget); if (NS_FAILED(result)) { NS_ASSERTION(0, "The widget interface is invalid"); // need to print out more details of the widget } NS_RELEASE(widget); } return result; } const nsIID& nsInputFrame::GetIID() { static NS_DEFINE_IID(kButtonIID, NS_IBUTTON_IID); return kButtonIID; } const nsIID& nsInputFrame::GetCID() { static NS_DEFINE_IID(kButtonCID, NS_BUTTON_CID); return kButtonCID; } NS_METHOD nsInputFrame::HandleEvent(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus) { if (nsEventStatus_eConsumeNoDefault == aEventStatus) { return NS_OK; } // make sure that the widget in the event is this // XXX if there is no view, it could be an image button. Unfortunately, // every image button will get every event. nsIView* view; GetView(view); if (view) { nsInput* content = (nsInput *)mContent; if (content->GetWidget() != aEvent->widget) { aEventStatus = nsEventStatus_eIgnore; return NS_OK; } } //printf(" %d %d %d %d (%d,%d) \n", this, aEvent->widget, aEvent->widgetSupports, // aEvent->message, aEvent->point.x, aEvent->point.y); switch (aEvent->message) { case NS_MOUSE_ENTER: mLastMouseState = eMouseEnter; break; case NS_MOUSE_LEFT_BUTTON_DOWN: mLastMouseState = (eMouseEnter == mLastMouseState) ? eMouseDown : eMouseNone; break; case NS_MOUSE_LEFT_BUTTON_UP: if (eMouseDown == mLastMouseState) { if (nsEventStatus_eConsumeNoDefault != aEventStatus) { MouseClicked(&aPresContext); } } mLastMouseState = eMouseEnter; break; case NS_MOUSE_EXIT: mLastMouseState = eMouseNone; break; case NS_KEY_DOWN: if (NS_KEY_EVENT == aEvent->eventStructType) { nsKeyEvent* keyEvent = (nsKeyEvent*)aEvent; if (NS_VK_RETURN == keyEvent->keyCode) { EnterPressed(aPresContext); } } break; } aEventStatus = nsEventStatus_eConsumeDoDefault; return NS_OK; } void nsInputFrame::GetStyleSize(nsIPresContext& aPresContext, const nsReflowState& aReflowState, nsSize& aSize) { PRIntn ss = nsCSSLayout::GetStyleSize(&aPresContext, aReflowState, aSize); if (0 == (ss & NS_SIZE_HAS_WIDTH)) { aSize.width = CSS_NOTSET; } if (0 == (ss & NS_SIZE_HAS_HEIGHT)) { aSize.height = CSS_NOTSET; } } nscoord nsInputFrame::GetTextSize(nsIPresContext& aPresContext, nsInputFrame* aFrame, const nsString& aString, nsSize& aSize) { nsFont font = aPresContext.GetDefaultFixedFont(); aFrame->GetFont(&aPresContext, font); //printf("\n GetTextSize %s", aString.ToNewCString()); nsIDeviceContext* deviceContext = aPresContext.GetDeviceContext(); nsIFontCache* fontCache; deviceContext->GetFontCache(fontCache); nsIFontMetrics* fontMet; fontCache->GetMetricsFor(font, fontMet); fontMet->GetWidth(aString, aSize.width); fontMet->GetHeight(aSize.height); nscoord charWidth; fontMet->GetWidth("W", charWidth); NS_RELEASE(fontMet); NS_RELEASE(fontCache); NS_RELEASE(deviceContext); return charWidth; } nscoord nsInputFrame::GetTextSize(nsIPresContext& aPresContext, nsInputFrame* aFrame, PRInt32 aNumChars, nsSize& aSize) { nsAutoString val; char repChar = (kBackwardMode == aFrame->GetMode()) ? '%' : 'e'; for (int i = 0; i < aNumChars; i++) { val += repChar; } return GetTextSize(aPresContext, aFrame, val, aSize); } PRInt32 nsInputFrame::CalculateSize (nsIPresContext* aPresContext, nsInputFrame* aFrame, const nsSize& aCSSSize, nsInputDimensionSpec& aSpec, nsSize& aBounds, PRBool& aWidthExplicit, PRBool& aHeightExplicit, nscoord& aRowHeight) { nscoord charWidth = 0; PRInt32 numRows = ATTR_NOTSET; aWidthExplicit = PR_FALSE; aHeightExplicit = PR_FALSE; /* XXX For some reason on Win95 Paint doesn't initially get called */ /* for elements that are out of the view, so we weren't calling */ /* PostCreateWidget. I'm calling it here as a work-around */ nsIView* view = nsnull; aFrame->GetView(view); if (nsnull != view) { if (PR_FALSE == aFrame->mDidInit) { nsIFormManager* formMan = ((nsInput*)aFrame->mContent)->GetFormManager(); if (formMan) { formMan->Init(PR_FALSE); aFrame->PostCreateWidget(aPresContext, view); aFrame->mDidInit = PR_TRUE; NS_RELEASE(formMan); } } } aBounds.width = CSS_NOTSET; aBounds.height = CSS_NOTSET; nsSize textSize(0,0); nsInput* content; aFrame->GetContent((nsIContent*&) content); nsAutoString valAttr; nsresult valStatus = NS_CONTENT_ATTR_NOT_THERE; if (nsnull != aSpec.mColValueAttr) { valStatus = ((nsHTMLTagContent*)content)->GetAttribute(aSpec.mColValueAttr, valAttr); } nsHTMLValue colAttr; nsresult colStatus = NS_CONTENT_ATTR_NOT_THERE; if (nsnull != aSpec.mColSizeAttr) { colStatus = content->GetAttribute(aSpec.mColSizeAttr, colAttr); } float p2t = aPresContext->GetPixelsToTwips(); // determine the width nscoord adjSize; if (NS_CONTENT_ATTR_HAS_VALUE == colStatus) { // col attr will provide width if (aSpec.mColSizeAttrInPixels) { adjSize = (colAttr.GetPixelValue() > 0) ? colAttr.GetPixelValue() : 15; aBounds.width = NSIntPixelsToTwips(adjSize, p2t); } else { PRInt32 col = ((colAttr.GetUnit() == eHTMLUnit_Pixel) ? colAttr.GetPixelValue() : colAttr.GetIntValue()); if (col <= 0) { col = 1; } charWidth = GetTextSize(*aPresContext, aFrame, col, aBounds); aRowHeight = aBounds.height; // XXX aBounds.height has CSS_NOTSET } if (aSpec.mColSizeAttrInPixels) { aWidthExplicit = PR_TRUE; } } else { if (CSS_NOTSET != aCSSSize.width) { // css provides width aBounds.width = (aCSSSize.width > 0) ? aCSSSize.width : 1; aWidthExplicit = PR_TRUE; } else { if (NS_CONTENT_ATTR_HAS_VALUE == valStatus) { // use width of initial value charWidth = GetTextSize(*aPresContext, aFrame, valAttr, aBounds); } else if (aSpec.mColDefaultValue) { // use default value charWidth = GetTextSize(*aPresContext, aFrame, *aSpec.mColDefaultValue, aBounds); } else if (aSpec.mColDefaultSizeInPixels) { // use default width in pixels charWidth = GetTextSize(*aPresContext, aFrame, 1, aBounds); aBounds.width = aSpec.mColDefaultSize; } else { // use default width in num characters charWidth = GetTextSize(*aPresContext, aFrame, aSpec.mColDefaultSize, aBounds); } aRowHeight = aBounds.height; // XXX aBounds.height has CSS_NOTSET } } // determine the height nsHTMLValue rowAttr; nsresult rowStatus = NS_CONTENT_ATTR_NOT_THERE; if (nsnull != aSpec.mRowSizeAttr) { rowStatus = content->GetAttribute(aSpec.mRowSizeAttr, rowAttr); } if (NS_CONTENT_ATTR_HAS_VALUE == rowStatus) { // row attr will provide height PRInt32 rowAttrInt = ((rowAttr.GetUnit() == eHTMLUnit_Pixel) ? rowAttr.GetPixelValue() : rowAttr.GetIntValue()); adjSize = (rowAttrInt > 0) ? rowAttrInt : 1; if (0 == charWidth) { charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize); aBounds.height = textSize.height * adjSize; aRowHeight = textSize.height; numRows = adjSize; } else { aBounds.height = aBounds.height * adjSize; } } else if (CSS_NOTSET != aCSSSize.height) { // css provides height aBounds.height = (aCSSSize.height > 0) ? aCSSSize.height : 1; aHeightExplicit = PR_TRUE; } else { // use default height in num lines if (0 == charWidth) { charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize); aBounds.height = textSize.height * aSpec.mRowDefaultSize; aRowHeight = textSize.height; } else { aBounds.height = aBounds.height * aSpec.mRowDefaultSize; } } if ((0 == charWidth) || (0 == textSize.width)) { charWidth = GetTextSize(*aPresContext, aFrame, 1, textSize); aRowHeight = textSize.height; } // add inside padding if necessary if (!aWidthExplicit) { aBounds.width += (2 * aFrame->GetHorizontalInsidePadding(p2t, aBounds.width, charWidth)); } if (!aHeightExplicit) { aBounds.height += (2 * aFrame->GetVerticalInsidePadding(p2t, textSize.height)); } NS_RELEASE(content); if (ATTR_NOTSET == numRows) { numRows = aBounds.height / aRowHeight; } return numRows; } // this handles all of the input types rather than having them do it. const void nsInputFrame::GetFont(nsIPresContext* aPresContext, nsFont& aFont) { const nsStyleFont* styleFont = (const nsStyleFont*)mStyleContext->GetStyleData(eStyleStruct_Font); nsString type; ((nsInput*)mContent)->GetType(type); // XXX shouldn't this be atom compares instead? if ( type.EqualsIgnoreCase("text") || type.EqualsIgnoreCase("textarea") || type.EqualsIgnoreCase("password")) { aFont = styleFont->mFixedFont; } else if (type.EqualsIgnoreCase("button") || type.EqualsIgnoreCase("submit") || type.EqualsIgnoreCase("reset") || type.EqualsIgnoreCase("select")) { if ((styleFont->mFlags & NS_STYLE_FONT_FACE_EXPLICIT) || (styleFont->mFlags & NS_STYLE_FONT_SIZE_EXPLICIT)) { aFont = styleFont->mFixedFont; aFont.weight = NS_FONT_WEIGHT_NORMAL; // always normal weight aFont.size = styleFont->mFont.size; // normal font size if (0 == (styleFont->mFlags & NS_STYLE_FONT_FACE_EXPLICIT)) { aFont.name = "Arial"; // XXX windows specific font } } else { // use arial, scaled down one HTML size // italics, decoration & variant(?) get used aFont = styleFont->mFont; aFont.name = "Arial"; // XXX windows specific font aFont.weight = NS_FONT_WEIGHT_NORMAL; const nsFont& normal = aPresContext->GetDefaultFont(); PRInt32 scaler = aPresContext->GetFontScaler(); float scaleFactor = nsStyleUtil::GetScalingFactor(scaler); PRInt32 fontIndex = nsStyleUtil::FindNextSmallerFontSize(aFont.size, (PRInt32)normal.size, scaleFactor); aFont.size = nsStyleUtil::CalcFontPointSize(fontIndex, (PRInt32)normal.size, scaleFactor); } } }