From b119ed934573f036f645389bac378b97d87f2d1c Mon Sep 17 00:00:00 2001 From: Michael Ventnor Date: Thu, 22 Jan 2009 13:07:44 +1300 Subject: [PATCH] Bug 123836 - Implement indeterminate property on checkboxes and radio buttons - r=roc,jst sr=roc --- .../html/content/src/nsHTMLInputElement.cpp | 34 ++++++++++++++++++- .../idl/html/nsIDOMNSHTMLInputElement.idl | 4 ++- layout/forms/nsGfxCheckboxControlFrame.cpp | 31 +++++++++++++++-- layout/forms/nsGfxCheckboxControlFrame.h | 3 +- .../forms/indeterminate-checked-notref.html | 1 + .../reftests/forms/indeterminate-checked.html | 1 + .../forms/indeterminate-unchecked-notref.html | 1 + .../forms/indeterminate-unchecked.html | 1 + layout/reftests/forms/reftest.list | 2 ++ widget/src/gtk2/gtk2drawing.c | 18 ++++++++-- widget/src/gtk2/gtkdrawing.h | 4 +++ widget/src/gtk2/nsNativeThemeGTK.cpp | 15 ++++++-- 12 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 layout/reftests/forms/indeterminate-checked-notref.html create mode 100644 layout/reftests/forms/indeterminate-checked.html create mode 100644 layout/reftests/forms/indeterminate-unchecked-notref.html create mode 100644 layout/reftests/forms/indeterminate-unchecked.html diff --git a/content/html/content/src/nsHTMLInputElement.cpp b/content/html/content/src/nsHTMLInputElement.cpp index fcfb24c4b7ff..a70d92467808 100644 --- a/content/html/content/src/nsHTMLInputElement.cpp +++ b/content/html/content/src/nsHTMLInputElement.cpp @@ -129,6 +129,7 @@ static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); #define BF_PARSER_CREATING 7 #define BF_IN_INTERNAL_ACTIVATE 8 #define BF_CHECKED_IS_TOGGLED 9 +#define BF_INDETERMINATE 10 #define GET_BOOLBIT(bitfield, field) (((bitfield) & (0x01 << (field))) \ ? PR_TRUE : PR_FALSE) @@ -140,8 +141,10 @@ static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); #define NS_OUTER_ACTIVATE_EVENT (1 << 9) #define NS_ORIGINAL_CHECKED_VALUE (1 << 10) #define NS_NO_CONTENT_DISPATCH (1 << 11) +#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12) #define NS_CONTROL_TYPE(bits) ((bits) & ~( \ - NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH)) + NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \ + NS_ORIGINAL_INDETERMINATE_VALUE)) static const char kWhitespace[] = "\n\r\t\b"; @@ -753,6 +756,26 @@ nsHTMLInputElement::SetDefaultValue(const nsAString& aValue) return SetAttrHelper(nsGkAtoms::value, aValue); } +NS_IMETHODIMP +nsHTMLInputElement::GetIndeterminate(PRBool* aValue) +{ + *aValue = GET_BOOLBIT(mBitField, BF_INDETERMINATE); + return NS_OK; +} + +NS_IMETHODIMP +nsHTMLInputElement::SetIndeterminate(PRBool aValue) +{ + SET_BOOLBIT(mBitField, BF_INDETERMINATE, aValue); + + // Repaint the frame + nsIFrame* frame = GetPrimaryFrame(); + if (frame) + frame->InvalidateOverflowRect(); + + return NS_OK; +} + NS_IMETHODIMP nsHTMLInputElement::GetSize(PRUint32* aValue) { @@ -1529,6 +1552,12 @@ nsHTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor) switch(mType) { case NS_FORM_INPUT_CHECKBOX: { + if (GET_BOOLBIT(mBitField, BF_INDETERMINATE)) { + // indeterminate is always set to FALSE when the checkbox is toggled + SET_BOOLBIT(mBitField, BF_INDETERMINATE, PR_FALSE); + aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE; + } + GetChecked(&originalCheckedValue); DoSetChecked(!originalCheckedValue); SET_BOOLBIT(mBitField, BF_CHECKED_IS_TOGGLED, PR_TRUE); @@ -1679,6 +1708,9 @@ nsHTMLInputElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor) DoSetChecked(PR_FALSE); } } else if (oldType == NS_FORM_INPUT_CHECKBOX) { + PRBool originalIndeterminateValue = + !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE); + SET_BOOLBIT(mBitField, BF_INDETERMINATE, originalIndeterminateValue); DoSetChecked(originalCheckedValue); } } else { diff --git a/dom/public/idl/html/nsIDOMNSHTMLInputElement.idl b/dom/public/idl/html/nsIDOMNSHTMLInputElement.idl index 6f1266c8a31a..4e63aab7e557 100644 --- a/dom/public/idl/html/nsIDOMNSHTMLInputElement.idl +++ b/dom/public/idl/html/nsIDOMNSHTMLInputElement.idl @@ -42,7 +42,7 @@ interface nsIControllers; interface nsIDOMFileList; -[scriptable, uuid(df3dc133-d77a-482f-8364-8e40df978a33)] +[scriptable, uuid(71e9ecc0-f2d0-422c-8601-430e7f17fa47)] interface nsIDOMNSHTMLInputElement : nsISupports { readonly attribute nsIControllers controllers; @@ -54,6 +54,8 @@ interface nsIDOMNSHTMLInputElement : nsISupports readonly attribute nsIDOMFileList files; + attribute boolean indeterminate; + /* convenience */ void setSelectionRange(in long selectionStart, in long selectionEnd); diff --git a/layout/forms/nsGfxCheckboxControlFrame.cpp b/layout/forms/nsGfxCheckboxControlFrame.cpp index 5b9fc5cde57a..43cd75daa273 100644 --- a/layout/forms/nsGfxCheckboxControlFrame.cpp +++ b/layout/forms/nsGfxCheckboxControlFrame.cpp @@ -46,6 +46,7 @@ #include "nsIDOMHTMLInputElement.h" #include "nsDisplayList.h" #include "nsCSSAnonBoxes.h" +#include "nsIDOMNSHTMLInputElement.h" static void PaintCheckMark(nsIRenderingContext& aRenderingContext, @@ -74,6 +75,18 @@ PaintCheckMark(nsIRenderingContext& aRenderingContext, aRenderingContext.FillPolygon(paintPolygon, checkNumPoints); } +static void +PaintIndeterminateMark(nsIRenderingContext& aRenderingContext, + const nsRect& aRect) +{ + // Drawing a thin horizontal line in the middle of the rect. + nsRect fillRect = aRect; + fillRect.height /= 4; + fillRect.y += (aRect.height - fillRect.height) / 2; + + aRenderingContext.FillRect(fillRect); +} + //------------------------------------------------------------ nsIFrame* NS_NewGfxCheckboxControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) @@ -209,7 +222,10 @@ nsGfxCheckboxControlFrame::PaintCheckBox(nsIRenderingContext& aRenderingContext, const nsStyleColor* color = GetStyleColor(); aRenderingContext.SetColor(color->mColor); - PaintCheckMark(aRenderingContext, checkRect); + if (IsIndeterminate()) + PaintIndeterminateMark(aRenderingContext, checkRect); + else + PaintCheckMark(aRenderingContext, checkRect); } //------------------------------------------------------------ @@ -222,7 +238,7 @@ nsGfxCheckboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, NS_ENSURE_SUCCESS(rv, rv); // Get current checked state through content model. - if (!GetCheckboxState() || !IsVisibleForPainting(aBuilder)) + if ((!IsChecked() && !IsIndeterminate()) || !IsVisibleForPainting(aBuilder)) return NS_OK; // we're not checked or not visible, nothing to paint. if (IsThemed()) @@ -271,10 +287,19 @@ nsGfxCheckboxControlFrame::PaintCheckBoxFromStyle( //------------------------------------------------------------ PRBool -nsGfxCheckboxControlFrame::GetCheckboxState ( ) +nsGfxCheckboxControlFrame::IsChecked() { nsCOMPtr elem(do_QueryInterface(mContent)); PRBool retval = PR_FALSE; elem->GetChecked(&retval); return retval; } + +PRBool +nsGfxCheckboxControlFrame::IsIndeterminate() +{ + nsCOMPtr elem(do_QueryInterface(mContent)); + PRBool retval = PR_FALSE; + elem->GetIndeterminate(&retval); + return retval; +} diff --git a/layout/forms/nsGfxCheckboxControlFrame.h b/layout/forms/nsGfxCheckboxControlFrame.h index b9eb1a678bf0..fc89d04df94c 100644 --- a/layout/forms/nsGfxCheckboxControlFrame.h +++ b/layout/forms/nsGfxCheckboxControlFrame.h @@ -91,7 +91,8 @@ public: protected: - PRBool GetCheckboxState(); + PRBool IsChecked(); + PRBool IsIndeterminate(); nsRefPtr mCheckButtonFaceStyle; }; diff --git a/layout/reftests/forms/indeterminate-checked-notref.html b/layout/reftests/forms/indeterminate-checked-notref.html new file mode 100644 index 000000000000..37ea17655064 --- /dev/null +++ b/layout/reftests/forms/indeterminate-checked-notref.html @@ -0,0 +1 @@ + diff --git a/layout/reftests/forms/indeterminate-checked.html b/layout/reftests/forms/indeterminate-checked.html new file mode 100644 index 000000000000..7cecf09a0486 --- /dev/null +++ b/layout/reftests/forms/indeterminate-checked.html @@ -0,0 +1 @@ + diff --git a/layout/reftests/forms/indeterminate-unchecked-notref.html b/layout/reftests/forms/indeterminate-unchecked-notref.html new file mode 100644 index 000000000000..1055cbef1484 --- /dev/null +++ b/layout/reftests/forms/indeterminate-unchecked-notref.html @@ -0,0 +1 @@ + diff --git a/layout/reftests/forms/indeterminate-unchecked.html b/layout/reftests/forms/indeterminate-unchecked.html new file mode 100644 index 000000000000..56a8d0955a61 --- /dev/null +++ b/layout/reftests/forms/indeterminate-unchecked.html @@ -0,0 +1 @@ + diff --git a/layout/reftests/forms/reftest.list b/layout/reftests/forms/reftest.list index 9c798697d857..15ef7400957f 100644 --- a/layout/reftests/forms/reftest.list +++ b/layout/reftests/forms/reftest.list @@ -5,3 +5,5 @@ == input-text-size-2.html input-text-size-2-ref.html == radio-label-dynamic.html radio-label-dynamic-ref.html == out-of-bounds-selectedindex.html out-of-bounds-selectedindex-ref.html # bug 471741 +!= indeterminate-checked.html indeterminate-checked-notref.html +!= indeterminate-unchecked.html indeterminate-unchecked-notref.html diff --git a/widget/src/gtk2/gtk2drawing.c b/widget/src/gtk2/gtk2drawing.c index 721e8ea4dfe4..e6bb4f52fbe4 100644 --- a/widget/src/gtk2/gtk2drawing.c +++ b/widget/src/gtk2/gtk2drawing.c @@ -964,8 +964,8 @@ moz_gtk_button_get_inner_border(GtkWidget* widget, GtkBorder* inner_border) static gint moz_gtk_toggle_paint(GdkDrawable* drawable, GdkRectangle* rect, GdkRectangle* cliprect, GtkWidgetState* state, - gboolean selected, gboolean isradio, - GtkTextDirection direction) + gboolean selected, gboolean inconsistent, + gboolean isradio, GtkTextDirection direction) { GtkStateType state_type = ConvertGtkState(state); GtkShadowType shadow_type = (selected)?GTK_SHADOW_IN:GTK_SHADOW_OUT; @@ -1017,6 +1017,17 @@ moz_gtk_toggle_paint(GdkDrawable* drawable, GdkRectangle* rect, } } else { + /* + * 'indeterminate' type on checkboxes. In GTK, the shadow type + * must also be changed for the state to be drawn. + */ + if (inconsistent) { + gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), TRUE); + shadow_type = GTK_SHADOW_ETCHED_IN; + } else { + gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(gCheckboxWidget), FALSE); + } + gtk_paint_check(style, drawable, state_type, shadow_type, cliprect, gCheckboxWidget, "checkbutton", x, y, width, height); if (state->focused) { @@ -3042,7 +3053,8 @@ moz_gtk_widget_paint(GtkThemeWidgetType widget, GdkDrawable* drawable, case MOZ_GTK_CHECKBUTTON: case MOZ_GTK_RADIOBUTTON: return moz_gtk_toggle_paint(drawable, rect, cliprect, state, - (gboolean) flags, + !!(flags & MOZ_GTK_WIDGET_CHECKED), + !!(flags & MOZ_GTK_WIDGET_INCONSISTENT), (widget == MOZ_GTK_RADIOBUTTON), direction); break; diff --git a/widget/src/gtk2/gtkdrawing.h b/widget/src/gtk2/gtkdrawing.h index eb6995a45a14..1a33bfb91de3 100644 --- a/widget/src/gtk2/gtkdrawing.h +++ b/widget/src/gtk2/gtkdrawing.h @@ -110,6 +110,10 @@ typedef gint (*style_prop_t)(GtkStyle*, const gchar*, gint); #define MOZ_GTK_UNKNOWN_WIDGET -1 #define MOZ_GTK_UNSAFE_THEME -2 +/*** checkbox/radio flags ***/ +#define MOZ_GTK_WIDGET_CHECKED 1 +#define MOZ_GTK_WIDGET_INCONSISTENT (1 << 1) + /*** widget type constants ***/ typedef enum { /* Paints a GtkButton. flags is a GtkReliefStyle. */ diff --git a/widget/src/gtk2/nsNativeThemeGTK.cpp b/widget/src/gtk2/nsNativeThemeGTK.cpp index 0a0050308ffe..1cb64c749edb 100644 --- a/widget/src/gtk2/nsNativeThemeGTK.cpp +++ b/widget/src/gtk2/nsNativeThemeGTK.cpp @@ -59,6 +59,7 @@ #include "nsIMenuFrame.h" #include "prlink.h" #include "nsIDOMHTMLInputElement.h" +#include "nsIDOMNSHTMLInputElement.h" #include "nsWidgetAtoms.h" #include @@ -212,10 +213,20 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame, } else { if (aWidgetFlags) { nsCOMPtr inputElt(do_QueryInterface(content)); + *aWidgetFlags = 0; if (inputElt) { PRBool isHTMLChecked; inputElt->GetChecked(&isHTMLChecked); - *aWidgetFlags = isHTMLChecked; + if (isHTMLChecked) + *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED; + } + + nsCOMPtr inputEltNS(do_QueryInterface(content)); + if (inputEltNS) { + PRBool isIndeterminate; + inputEltNS->GetIndeterminate(&isIndeterminate); + if (isIndeterminate) + *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT; } } } @@ -234,7 +245,7 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame, aState->canDefault = FALSE; // XXX fix me aState->depressed = FALSE; - if (aFrame && aFrame->GetContent()->IsNodeOfType(nsINode::eXUL)) { + if (aFrame->GetContent()->IsNodeOfType(nsINode::eXUL)) { // For these widget types, some element (either a child or parent) // actually has element focus, so we check the focused attribute // to see whether to draw in the focused state.