Mozilla/mozilla/layout/html/forms/src/nsSelectControlFrame.cpp
rods%netscape.com 7ff8fc122b Changed GetVerticalInsidePading to take the PresContext to the method can use the cached LookAndFeel object
and it now uses the cached L&F obj


git-svn-id: svn://10.0.0.236/trunk@49424 18797224-902f-48f8-a5cc-f745e15eee43
1999-09-30 11:33:33 +00:00

1419 lines
45 KiB
C++

/* -*- 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.
*/
// YY need to pass isMultiple before create called
#include "nsCOMPtr.h"
#include "nsFormControlFrame.h"
#include "nsFormFrame.h"
#include "nsIDOMNode.h"
#include "nsIDOMHTMLSelectElement.h"
#include "nsIDOMHTMLOptionElement.h"
#include "nsIDOMHTMLCollection.h"
#include "nsDOMEvent.h"
#include "nsIContent.h"
#include "prtypes.h"
#include "nsIFrame.h"
#include "nsISupports.h"
#include "nsIAtom.h"
#include "nsIPresContext.h"
#include "nsIHTMLContent.h"
#include "nsHTMLIIDs.h"
#include "nsIRadioButton.h"
#include "nsWidgetsCID.h"
#include "nsSize.h"
#include "nsHTMLAtoms.h"
#include "nsIView.h"
#include "nsIListWidget.h"
#include "nsIComboBox.h"
#include "nsIListBox.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsStyleUtil.h"
#include "nsFont.h"
#include "nsIDeviceContext.h"
#include "nsIFontMetrics.h"
#include "nsILookAndFeel.h"
#include "nsIComponentManager.h"
static NS_DEFINE_IID(kIDOMHTMLSelectElementIID, NS_IDOMHTMLSELECTELEMENT_IID);
static NS_DEFINE_IID(kIDOMHTMLOptionElementIID, NS_IDOMHTMLOPTIONELEMENT_IID);
static NS_DEFINE_IID(kIDOMHTMLCollectionIID, NS_IDOMHTMLCOLLECTION_IID);
static NS_DEFINE_IID(kIDOMNodeIID, NS_IDOMNODE_IID);
static NS_DEFINE_IID(kListWidgetIID, NS_ILISTWIDGET_IID);
static NS_DEFINE_IID(kComboBoxIID, NS_ICOMBOBOX_IID);
static NS_DEFINE_IID(kListBoxIID, NS_ILISTBOX_IID);
static NS_DEFINE_IID(kComboCID, NS_COMBOBOX_CID);
static NS_DEFINE_IID(kListCID, NS_LISTBOX_CID);
class nsOption;
class nsSelectControlFrame : public nsFormControlFrame {
public:
nsSelectControlFrame();
NS_IMETHOD GetFrameName(nsString& aResult) const {
return MakeFrameName("SelectControl", aResult);
}
virtual nsWidgetInitData* GetWidgetInitData(nsIPresContext& aPresContext);
virtual void PostCreateWidget(nsIPresContext* aPresContext,
nscoord& aWidth,
nscoord& aHeight);
virtual const nsIID& GetCID();
virtual const nsIID& GetIID();
virtual nscoord GetVerticalBorderWidth(float aPixToTwip) const;
virtual nscoord GetHorizontalBorderWidth(float aPixToTwip) const;
virtual nscoord GetVerticalInsidePadding(nsIPresContext& aPresContext,
float aPixToTwip,
nscoord aInnerHeight) const;
virtual nscoord GetHorizontalInsidePadding(nsIPresContext& aPresContext,
float aPixToTwip,
nscoord aInnerWidth,
nscoord aCharWidth) const;
virtual PRInt32 GetMaxNumValues();
virtual PRBool GetNamesValues(PRInt32 aMaxNumValues, PRInt32& aNumValues,
nsString* aValues, nsString* aNames);
NS_METHOD GetMultiple(PRBool* aResult, nsIDOMHTMLSelectElement* aSelect = nsnull);
virtual void Reset();
//
// XXX: The following paint methods are TEMPORARY. It is being used to get printing working
// under windows. Later it may be used to GFX-render the controls to the display.
// Expect this code to repackaged and moved to a new location in the future.
//
NS_IMETHOD Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer);
virtual void PaintOption(PRBool aPaintSelected, nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext, nsString aText, nscoord aX, nscoord aY,
const nsRect& aInside, nscoord aTextHeight);
virtual void PaintSelectControl(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect);
///XXX: End o the temporary methods
virtual void MouseClicked(nsIPresContext* aPresContext);
virtual void ControlChanged(nsIPresContext* aPresContext);
// nsIFormControLFrame
NS_IMETHOD SetProperty(nsIAtom* aName, const nsString& aValue);
NS_IMETHOD GetProperty(nsIAtom* aName, nsString& aValue);
NS_IMETHOD AttributeChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
nsIAtom* aAttribute,
PRInt32 aHint);
protected:
PRUint32 mNumRows;
nsIDOMHTMLSelectElement* GetSelect();
nsIDOMHTMLCollection* GetOptions(nsIDOMHTMLSelectElement* aSelect = nsnull);
nsIDOMHTMLOptionElement* GetOption(nsIDOMHTMLCollection& aOptions, PRUint32 aIndex);
PRBool GetOptionValue(nsIDOMHTMLCollection& aCollecton, PRUint32 aIndex, nsString& aValue);
PRUint32 GetSelectedIndex();
virtual void GetDesiredSize(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsHTMLReflowMetrics& aDesiredLayoutSize,
nsSize& aDesiredWidgetSize);
void GetWidgetSize(nsIPresContext& aPresContext, nscoord& aWidth, nscoord& aHeight);
PRBool mIsComboBox;
PRBool mOptionsAdded;
// Store the state of the options in a local array whether the widget is
// GFX-rendered or not. This is used to detect changes in MouseClicked
PRUint32 mNumOptions;
PRBool* mOptionSelected;
// Accessor methods for mOptionsSelected and mNumOptions
void GetOptionSelected(PRUint32 index, PRBool* aValue);
void SetOptionSelected(PRUint32 index, PRBool aValue);
void GetOptionSelectedFromWidget(PRInt32 index, PRBool* aValue);
// Free our locally cached option array
~nsSelectControlFrame();
};
nsresult
NS_NewSelectControlFrame(nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsSelectControlFrame* it = new nsSelectControlFrame;
if (!it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
nsSelectControlFrame::nsSelectControlFrame()
: nsFormControlFrame()
{
mIsComboBox = PR_FALSE;
mOptionsAdded = PR_FALSE;
mNumRows = 0;
mNumOptions = 0;
mOptionSelected = nsnull;
}
// XXX is this the right way to clean up?
nsSelectControlFrame::~nsSelectControlFrame()
{
if (mOptionSelected)
delete[] mOptionSelected;
}
nscoord
nsSelectControlFrame::GetVerticalBorderWidth(float aPixToTwip) const
{
return NSIntPixelsToTwips(1, aPixToTwip);
}
nscoord
nsSelectControlFrame::GetHorizontalBorderWidth(float aPixToTwip) const
{
return GetVerticalBorderWidth(aPixToTwip);
}
nscoord
nsSelectControlFrame::GetVerticalInsidePadding(nsIPresContext& aPresContext,
float aPixToTwip,
nscoord aInnerHeight) const
{
// XXX NOTE: the enums eMetric_ListVerticalInsidePadding and eMetric_ListShouldUseVerticalInsidePadding
// are ONLY needed because GTK is not using the "float" padding values and wants to only use an
// integer value for the padding instead of calculating like the other platforms.
//
// If GTK decides to start calculating the value, PLEASE remove these two enum from nsILookAndFeel and
// all the platforms nsLookAndFeel impementations so we don't have these extra values remaining in the code.
// The two enums are:
// eMetric_ListVerticalInsidePadding
// eMetric_ListShouldUseVerticalInsidePadding
//
float pad;
PRInt32 padInside;
PRInt32 shouldUsePadInside;
nsCOMPtr<nsILookAndFeel> lookAndFeel;
if (NS_SUCCEEDED(aPresContext.GetLookAndFeel(getter_AddRefs(lookAndFeel)))) {
lookAndFeel->GetMetric(nsILookAndFeel::eMetricFloat_ListVerticalInsidePadding, pad);
// These two (below) are really only needed for GTK
lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ListVerticalInsidePadding, padInside);
lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ListShouldUseVerticalInsidePadding, shouldUsePadInside);
NS_RELEASE(lookAndFeel);
}
if (1 == shouldUsePadInside) {
return NSIntPixelsToTwips(padInside, aPixToTwip); // XXX this is probably wrong (GTK)
} else {
return (nscoord)NSToIntRound(float(aInnerHeight) * pad);
}
}
PRInt32
nsSelectControlFrame::GetHorizontalInsidePadding(nsIPresContext& aPresContext,
float aPixToTwip,
nscoord aInnerWidth,
nscoord aCharWidth) const
{
// XXX NOTE: the enum eMetric_ListShouldUseHorizontalInsideMinimumPadding
// is ONLY needed because GTK is not using the "float" padding values and wants to only use the
// "minimum" integer value for the padding instead of calculating and comparing like the other platforms.
//
// If GTK decides to start calculating and comparing the values,
// PLEASE remove these the enum from nsILookAndFeel and
// all the platforms nsLookAndFeel impementations so we don't have these extra values remaining in the code.
// The enum is:
// eMetric_ListShouldUseHorizontalInsideMinimumPadding
//
float pad;
PRInt32 padMin;
PRInt32 shouldUsePadMin;
nsCOMPtr<nsILookAndFeel> lookAndFeel;
if (NS_SUCCEEDED(aPresContext.GetLookAndFeel(getter_AddRefs(lookAndFeel)))) {
lookAndFeel->GetMetric(nsILookAndFeel::eMetricFloat_ListHorizontalInsidePadding, pad);
lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ListHorizontalInsideMinimumPadding, padMin);
// This one (below) is really only needed for GTK
lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ListShouldUseHorizontalInsideMinimumPadding, shouldUsePadMin);
NS_RELEASE(lookAndFeel);
}
if (1 == shouldUsePadMin) {
return NSIntPixelsToTwips(padMin, aPixToTwip); // XXX this is probably wrong (GTK)
} else {
nscoord padding = (nscoord)NSToIntRound(float(aCharWidth) * pad);
nscoord min = NSIntPixelsToTwips(padMin, aPixToTwip);
if (padding > min) {
return padding;
} else {
return min;
}
}
}
const nsIID&
nsSelectControlFrame::GetIID()
{
if (mIsComboBox) {
return kComboBoxIID;
} else {
return kListBoxIID;
}
}
const nsIID&
nsSelectControlFrame::GetCID()
{
if (mIsComboBox) {
return kComboCID;
} else {
return kListCID;
}
}
void
nsSelectControlFrame::GetDesiredSize(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsHTMLReflowMetrics& aDesiredLayoutSize,
nsSize& aDesiredWidgetSize)
{
nsIDOMHTMLCollection* options = GetOptions();
if (!options)
return;
// get the css size
nsSize styleSize;
GetStyleSize(*aPresContext, aReflowState, styleSize);
// get the size of the longest option
PRInt32 maxWidth = 1;
PRUint32 numOptions;
options->GetLength(&numOptions);
for (PRUint32 i = 0; i < numOptions; i++) {
nsIDOMHTMLOptionElement* option = GetOption(*options, i);
if (option) {
//option->CompressContent();
nsAutoString text;
if (NS_CONTENT_ATTR_HAS_VALUE != option->GetText(text)) {
continue;
}
nsSize textSize;
// use the style for the select rather that the option, since widgets don't support it
nsFormControlHelper::GetTextSize(*aPresContext, this, text, textSize, aReflowState.rendContext);
if (textSize.width > maxWidth) {
maxWidth = textSize.width;
}
NS_RELEASE(option);
}
}
PRInt32 rowHeight = 0;
nsSize desiredSize;
nsSize minSize;
PRBool widthExplicit, heightExplicit;
nsInputDimensionSpec textSpec(nsnull, PR_FALSE, nsnull, nsnull,
maxWidth, PR_TRUE, nsHTMLAtoms::size, 1);
// XXX fix CalculateSize to return PRUint32
mNumRows =
(PRUint32)nsFormControlHelper::CalculateSize(aPresContext, aReflowState.rendContext, this, styleSize,
textSpec, desiredSize, minSize, widthExplicit,
heightExplicit, rowHeight);
// here it is determined whether we are a combo box
PRInt32 sizeAttr;
GetSizeFromContent(&sizeAttr);
PRBool multiple;
if (!GetMultiple(&multiple) &&
((1 >= sizeAttr) || ((ATTR_NOTSET == sizeAttr) && (1 >= mNumRows)))) {
mIsComboBox = PR_TRUE;
}
float sp2t;
float p2t;
aPresContext->GetPixelsToTwips(&p2t);
aPresContext->GetScaledPixelsToTwips(&sp2t);
nscoord scrollbarWidth = 0;
nscoord scrollbarHeight = 0;
float scale;
nsCOMPtr<nsIDeviceContext> dx;
aPresContext->GetDeviceContext(getter_AddRefs(dx));
if (dx) {
float sbWidth;
float sbHeight;
dx->GetCanonicalPixelScale(scale);
dx->GetScrollBarDimensions(sbWidth, sbHeight);
scrollbarWidth = PRInt32(sbWidth * scale);
scrollbarHeight = PRInt32(sbHeight * scale);
} else {
scrollbarWidth = GetScrollbarWidth(sp2t);
scrollbarHeight = scrollbarWidth;
}
aDesiredLayoutSize.width = desiredSize.width;
PRBool needScrollbar;
#ifdef XP_MAC
needScrollbar = PR_TRUE;
scrollbarWidth = (mIsComboBox)
? NSIntPixelsToTwips(25, p2t*scale)
: (scrollbarWidth + NSIntPixelsToTwips(2, p2t*scale));
#else
needScrollbar = ((mNumRows < numOptions) || mIsComboBox);
#endif
// account for vertical scrollbar, if present
if (!widthExplicit && needScrollbar) {
aDesiredLayoutSize.width += scrollbarWidth;
}
if (aDesiredLayoutSize.maxElementSize) {
aDesiredLayoutSize.maxElementSize->width = minSize.width;
if (!widthExplicit && needScrollbar) {
aDesiredLayoutSize.maxElementSize->width += scrollbarWidth;
}
}
// XXX put this in widget library, combo boxes are fixed height (visible part)
aDesiredLayoutSize.height = (mIsComboBox)
? rowHeight + (2 * GetVerticalInsidePadding(*aPresContext, p2t, rowHeight))
: desiredSize.height;
if (aDesiredLayoutSize.maxElementSize) {
aDesiredLayoutSize.maxElementSize->height = (mIsComboBox)
? rowHeight + (2 * GetVerticalInsidePadding(*aPresContext, p2t, rowHeight))
: minSize.height;
}
aDesiredLayoutSize.ascent = aDesiredLayoutSize.height;
aDesiredLayoutSize.descent = 0;
aDesiredWidgetSize.width = aDesiredLayoutSize.width;
aDesiredWidgetSize.height = aDesiredLayoutSize.height;
if (mIsComboBox) { // add in pull down size
PRInt32 extra = NSIntPixelsToTwips(10, p2t*scale);
aDesiredWidgetSize.height += (rowHeight * (numOptions > 20 ? 20 : numOptions)) + extra;
}
// override the width and height for a combo box that has already got a widget
if (mWidget && mIsComboBox) {
nscoord ignore;
GetWidgetSize(*aPresContext, ignore, aDesiredLayoutSize.height);
aDesiredLayoutSize.ascent = aDesiredLayoutSize.height;
if (aDesiredLayoutSize.maxElementSize) {
aDesiredLayoutSize.maxElementSize->height = aDesiredLayoutSize.height;
}
}
NS_RELEASE(options);
}
nsWidgetInitData*
nsSelectControlFrame::GetWidgetInitData(nsIPresContext& aPresContext)
{
if (mIsComboBox) {
nsComboBoxInitData* initData = new nsComboBoxInitData();
if (initData) {
initData->clipChildren = PR_TRUE;
float twipToPix;
aPresContext.GetTwipsToPixels(&twipToPix);
initData->mDropDownHeight = NSTwipsToIntPixels(mWidgetSize.height, twipToPix);
}
return initData;
} else {
PRBool multiple;
GetMultiple(&multiple);
nsListBoxInitData* initData = nsnull;
if (multiple) {
initData = new nsListBoxInitData();
if (initData) {
initData->clipChildren = PR_TRUE;
initData->mMultiSelect = PR_TRUE;
}
}
return initData;
}
}
NS_IMETHODIMP
nsSelectControlFrame::GetMultiple(PRBool* aMultiple, nsIDOMHTMLSelectElement* aSelect)
{
if (!aSelect) {
nsIDOMHTMLSelectElement* thisSelect = nsnull;
nsresult result = mContent->QueryInterface(kIDOMHTMLSelectElementIID, (void**)&thisSelect);
if ((NS_OK == result) && thisSelect) {
result = thisSelect->GetMultiple(aMultiple);
NS_RELEASE(thisSelect);
}
return result;
} else {
return aSelect->GetMultiple(aMultiple);
}
}
nsIDOMHTMLSelectElement*
nsSelectControlFrame::GetSelect()
{
nsIDOMHTMLSelectElement* thisSelect = nsnull;
nsresult result = mContent->QueryInterface(kIDOMHTMLSelectElementIID, (void**)&thisSelect);
if ((NS_OK == result) && thisSelect) {
return thisSelect;
} else {
return nsnull;
}
}
PRUint32
nsSelectControlFrame::GetSelectedIndex()
{
PRInt32 indx = -1; // No selection
nsIDOMHTMLSelectElement* thisSelect = nsnull;
if (NS_SUCCEEDED(mContent->QueryInterface(kIDOMHTMLSelectElementIID, (void**)&thisSelect))) {
thisSelect->GetSelectedIndex(&indx);
NS_RELEASE(thisSelect);
}
return(indx);
}
nsIDOMHTMLCollection*
nsSelectControlFrame::GetOptions(nsIDOMHTMLSelectElement* aSelect)
{
nsIDOMHTMLCollection* options = nsnull;
if (!aSelect) {
nsIDOMHTMLSelectElement* thisSelect = GetSelect();
if (thisSelect) {
thisSelect->GetOptions(&options);
NS_RELEASE(thisSelect);
return options;
} else {
return nsnull;
}
} else {
aSelect->GetOptions(&options);
return options;
}
}
void
nsSelectControlFrame::GetWidgetSize(nsIPresContext& aPresContext, nscoord& aWidth, nscoord& aHeight)
{
nsRect bounds;
mWidget->GetBounds(bounds);
float p2t;
aPresContext.GetPixelsToTwips(&p2t);
aWidth = NSIntPixelsToTwips(bounds.width, p2t);
aHeight = NSTwipsToIntPixels(bounds.height, p2t);
}
void
nsSelectControlFrame::PostCreateWidget(nsIPresContext* aPresContext,
nscoord& aWidth,
nscoord& aHeight)
{
if (!mWidget) {
return;
}
nsIListWidget* listWidget;
if (NS_OK != mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget)) {
NS_ASSERTION(PR_FALSE, "invalid widget");
return;
}
mWidget->Enable(!nsFormFrame::GetDisabled(this));
nsFont font(aPresContext->GetDefaultFixedFontDeprecated());
GetFont(aPresContext, font);
mWidget->SetFont(font);
SetColors(*aPresContext);
// add the options
if (!mOptionsAdded) {
nsIDOMHTMLCollection* options = GetOptions();
if (options) {
PRUint32 numOptions;
options->GetLength(&numOptions);
nsIDOMNode* node;
nsIDOMHTMLOptionElement* option;
// Initialize the locally cached numOptions and optionSelected array.
// The values of the optionsSelected array are filled in by Reset()
mNumOptions = numOptions;
if ((numOptions > 0) && (mOptionSelected == nsnull)) {
mOptionSelected = new PRBool[numOptions];
}
for (PRUint32 i = 0; i < numOptions; i++) {
options->Item(i, &node);
if (node) {
nsresult result = node->QueryInterface(kIDOMHTMLOptionElementIID, (void**)&option);
if ((NS_OK == result) && option) {
nsString text;
// XXX need to compress whitespace
if (NS_CONTENT_ATTR_HAS_VALUE != option->GetText(text)) {
text = " ";
}
listWidget->AddItemAt(text, i);
NS_RELEASE(option);
}
NS_RELEASE(node);
}
}
NS_RELEASE(options);
}
mOptionsAdded = PR_TRUE;
}
NS_RELEASE(listWidget);
// get the size of the combo box and let Reflow change its desired size
// XXX this technique should be considered for other widgets as well
if (mIsComboBox) {
nscoord ignore;
nscoord height;
GetWidgetSize(*aPresContext, ignore, height);
if (height > aHeight) {
aHeight = height;
}
}
Reset(); // initializes selections
}
PRInt32
nsSelectControlFrame::GetMaxNumValues()
{
PRBool multiple;
GetMultiple(&multiple);
if (multiple) {
PRUint32 length = 0;
nsIDOMHTMLCollection* options = GetOptions();
if (options) {
options->GetLength(&length);
NS_RELEASE(options);
}
return (PRInt32)length; // XXX fix return on GetMaxNumValues
} else {
return 1;
}
}
PRBool
nsSelectControlFrame::GetNamesValues(PRInt32 aMaxNumValues, PRInt32& aNumValues,
nsString* aValues, nsString* aNames)
{
aNumValues = 0;
nsAutoString name;
nsresult result = GetName(&name);
if ((aMaxNumValues <= 0) || (NS_CONTENT_ATTR_NOT_THERE == result)) {
return PR_FALSE;
}
nsIDOMHTMLCollection* options = GetOptions();
if (!options) {
return PR_FALSE;
}
PRBool status = PR_FALSE;
PRBool multiple;
GetMultiple(&multiple);
if (!multiple) {
nsIListWidget* listWidget;
result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && listWidget) {
PRInt32 indx = listWidget->GetSelectedIndex();
NS_RELEASE(listWidget);
if (indx >= 0) {
nsAutoString value;
GetOptionValue(*options, indx, value);
aNumValues = 1;
aNames[0] = name;
aValues[0] = value;
status = PR_TRUE;
}
}
} else {
nsIListBox* listBox;
result = mWidget->QueryInterface(kListBoxIID, (void **) &listBox);
if ((NS_OK == result) && listBox) {
PRInt32 numSelections = listBox->GetSelectedCount();
NS_ASSERTION(aMaxNumValues >= numSelections, "invalid max num values");
if (numSelections >= 0) {
PRInt32* selections = new PRInt32[numSelections];
listBox->GetSelectedIndices(selections, numSelections);
aNumValues = 0;
for (int i = 0; i < numSelections; i++) {
nsAutoString value;
GetOptionValue(*options, selections[i], value);
aNames[i] = name;
aValues[i] = value;
aNumValues++;
}
delete[] selections;
status = PR_TRUE;
}
NS_RELEASE(listBox);
}
}
NS_RELEASE(options);
return status;
}
void
nsSelectControlFrame::Reset()
{
nsIDOMHTMLCollection* options = GetOptions();
if (!options) {
return;
}
PRBool multiple;
GetMultiple(&multiple);
PRUint32 numOptions;
options->GetLength(&numOptions);
PRInt32 selectedIndex = -1;
nsIListWidget* listWidget;
nsresult result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && listWidget) {
listWidget->Deselect();
for (PRUint32 i = 0; i < numOptions; i++) {
nsIDOMHTMLOptionElement* option = GetOption(*options, i);
if (option) {
PRBool selected = PR_FALSE;
// Cache the state of each option locally
option->GetDefaultSelected(&selected);
SetOptionSelected(i, selected);
if (selected) {
listWidget->SelectItem(i);
if (selectedIndex < 0)
selectedIndex = i;
}
NS_RELEASE(option);
}
}
}
// if none were selected, select 1st one if we are a combo box
if (mIsComboBox && (numOptions > 0) && (selectedIndex < 0)) {
listWidget->SelectItem(0);
SetOptionSelected(0, PR_TRUE);
}
NS_RELEASE(listWidget);
NS_RELEASE(options);
}
/* XXX add this to nsHTMLOptionElement.cpp
void
nsOption::CompressContent()
{
if (nsnull != mContent) {
mContent->CompressWhitespace(PR_TRUE, PR_TRUE);
}
}*/
nsIDOMHTMLOptionElement*
nsSelectControlFrame::GetOption(nsIDOMHTMLCollection& aCollection, PRUint32 aIndex)
{
nsIDOMNode* node = nsnull;
if ((NS_OK == aCollection.Item(aIndex, &node)) && node) {
nsIDOMHTMLOptionElement* option = nsnull;
node->QueryInterface(kIDOMHTMLOptionElementIID, (void**)&option);
NS_RELEASE(node);
return option;
}
return nsnull;
}
PRBool
nsSelectControlFrame::GetOptionValue(nsIDOMHTMLCollection& aCollection, PRUint32 aIndex, nsString& aValue)
{
PRBool status = PR_FALSE;
nsIDOMHTMLOptionElement* option = GetOption(aCollection, aIndex);
if (option) {
nsIHTMLContent* content = nsnull;
nsresult result = option->QueryInterface(kIHTMLContentIID, (void **)&content);
if (NS_SUCCEEDED(result) && content) {
nsHTMLValue value(aValue);
result = content->GetHTMLAttribute(nsHTMLAtoms::value, value);
if (NS_CONTENT_ATTR_HAS_VALUE == result) {
value.GetStringValue(aValue);
status = PR_TRUE;
}
NS_RELEASE(content);
}
if (!status) {
result = option->GetText(aValue);
if (aValue.Length() > 0) {
status = PR_TRUE;
}
}
NS_RELEASE(option);
}
return status;
}
void
nsSelectControlFrame::PaintOption(PRBool aPaintSelected, nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext, nsString aText, nscoord aX, nscoord aY,
const nsRect& aInside,
nscoord aTextHeight)
{
nscolor foreground = NS_RGB(0, 0, 0);
nscolor background = NS_RGB(255, 255, 255);
if (PR_TRUE == aPaintSelected) {
background = NS_RGB(0, 0, 0);
foreground = NS_RGB(255, 255, 255);
}
float p2t;
aPresContext.GetScaledPixelsToTwips(&p2t);
nscoord onePixel = NSIntPixelsToTwips(1, p2t);
nsRect rect(aInside.x, aY-onePixel, mRect.width-onePixel, aTextHeight+onePixel);
nscolor currentColor;
aRenderingContext.GetColor(currentColor);
aRenderingContext.SetColor(background);
aRenderingContext.FillRect(rect);
aRenderingContext.SetColor(foreground);
aRenderingContext.DrawString(aText, aX, aY);
aRenderingContext.SetColor(currentColor);
}
void
nsSelectControlFrame::PaintSelectControl(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
aRenderingContext.PushState();
/**
* Resolve style for a pseudo frame within the given aParentContent & aParentContext.
* The tag should be lowercase and inclue the colon.
* ie: NS_NewAtom(":first-line");
*/
nsIAtom * sbAtom = NS_NewAtom(":scrollbar-look");
nsIStyleContext* scrollbarStyle;
aPresContext.ResolvePseudoStyleContextFor(mContent, sbAtom, mStyleContext,
PR_FALSE, &scrollbarStyle);
NS_RELEASE(sbAtom);
sbAtom = NS_NewAtom(":scrollbar-arrow-look");
nsIStyleContext* arrowStyle;
aPresContext.ResolvePseudoStyleContextFor(mContent, sbAtom, mStyleContext,
PR_FALSE, &arrowStyle);
NS_RELEASE(sbAtom);
nsIDOMHTMLCollection* options = GetOptions();
if (!options) {
return;
}
PRUint32 numOptions;
options->GetLength(&numOptions);
float scale;
nsIDeviceContext * context;
aRenderingContext.GetDeviceContext(context);
context->GetCanonicalPixelScale(scale);
const nsStyleSpacing* spacing =
(const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing);
nsMargin border;
spacing->CalcBorderFor(this, border);
float p2t;
aPresContext.GetScaledPixelsToTwips(&p2t);
nscoord onePixel = NSIntPixelsToTwips(1, p2t);
nsRect outside(0, 0, mRect.width, mRect.height);
outside.Deflate(border);
outside.Deflate(onePixel, onePixel);
nsRect inside(outside);
aRenderingContext.SetColor(NS_RGB(0,0,0));
nsFont font(aPresContext.GetDefaultFixedFontDeprecated());
GetFont(&aPresContext, font);
aRenderingContext.SetFont(font);
//nscoord textWidth;
nscoord textHeight;
nsString text;
// Calculate the height of the text
nsIFontMetrics* metrics;
context->GetMetricsFor(font, metrics);
metrics->GetHeight(textHeight);
// Calculate the width of the scrollbar
PRInt32 scrollbarWidth;
if (numOptions > mNumRows) {
float sbWidth;
float sbHeight;
context->GetCanonicalPixelScale(scale);
context->GetScrollBarDimensions(sbWidth, sbHeight);
scrollbarWidth = PRInt32(sbWidth * scale);
} else {
scrollbarWidth = 0;
}
// shrink the inside rect's width for the scrollbar
inside.width -= scrollbarWidth;
PRBool clipEmpty;
aRenderingContext.PushState();
nsRect clipRect(inside);
clipRect.Inflate(onePixel, onePixel);
aRenderingContext.SetClipRect(clipRect, nsClipCombine_kReplace, clipEmpty);
nscoord x = inside.x + onePixel;
nscoord y;
if (mIsComboBox) {
y = ((inside.height - textHeight) / 2) + inside.y;
} else {
y = inside.y;
}
// Get Selected index out of Content model
PRInt32 selectedIndex = GetSelectedIndex();
PRBool multiple = PR_FALSE;
GetMultiple(&multiple);
nsIDOMNode* node;
nsIDOMHTMLOptionElement* option;
for (PRUint32 i = 0; i < numOptions; i++) {
options->Item(i, &node);
if (node) {
nsresult result = node->QueryInterface(kIDOMHTMLOptionElementIID, (void**)&option);
if ((NS_OK == result) && option) {
// XXX need to compress whitespace
if (NS_CONTENT_ATTR_HAS_VALUE != option->GetText(text)) {
text = " ";
}
if (mIsComboBox) {
// Paint a non-selected option
if ((selectedIndex == -1) && (i == 0)) {
PaintOption(PR_FALSE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
}
else if ((PRUint32)selectedIndex == i) {
PaintOption(PR_FALSE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
}
if ((-1 == selectedIndex && (0 == i)) || ((PRUint32)selectedIndex == i)) {
i = numOptions;
}
} else {
// Its a list box
if (!multiple) {
// Single select list box
if (selectedIndex == i)
PaintOption(PR_TRUE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
else
PaintOption(PR_FALSE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
}
else {
PRBool selected = PR_FALSE;
option->GetSelected(&selected);
// Multi-selection list box
if (selected)
PaintOption(PR_TRUE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
else
PaintOption(PR_FALSE, aPresContext, aRenderingContext, text, x, y, inside, textHeight);
}
y += textHeight;
if (i == mNumRows-1) {
i = numOptions;
}
}
}
NS_RELEASE(option);
}
NS_RELEASE(node);
}
aRenderingContext.PopState(clipEmpty);
// Draw Scrollbars
if (numOptions > mNumRows) {
//const nsStyleColor* myColor =
// (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color);
if (mIsComboBox) {
// Get the Scrollbar's Arrow's Style structs
const nsStyleSpacing* arrowSpacing = (const nsStyleSpacing*)arrowStyle->GetStyleData(eStyleStruct_Spacing);
//XXX const nsStyleColor* arrowColor = (const nsStyleColor*)arrowStyle->GetStyleData(eStyleStruct_Color);
nsRect srect(mRect.width-scrollbarWidth-onePixel, onePixel, scrollbarWidth, mRect.height-(onePixel*2));
nsFormControlHelper::PaintArrow(nsFormControlHelper::eArrowDirection_Down, aRenderingContext,aPresContext,
aDirtyRect, srect, onePixel, arrowStyle, *arrowSpacing, this, mRect);
} else {
nsRect srect(mRect.width-scrollbarWidth-onePixel, onePixel, scrollbarWidth, mRect.height-(onePixel*2));
nsFormControlHelper::PaintScrollbar(aRenderingContext,aPresContext, aDirtyRect, srect, PR_FALSE, onePixel,
scrollbarStyle, arrowStyle, this, mRect);
}
}
NS_RELEASE(context);
NS_RELEASE(options);
aRenderingContext.PopState(clipEmpty);
NS_RELEASE(scrollbarStyle);
NS_RELEASE(arrowStyle);
}
NS_METHOD
nsSelectControlFrame::Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
nsFormControlFrame::Paint(aPresContext, aRenderingContext, aDirtyRect,
aWhichLayer);
if (NS_FRAME_PAINT_LAYER_FOREGROUND == aWhichLayer) {
PaintSelectControl(aPresContext, aRenderingContext, aDirtyRect);
}
return NS_OK;
}
// Forward this on as a control changed event - this enables onChange for
// list boxes as ControlChanged is currently only sent for combos
void
nsSelectControlFrame::MouseClicked(nsIPresContext* aPresContext)
{
ControlChanged(aPresContext);
}
// Update the locally cached selection array.
// If different option(s) are selected, send a DOM onChange event.
void
nsSelectControlFrame::ControlChanged(nsIPresContext* aPresContext)
{
if (!nsFormFrame::GetDisabled(this)) {
PRBool changed = PR_FALSE;
PRBool multiple;
GetMultiple(&multiple);
if (!multiple) {
nsIListWidget* listWidget;
nsresult result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && listWidget) {
PRInt32 viewIndex = listWidget->GetSelectedIndex();
NS_RELEASE(listWidget);
PRBool wasSelected = PR_FALSE;
GetOptionSelected(viewIndex, &wasSelected);
if (wasSelected == PR_FALSE) {
changed = PR_TRUE;
}
// Update the locally cached array
for (PRUint32 i=0; i < mNumOptions; i++)
SetOptionSelected(i, PR_FALSE);
SetOptionSelected(viewIndex, PR_TRUE);
}
} else {
// Get the selected option from the widget
nsIListBox* listBox;
nsresult result = mWidget->QueryInterface(kListBoxIID, (void **) &listBox);
if (!(NS_OK == result) || !listBox) {
return;
}
PRUint32 numSelected = listBox->GetSelectedCount();
PRInt32* selOptions = nsnull;
if (numSelected >= 0) {
selOptions = new PRInt32[numSelected];
listBox->GetSelectedIndices(selOptions, numSelected);
}
NS_RELEASE(listBox);
// XXX Assume options are sorted in selOptions
PRUint32 selIndex = 0;
PRUint32 nextSel = 0;
if ((nsnull != selOptions) && (numSelected > 0)) {
nextSel = selOptions[selIndex];
}
// Step through each option in local cache
for (PRUint32 i=0; i < mNumOptions; i++) {
PRBool selectedInLocalCache = PR_FALSE;
PRBool selectedInView = (i == nextSel);
GetOptionSelected(i, &selectedInLocalCache);
if (selectedInView != selectedInLocalCache) {
changed = PR_TRUE;
SetOptionSelected(i, selectedInView);
if (selectedInView) {
// Get the next selected option in the view
selIndex++;
if (selIndex < numSelected) {
nextSel = selOptions[selIndex];
}
}
}
}
delete[] selOptions;
}
if (changed) {
nsEventStatus status = nsEventStatus_eIgnore;
nsEvent event;
event.eventStructType = NS_EVENT;
event.message = NS_FORM_CHANGE;
if (nsnull != mContent) {
mContent->HandleDOMEvent(*aPresContext, &event, nsnull, NS_EVENT_FLAG_INIT, status);
}
}
}
}
// Get the selected state from the local cache (not widget)
void nsSelectControlFrame::GetOptionSelected(PRUint32 indx, PRBool* aValue)
{
if (nsnull != mOptionSelected) {
if (mNumOptions >= indx) {
*aValue = mOptionSelected[indx];
return;
}
}
*aValue = PR_FALSE;
}
// Get the selected state from the widget
void nsSelectControlFrame::GetOptionSelectedFromWidget(PRInt32 indx, PRBool* aValue)
{
*aValue = PR_FALSE;
nsresult result;
PRBool multiple;
GetMultiple(&multiple);
if (!multiple) {
nsIListWidget* listWidget;
result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && (nsnull != listWidget)) {
PRInt32 selIndex = listWidget->GetSelectedIndex();
NS_RELEASE(listWidget);
if (selIndex == indx) {
*aValue = PR_TRUE;
}
}
}
else {
nsIListBox* listBox;
result = mWidget->QueryInterface(kListBoxIID, (void **) &listBox);
if ((NS_OK == result) && (nsnull != listBox)) {
PRUint32 numSelected = listBox->GetSelectedCount();
PRInt32* selOptions = nsnull;
if (numSelected > 0) {
// Could we set numSelected to 1 here? (memory, speed optimization)
selOptions = new PRInt32[numSelected];
listBox->GetSelectedIndices(selOptions, numSelected);
PRUint32 i;
for (i = 0; i < numSelected; i++) {
if (selOptions[i] == indx) {
*aValue = PR_TRUE;
break;
}
}
delete[] selOptions;
}
NS_RELEASE(listBox);
}
}
}
// Update the locally cached selection state (not widget)
void nsSelectControlFrame::SetOptionSelected(PRUint32 indx, PRBool aValue)
{
if (nsnull != mOptionSelected) {
if (mNumOptions >= indx) {
mOptionSelected[indx] = aValue;
}
}
}
NS_IMETHODIMP
nsSelectControlFrame::AttributeChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
nsIAtom* aAttribute,
PRInt32 aHint)
{
if (nsnull != mWidget) {
if (nsHTMLAtoms::disabled == aAttribute) {
mWidget->Enable(!nsFormFrame::GetDisabled(this));
}
}
return NS_OK;
}
NS_IMETHODIMP nsSelectControlFrame::SetProperty(nsIAtom* aName, const nsString& aValue)
{
if (nsHTMLAtoms::selected == aName) {
return NS_ERROR_INVALID_ARG; // Selected is readonly according to spec.
} else if (nsHTMLAtoms::selectedindex == aName) {
PRInt32 error = 0;
PRInt32 selectedIndex = aValue.ToInteger(&error, 10); // Get index from aValue
if (error)
return NS_ERROR_INVALID_ARG; // Couldn't convert to integer
// Update local cache of selected values
for (PRUint32 i=0; i < mNumOptions; i++)
SetOptionSelected(i, PR_FALSE);
SetOptionSelected(selectedIndex, PR_TRUE);
// Update widget
nsIListWidget* listWidget;
nsresult result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && (nsnull != listWidget)) {
listWidget->Deselect();
listWidget->SelectItem(selectedIndex);
NS_RELEASE(listWidget);
}
} else if (nsHTMLAtoms::option == aName) { // Add or Remove an option
nsString aValCopy(aValue);
aValCopy.Trim("ar",PR_TRUE,PR_FALSE); // Chop off leading a or r
PRInt32 error = 0;
PRUint32 actionIndex = aValCopy.ToInteger(&error, 10);
if (error) return NS_ERROR_INVALID_ARG; // Couldn't convert to integer
// Grab the content model information (merge with postcreatewidget code?)
nsIDOMHTMLCollection* options = GetOptions();
if (!options) return NS_ERROR_UNEXPECTED;
// Save the cache data structure
PRUint32 saveNumOptions = mNumOptions;
PRBool* saveOptionSelected = mOptionSelected;
PRBool selected = PR_FALSE;
nsString text(" ");
// Grab the widget (we need to update it)
nsIListWidget* listWidget;
nsresult result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_FAILED(result)) || (nsnull == listWidget)) {
return NS_ERROR_UNEXPECTED;
}
if (!aValue.Find("a")) { // First character is "a" = add an option
mNumOptions++;
mOptionSelected = new PRBool[mNumOptions];
if (!mOptionSelected) return NS_ERROR_OUT_OF_MEMORY;
// Copy saved local cache into new local cache
for (PRUint32 i=0, j=0; j < mNumOptions; i++,j++) {
if (i == actionIndex) { // At the point of insertion
// Get the correct selected value and text of the option
nsIDOMNode* node = nsnull;
options->Item(i, &node);
if (node) {
nsIDOMHTMLOptionElement* option = nsnull;
result = node->QueryInterface(kIDOMHTMLOptionElementIID, (void**)&option);
if ((NS_OK == result) && option) {
option->GetDefaultSelected(&selected);
mOptionSelected[j]=selected;
// XXX need to compress whitespace
if (NS_CONTENT_ATTR_HAS_VALUE != option->GetText(text)) {
text = " "; // needed?
}
NS_RELEASE(option);
} else {
mOptionSelected[j]=PR_FALSE; // Couldn't get selected val from content!
}
NS_RELEASE(node);
} else {
mOptionSelected[j]=PR_FALSE; // Couldn't get selected val from content!
}
j++;
}
// Copy old selected value
if (j < mNumOptions) {
mOptionSelected[j]=saveOptionSelected[i];
}
}
// Update widget
listWidget->AddItemAt(text, actionIndex);
} else {
mNumOptions--;
if (mNumOptions) {
mOptionSelected = new PRBool[mNumOptions];
if (!mOptionSelected) return NS_ERROR_OUT_OF_MEMORY;
} else {
mOptionSelected = nsnull;
}
// If we got the index, remove just that one option, like this:
if (actionIndex >= 0) {
// Copy saved local cache into new local cache
for (PRUint32 i=0, j=0; j < mNumOptions; i++,j++) {
if (i == (PRUint32)actionIndex) i++; // At the point of insertion
mOptionSelected[j]=saveOptionSelected[i];
}
// Update widget
listWidget->RemoveItemAt(actionIndex);
// No index, so we'll have to remove all options and recreate. (Performance hit)
// We also loose the status of what has been selected.
} else {
// Remove all stale options
PRUint32 i;
for (i=saveNumOptions; i>=0; i--) {
listWidget->RemoveItemAt(i);
}
// Add all the options back in
for (i=0; i<mNumOptions; i++) {
// Get the default (XXXincorrect) selected value and text of the option
nsIDOMNode* node = nsnull;
options->Item(i, &node);
if (node) {
nsIDOMHTMLOptionElement* option = nsnull;
result = node->QueryInterface(kIDOMHTMLOptionElementIID, (void**)&option);
if ((NS_OK == result) && option) {
option->GetDefaultSelected(&selected); // Should be sel, not defsel :(
mOptionSelected[i]=selected;
// XXX need to compress whitespace
if (NS_CONTENT_ATTR_HAS_VALUE != option->GetText(text)) {
text = " "; // needed?
}
NS_RELEASE(option);
} else {
mOptionSelected[i]=PR_FALSE; // Couldn't get selected val from content!
text = " ";
}
NS_RELEASE(node);
} else {
mOptionSelected[i]=PR_FALSE; // Couldn't get selected val from content!
text = " ";
}
listWidget->AddItemAt(text, i);
}
}
}
// Select options as needed (this is needed for Windows at least)
// Note, as mentioned above, we can't restore selection if option is
// replaced - no index is reported back to us - use DefaultSelected instead
listWidget->Deselect();
PRInt32 selectedIndex = -1;
for (PRUint32 i=0; i < mNumOptions; i++) {
GetOptionSelected(i, &selected);
if (selected) {
listWidget->SelectItem(i);
selectedIndex = i;
}
}
if (mIsComboBox && (mNumOptions > 0) && (selectedIndex == -1)) {
listWidget->SelectItem(0);
SetOptionSelected(0, PR_TRUE);
}
NS_RELEASE(options);
NS_RELEASE(listWidget);
if (saveOptionSelected)
delete [] saveOptionSelected;
} else {
return nsFormControlFrame::SetProperty(aName, aValue);
}
return NS_OK;
}
NS_IMETHODIMP nsSelectControlFrame::GetProperty(nsIAtom* aName, nsString& aValue)
{
// Get the selected value of option from local cache (optimization vs. widget)
if (nsHTMLAtoms::selected == aName) {
PRInt32 error = 0;
PRBool selected = PR_FALSE;
PRInt32 indx = aValue.ToInteger(&error, 10); // Get index from aValue
if (error == 0)
GetOptionSelectedFromWidget(indx, &selected);
nsFormControlHelper::GetBoolString(selected, aValue);
// For selectedIndex, get the value from the widget
} else if (nsHTMLAtoms::selectedindex == aName) {
PRInt32 selectedIndex = -1;
PRBool multiple;
GetMultiple(&multiple);
if (!multiple) {
nsIListWidget* listWidget;
nsresult result = mWidget->QueryInterface(kListWidgetIID, (void **) &listWidget);
if ((NS_OK == result) && (nsnull != listWidget)) {
selectedIndex = listWidget->GetSelectedIndex();
NS_RELEASE(listWidget);
}
} else {
// Listboxes don't do GetSelectedIndex on windows. Use GetSelectedIndices
nsIListBox* listBox;
nsresult result = mWidget->QueryInterface(kListBoxIID, (void **) &listBox);
if ((NS_OK == result) && (nsnull != listBox)) {
PRUint32 numSelected = listBox->GetSelectedCount();
PRInt32* selOptions = nsnull;
if (numSelected > 0) {
// Could we set numSelected to 1 here? (memory, speed optimization)
selOptions = new PRInt32[numSelected];
listBox->GetSelectedIndices(selOptions, numSelected);
selectedIndex = selOptions[0];
delete[] selOptions;
}
NS_RELEASE(listBox);
}
}
aValue.Append(selectedIndex, 10);
} else {
return nsFormControlFrame::GetProperty(aName, aValue);
}
return NS_OK;
}