Bug 123836 - Implement indeterminate property on checkboxes and radio buttons - r=roc,jst sr=roc

This commit is contained in:
Michael Ventnor 2009-01-22 13:07:44 +13:00
parent 32da7c5a78
commit b119ed9345
12 changed files with 104 additions and 11 deletions

View File

@ -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 {

View File

@ -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);

View File

@ -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<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
PRBool retval = PR_FALSE;
elem->GetChecked(&retval);
return retval;
}
PRBool
nsGfxCheckboxControlFrame::IsIndeterminate()
{
nsCOMPtr<nsIDOMNSHTMLInputElement> elem(do_QueryInterface(mContent));
PRBool retval = PR_FALSE;
elem->GetIndeterminate(&retval);
return retval;
}

View File

@ -91,7 +91,8 @@ public:
protected:
PRBool GetCheckboxState();
PRBool IsChecked();
PRBool IsIndeterminate();
nsRefPtr<nsStyleContext> mCheckButtonFaceStyle;
};

View File

@ -0,0 +1 @@
<input type="checkbox" checked style="-moz-appearance: none;">

View File

@ -0,0 +1 @@
<input type="checkbox" id="s" checked style="-moz-appearance: none;"><script>document.getElementById("s").indeterminate = true;</script>

View File

@ -0,0 +1 @@
<input type="checkbox" style="-moz-appearance: none;">

View File

@ -0,0 +1 @@
<input type="checkbox" id="s" style="-moz-appearance: none;"><script>document.getElementById("s").indeterminate = true;</script>

View File

@ -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

View File

@ -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;

View File

@ -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. */

View File

@ -59,6 +59,7 @@
#include "nsIMenuFrame.h"
#include "prlink.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsIDOMNSHTMLInputElement.h"
#include "nsWidgetAtoms.h"
#include <gdk/gdkprivate.h>
@ -212,10 +213,20 @@ nsNativeThemeGTK::GetGtkWidgetAndState(PRUint8 aWidgetType, nsIFrame* aFrame,
} else {
if (aWidgetFlags) {
nsCOMPtr<nsIDOMHTMLInputElement> inputElt(do_QueryInterface(content));
*aWidgetFlags = 0;
if (inputElt) {
PRBool isHTMLChecked;
inputElt->GetChecked(&isHTMLChecked);
*aWidgetFlags = isHTMLChecked;
if (isHTMLChecked)
*aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
}
nsCOMPtr<nsIDOMNSHTMLInputElement> 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.