|
There are three common situations that require a widget
to redraw all or part of itself:
-
Expose events signal that all or part of a widget's
GdkWindow has just
become visible on the screen and needs repainting
(see the
section called Expose Events in the chapter
called GDK Basics). A widget's expose_event method
performs these redraws.
-
GTK+ sometimes determines that a widget should be
redrawn. This might happen when a widget receives a
new size allocation different from its previous size
allocation, or when a new theme is loaded. A widget's
draw method, usually
invoked via
gtk_widget_queue_draw() or
gtk_widget_queue_clear(), handles this case.
-
Widgets sometimes decide to redraw themselves. For
example, if you change the text of a GtkLabel, the label will redraw
itself to reflect the new text. Widget
implementations are free to handle this case however
they like, but most will use the draw method.
There are two special cases of the second situation. The
first occurs when a widget receives or loses the keyboard
focus; the second occurs when the widget becomes (or
unbecomes) the "default" widget. Widgets should indicate
these states visually, but they can often do so without a
complete redraw. Thus, there are special draw_focus and draw_default signals to handle them.
These signals only have to be implemented if a widget can
meaningfully receive the focus or default.
Because there is typically little difference between a
widget's draw and expose methods, a common convention is
to write a static function to handle both of them. This
function is standardly called
gtk_whatever_paint(). It's also possible to avoid
implementing the draw method, because the default draw
method synthesizes an expose event covering the widget's
entire allocation and invokes the expose method.
(Remember that a synthetic expose event will have its
send_event flag set to
TRUE; you can use this to
distinguish synthetic events.)
The primary reason for distinguishing expose events from
other draws is that expose events are marked with the
window they occurred on; for widgets with multiple
windows such as GtkEv, this
can increase efficiency. GtkEv
implements two private functions,
gtk_ev_paint() and
gtk_ev_paint_event_window(), which it uses to
implement the expose and draw methods.
Here is the draw method:
static void
gtk_ev_draw (GtkWidget *widget,
GdkRectangle *area)
{
GdkRectangle event_window_area;
GdkRectangle intersection;
GtkEv* ev;
g_return_if_fail(widget != NULL);
g_return_if_fail(GTK_IS_EV(widget));
ev = GTK_EV(widget);
gtk_ev_paint(ev, area);
event_window_area = *area;
if (gdk_rectangle_intersect(area, &ev->event_window_rect, &intersection))
{
/* Make the intersection relative to the event window */
intersection.x -= ev->event_window_rect.x;
intersection.y -= ev->event_window_rect.y;
gtk_ev_paint_event_window(ev, &intersection);
}
}
|
And the expose method:
static gint
gtk_ev_expose (GtkWidget *widget,
GdkEventExpose *event)
{
if (event->window == widget->window)
gtk_ev_paint(GTK_EV(widget), &event->area);
else if (event->window == GTK_EV(widget)->event_window)
gtk_ev_paint_event_window(GTK_EV(widget), &event->area);
else
g_assert_not_reached();
return TRUE;
}
|
Both the draw and expose methods should be
self-explanatory. All the work is done in the two paint
functions. Here is
gtk_ev_paint(), which renders the main widget
window:
static void
gtk_ev_paint (GtkEv *ev,
GdkRectangle *area)
{
GtkWidget* widget;
g_return_if_fail(ev != NULL);
g_return_if_fail(GTK_IS_EV(ev));
widget = GTK_WIDGET(ev);
if (!GTK_WIDGET_DRAWABLE (widget))
return;
gdk_window_clear_area (widget->window,
area->x,
area->y,
area->width,
area->height);
gdk_gc_set_clip_rectangle(widget->style->black_gc, area);
/* Draw a black rectangle around the event window */
gdk_draw_rectangle(widget->window,
widget->style->black_gc,
FALSE,
ev->event_window_rect.x - 1,
ev->event_window_rect.y - 1,
ev->event_window_rect.width + 2,
ev->event_window_rect.height + 2);
gdk_gc_set_clip_rectangle(widget->style->black_gc, NULL);
/* Draw text in the description area, if applicable */
if (ev->buffer)
{
GdkRectangle intersection;
if (gdk_rectangle_intersect(area,
&ev->description_rect,
&intersection))
{
static const gint space = 2;
gint line;
gint step;
gint first_baseline;
GList* tmp;
step = widget->style->font->ascent +
widget->style->font->descent + space;
first_baseline = ev->description_rect.y +
widget->style->font->ascent + space;
line = 0;
tmp = ev->buffer;
while (tmp != NULL)
{
gchar** this_event = tmp->data;
gint i = 0;
while (this_event[i])
{
gtk_paint_string (widget->style,
widget->window,
widget->state,
&intersection, widget, "ev",
ev->description_rect.x,
first_baseline + line*step,
this_event[i]);
++i;
++line;
}
/* Bail out if we're off the bottom; the "- 2*step" is
* needed because the next baseline may be outside the
* redraw area but we are interested in the whole row of
* text, not the baseline. The 2* is because line is one
* larger than we've actually drawn.
*/
if ((first_baseline + line*step - 2*step) >
(intersection.y + intersection.height))
break;
tmp = g_list_next(tmp);
}
}
}
if (GTK_WIDGET_HAS_FOCUS (widget))
{
gtk_paint_focus (widget->style, widget->window,
area, widget, "ev",
widget->allocation.x, widget->allocation.y,
widget->allocation.width-1, widget->allocation.height-1);
}
}
|
Most of gtk_ev_paint() is GtkEv-specific; it simply draws
the contents of the window. Notice that it checks GTK_WIDGET_DRAWABLE() at the
beginning; this is required because the draw method may
invoke the function. Unsynthesized expose events
guarantee that a widget's X window is on-screen and thus
this check is not really necessary when responding to
expose events.
gtk_ev_paint_event_window()
paints the small subwindow; it's a very simple function:
static void
gtk_ev_paint_event_window (GtkEv *ev,
GdkRectangle *area)
{
GtkWidget* widget;
gint width;
gint x, y;
const char* title;
g_return_if_fail(ev != NULL);
g_return_if_fail(GTK_IS_EV(ev));
widget = GTK_WIDGET(ev);
if (!GTK_WIDGET_DRAWABLE (widget))
return;
title = _("Event Window");
gdk_window_clear_area (ev->event_window,
area->x,
area->y,
area->width,
area->height);
gdk_gc_set_clip_rectangle(widget->style->black_gc, area);
/* Clearly it would be better to cache this */
width = gdk_string_width(widget->style->font,
title);
x = (ev->event_window_rect.width - width)/2;
y = widget->style->font->ascent + 2;
gdk_draw_string(ev->event_window,
widget->style->font,
widget->style->black_gc,
x, y,
title);
gdk_gc_set_clip_rectangle(widget->style->black_gc, NULL);
}
|
|
|