/*
* File: dw_button.c
*
* Copyright (C) 2002 Sebastian Geerken <S.Geerken@ping.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
/*
* This widget imitates the look and behavior of GtkButton.
*
* NOTE: Unlike in X, we do not get release events, if the pointer has
* been moved out of the widget with mouse button down (implicit
* pointer grab, will perhaps be added to Dw). For this reason, the
* behavior of DwButton is a bit differernt from GtkButton: If the
* user presses the mouse button within the button, then leaves it
* (with mouse button pressed), and enters it again, a GtkButton will
* still be in the state "pressed", while DwButton resets this state
* when the user leaves the button. This is to avoid problems in the
* situation when the user leaves the button with mouse button down,
* and releases it outside of the button.
*
* BUG (reminder): If the user releases the button, it gets somehow a
* "leave_notify" event. This bug is in the base code, not here,
* somehow it is assumed that the mouse pointer is out of the viewport
* (Dw_widget_mouse_event is called with widget == NULL). The effect
* is that after releasing the mouse button, the DwButton switches
* from "active" to "normal", not to "highlighted", as it should.
* Should be fixed at the next opportunity.
*/
#include "dw_button.h"
#include "dw_gtk_viewport.h"
#include "debug.h"
#include <gtk/gtk.h>
static void Dw_button_init (DwButton *button);
static void Dw_button_class_init (DwButtonClass *klass);
static void Dw_button_size_request (DwWidget *widget,
DwRequisition *requisition);
static void Dw_button_get_extremes (DwWidget *widget,
DwExtremes *extremes);
static void Dw_button_size_allocate (DwWidget *widget,
DwAllocation *allocation);
static void Dw_button_set_width (DwWidget *widget,
gint32 width);
static void Dw_button_set_ascent (DwWidget *widget,
gint32 ascent);
static void Dw_button_set_descent (DwWidget *widget,
gint32 descent);
static void Dw_button_draw (DwWidget *widget,
DwRectangle *area,
GdkEventExpose *event);
static gboolean Dw_button_button_press (DwWidget *widget,
gint32 x,
gint32 y,
GdkEventButton *event);
static gboolean Dw_button_button_release (DwWidget *widget,
gint32 x,
gint32 y,
GdkEventButton *event);
static gboolean Dw_button_enter_notify (DwWidget *widget,
DwWidget *last_widget,
GdkEventMotion *event);
static gboolean Dw_button_leave_notify (DwWidget *widget,
DwWidget *next_widget,
GdkEventMotion *event);
static void Dw_button_add (DwContainer *container,
DwWidget *widget);
static void Dw_button_remove (DwContainer *container,
DwWidget *widget);
static void Dw_button_forall (DwContainer *container,
DwCallback callback,
gpointer callback_data);
static DwIterator* Dw_button_iterator (DwWidget *widget,
gint mask,
gboolean at_end);
static gboolean Dw_button_iterator_next (DwIterator *it);
static gboolean Dw_button_iterator_prev (DwIterator *it);
static gint Dw_button_iterator_compare (DwIterator *it1,
DwIterator *it2);
enum
{
CLICKED,
CLICKED_AT,
LAST_SIGNAL
};
static DwContainerClass *parent_class;
static guint button_signals[LAST_SIGNAL] = { 0 };
/*
* Return the type of DwButton
*/
GtkType a_Dw_button_get_type (void)
{
static GtkType type = 0;
if (!type) {
GtkTypeInfo info = {
"DwButton",
sizeof (DwButton),
sizeof (DwButtonClass),
(GtkClassInitFunc) Dw_button_class_init,
(GtkObjectInitFunc) Dw_button_init,
(GtkArgSetFunc) NULL,
(GtkArgGetFunc) NULL,
(GtkClassInitFunc) NULL
};
type = gtk_type_unique (DW_TYPE_CONTAINER, &info);
}
return type;
}
/*
* Standard Gtk+ function.
*
* - "flags" is a mask for the window flags, it should be the mask the
* child uses (e.g. DW_USES_HINT for DwPage, 0 for DwImage). (Will
* hopefully be removed soon.)
* - When passing FALSE for "relief", the button will neither have a
* border, nor paint a background.
*/
DwWidget* a_Dw_button_new (gint flags,
gboolean relief)
{
DwButton *button;
button = DW_BUTTON (gtk_object_new (DW_TYPE_BUTTON, NULL));
DBG_OBJ_CREATE (button, "DwButton");
DW_WIDGET_SET_FLAGS (button, flags);
button->relief = relief;
return DW_WIDGET (button);
}
/*
* Standard Gtk+ function.
*/
static void Dw_button_init (DwButton *button)
{
button->child = NULL;
button->in_button = FALSE;
button->pressed = FALSE;
button->sensitive = FALSE;
}
/*
* Standard Gtk+ function.
*/
static void Dw_button_class_init (DwButtonClass *klass)
{
GtkObjectClass *object_class;
DwWidgetClass *widget_class;
DwContainerClass *container_class;
object_class = (GtkObjectClass*) klass;
widget_class = (DwWidgetClass*) klass;
container_class = (DwContainerClass*) klass;
parent_class = gtk_type_class (DW_TYPE_CONTAINER);
button_signals[CLICKED] =
gtk_signal_new ("clicked",
GTK_RUN_FIRST | GTK_RUN_ACTION,
object_class->type,
GTK_SIGNAL_OFFSET (DwButtonClass, clicked),
gtk_marshal_NONE__NONE,
GTK_TYPE_NONE, 0);
button_signals[CLICKED_AT] =
gtk_signal_new ("clicked_at",
GTK_RUN_FIRST | GTK_RUN_ACTION,
object_class->type,
GTK_SIGNAL_OFFSET (DwButtonClass, clicked_at),
gtk_marshal_NONE__INT_INT,
GTK_TYPE_NONE,
2, GTK_TYPE_INT, GTK_TYPE_INT);
gtk_object_class_add_signals (object_class, button_signals, LAST_SIGNAL);
widget_class->size_request = Dw_button_size_request;
widget_class->get_extremes = Dw_button_get_extremes;
widget_class->size_allocate = Dw_button_size_allocate;
widget_class->set_width = Dw_button_set_width;
widget_class->set_ascent = Dw_button_set_ascent;
widget_class->set_descent = Dw_button_set_descent;
widget_class->draw = Dw_button_draw;
widget_class->button_press_event = Dw_button_button_press;
widget_class->button_release_event = Dw_button_button_release;
widget_class->enter_notify_event = Dw_button_enter_notify;
widget_class->leave_notify_event = Dw_button_leave_notify;
widget_class->iterator = Dw_button_iterator;
container_class->add = Dw_button_add;
container_class->remove = Dw_button_remove;
container_class->forall = Dw_button_forall;
klass->clicked = NULL;
klass->clicked_at = NULL;
}
/*
* Standard Dw function.
*/
static void Dw_button_size_request (DwWidget *widget,
DwRequisition *requisition)
{
DwButton *button = DW_BUTTON (widget);
DwRequisition child_requisition;
GtkStyleClass *styleclass = widget->viewport->style->klass;
if (button->child) {
p_Dw_widget_size_request (button->child, &child_requisition);
*requisition = child_requisition;
} else {
requisition->width = 0;
requisition->ascent = 0;
requisition->descent = 0;
}
if (button->relief) {
requisition->width += 2 * styleclass->xthickness;
requisition->ascent += styleclass->ythickness;
requisition->descent += styleclass->ythickness;
}
}
/*
* Standard Dw function.
*/
static void Dw_button_get_extremes (DwWidget *widget,
DwExtremes *extremes)
{
DwButton *button = DW_BUTTON (widget);
DwExtremes child_extremes;
GtkStyleClass *styleclass = widget->viewport->style->klass;
if (button->child) {
p_Dw_widget_get_extremes (button->child, &child_extremes);
*extremes = child_extremes;
} else {
extremes->min_width = 0;
extremes->max_width = 0;
}
if (button->relief) {
extremes->min_width += 2 * styleclass->xthickness;
extremes->max_width += 2 * styleclass->xthickness;
}
}
/*
* Standard Dw function.
*/
static void Dw_button_size_allocate (DwWidget *widget,
DwAllocation *allocation)
{
DwButton *button = DW_BUTTON (widget);
DwAllocation child_allocation;
GtkStyleClass *styleclass = widget->viewport->style->klass;
if (button->child) {
child_allocation = *allocation;
if (button->relief) {
child_allocation. x += styleclass->xthickness;
child_allocation. y += styleclass->ythickness;
child_allocation.width -= 2 * styleclass->xthickness;
child_allocation.ascent -= styleclass->ythickness;
child_allocation.descent -= styleclass->ythickness;
}
p_Dw_widget_size_allocate (button->child, &child_allocation);
}
}
/*
* Standard Dw function.
*/
static void Dw_button_set_width (DwWidget *widget,
gint32 width)
{
DwButton *button = DW_BUTTON (widget);
if (button->child)
p_Dw_widget_set_width
(button->child,
width - (button->relief ?
(2 * widget->viewport->style->klass->xthickness) : 0));
}
/*
* Standard Dw function.
*/
static void Dw_button_set_ascent (DwWidget *widget,
gint32 ascent)
{
DwButton *button = DW_BUTTON (widget);
if (button->child)
p_Dw_widget_set_ascent
(button->child,
ascent - (button->relief ?
widget->viewport->style->klass->ythickness : 0));
}
/*
* Standard Dw function.
*/
static void Dw_button_set_descent (DwWidget *widget,
gint32 descent)
{
DwButton *button = DW_BUTTON (widget);
if (button->child)
p_Dw_widget_set_descent
(button->child,
descent - (button->relief ?
widget->viewport->style->klass->ythickness : 0));
}
/*
* Standard Dw function.
*/
static void Dw_button_draw (DwWidget *widget,
DwRectangle *area,
GdkEventExpose *event)
{
DwButton *button = DW_BUTTON (widget);
GtkStateType state;
GtkShadowType shadow;
GdkRectangle gdk_area;
DwRectangle child_area;
if (button->relief) {
if (button->sensitive) {
if (button->in_button) {
if (button->pressed) {
state = GTK_STATE_ACTIVE;
shadow = GTK_SHADOW_IN;
} else {
state = GTK_STATE_PRELIGHT;
shadow = GTK_SHADOW_OUT;
}
} else {
state = GTK_STATE_NORMAL;
shadow = GTK_SHADOW_OUT;
}
} else {
state = GTK_STATE_INSENSITIVE;
shadow = GTK_SHADOW_OUT;
}
gdk_area.x =
p_Dw_widget_x_world_to_viewport (widget,
area->x + widget->allocation.x);
gdk_area.y =
p_Dw_widget_y_world_to_viewport (widget,
area->y + widget->allocation.y);
gdk_area.width = area->width;
gdk_area.height = area->height;
gtk_paint_box (widget->viewport->style, DW_WIDGET_WINDOW (widget),
state, shadow, &gdk_area, widget->viewport,
"buttondefault",
p_Dw_widget_x_world_to_viewport (widget,
widget->allocation.x),
p_Dw_widget_y_world_to_viewport (widget,
widget->allocation.y),
widget->allocation.width,
DW_WIDGET_HEIGHT(widget));
}
if (button->child &&
p_Dw_widget_intersect (button->child, area, &child_area))
p_Dw_widget_draw (button->child, &child_area, event);
}
/*
* Standard Dw function.
*/
static gboolean Dw_button_button_press (DwWidget *widget,
gint32 x,
gint32 y,
GdkEventButton *event)
{
DwButton *button = DW_BUTTON (widget);
/* assert it was the left mouse button */
if (event->button != 1)
return FALSE;
button->pressed = TRUE;
if (button->relief)
p_Dw_widget_queue_draw (widget);
return TRUE;
}
/*
* Standard Dw function.
*/
static gboolean Dw_button_button_release (DwWidget *widget,
gint32 x,
gint32 y,
GdkEventButton *event)
{
DwButton *button = DW_BUTTON (widget);
gint32 cx, cy;
/* Notice that button->pressed may have been set to FALSE in
* Dw_button_leave_notify. */
if (button->pressed && button->in_button && button->sensitive) {
/* simple "click" */
gtk_signal_emit (GTK_OBJECT (widget), button_signals[CLICKED]);
/* "clicked_at": like "clicked", but position is passed, relative
* to the child widget. */
if (button->child) {
cx = x + widget->allocation.x - button->child->allocation.x;
cy = y + widget->allocation.y - button->child->allocation.y;
/* If position is outside of the child, clip at the child
allocation */
if (cx < 0)
cx = 0;
else if (cx > button->child->allocation.width - 1)
cx = button->child->allocation.width - 1;
if (cy < 0)
cy = 0;
else if (cy > (DW_WIDGET_HEIGHT(button->child) - 1))
cy = DW_WIDGET_HEIGHT(button->child) - 1;
gtk_signal_emit (GTK_OBJECT (widget), button_signals[CLICKED_AT],
cx, cy);
}
}
button->pressed = FALSE;
if (button->relief)
p_Dw_widget_queue_draw (widget);
return TRUE;
}
/*
Standard Dw function.
*/
static gboolean Dw_button_enter_notify (DwWidget *widget,
DwWidget *last_widget,
GdkEventMotion *event)
{
DwButton *button = DW_BUTTON (widget);
button->in_button = TRUE;
if (button->relief)
p_Dw_widget_queue_draw (widget);
return TRUE;
}
/*
* Standard Dw function.
*/
static gboolean Dw_button_leave_notify (DwWidget *widget,
DwWidget *next_widget,
GdkEventMotion *event)
{
DwButton *button = DW_BUTTON (widget);
if (button->child == NULL || next_widget != button->child) {
button->in_button = FALSE;
/* See comment at the beginning. */
button->pressed = FALSE;
if (button->relief)
p_Dw_widget_queue_draw (widget);
}
return TRUE;
}
/*
* Standard Dw function.
*/
static void Dw_button_add (DwContainer *container,
DwWidget *widget)
{
DwButton *button = DW_BUTTON (container);
g_return_if_fail (button->child == NULL);
button->child = widget;
p_Dw_widget_set_parent (widget, DW_WIDGET (container));
}
/*
* Standard Dw function.
*/
static void Dw_button_remove (DwContainer *container,
DwWidget *widget)
{
DwButton *button = DW_BUTTON (container);
g_return_if_fail (button->child != NULL);
g_return_if_fail (widget == button->child);
button->child = NULL;
}
/*
* Standard Dw function.
*/
static void Dw_button_forall (DwContainer *container,
DwCallback callback,
gpointer callback_data)
{
DwButton *button = DW_BUTTON (container);
if (button->child)
callback (button->child, callback_data);
}
/*
* An insensitive button does not respond on user interaction. Used
* in HTML forms.
*/
void a_Dw_button_set_sensitive (DwButton *button,
gboolean sensitive)
{
button->sensitive = sensitive;
if (button->relief)
p_Dw_widget_queue_draw (DW_WIDGET (button));
}
static DwIterator *Dw_button_iterator (DwWidget *widget,
gint mask,
gboolean at_end)
{
DwIterator *it = g_new (DwIterator, 1);
it->widget = widget;
it->mask = mask;
it->content.type = (at_end ? DW_CONTENT_END : DW_CONTENT_START);
it->next = Dw_button_iterator_next;
it->prev = Dw_button_iterator_prev;
it->clone = p_Dw_iterator_clone_std;
it->compare = Dw_button_iterator_compare;
it->free = p_Dw_iterator_free_std;
it->highlight = p_Dw_iterator_highlight_std;
it->get_allocation = p_Dw_iterator_get_allocation_std_only_widgets;
return it;
}
static gboolean Dw_button_iterator_next (DwIterator *it)
{
if (it->content.type == DW_CONTENT_START &&
(it->mask & DW_CONTENT_WIDGET) &&
DW_BUTTON(it->widget)->child) {
it->content.type = DW_CONTENT_WIDGET;
it->content.data.widget = DW_BUTTON(it->widget)->child;
return TRUE;
} else {
it->content.type = DW_CONTENT_END;
return FALSE;
}
}
static gboolean Dw_button_iterator_prev (DwIterator *it)
{
if (it->content.type == DW_CONTENT_END &&
(it->mask & DW_CONTENT_WIDGET) &&
DW_BUTTON(it->widget)->child) {
it->content.type = DW_CONTENT_WIDGET;
it->content.data.widget = DW_BUTTON(it->widget)->child;
return TRUE;
} else {
it->content.type = DW_CONTENT_START;
return FALSE;
}
}
static gint Dw_button_iterator_compare (DwIterator *it1,
DwIterator *it2)
{
if (it1->content.type == it2->content.type)
return 0;
switch (it1->content.type) {
case DW_CONTENT_START:
return -1;
case DW_CONTENT_TEXT:
if (it2->content.type == DW_CONTENT_START)
return +1;
else
return -1;
case DW_CONTENT_END:
return +1;
default:
return 0;
}
}