下载一个gtk,directfb源码,
就可以分析出其复杂的饶人的重绘过程。
不少用GTK做程序的人,知道用gtk_widget_show_all来显示,内部的现实过程如果比较清楚,分析问题的能力应该会好点。
程序都是调用这个接口: void
gtk_widget_show_all (GtkWidget *widget)
{
GtkWidgetClass *class;
g_return_if_fail (GTK_IS_WIDGET (widget));
if ((GTK_WIDGET_FLAGS (widget) & GTK_NO_SHOW_ALL) != 0)
return;
class = GTK_WIDGET_GET_CLASS (widget);
if (class->show_all)
class->show_all (widget);
}
获取其class,然后调用其注册的show_all函数:
static void
gtk_widget_class_init (GtkWidgetClass *klass)
{
……
klass->show = gtk_widget_real_show;
klass->show_all = gtk_widget_show;
klass->hide = gtk_widget_real_hide;
klass->hide_all = gtk_widget_hide;
klass->map = gtk_widget_real_map;
klass->unmap = gtk_widget_real_unmap;
klass->realize = gtk_widget_real_realize;
klass->unrealize = gtk_widget_real_unrealize;
把一个widget设置为可见,并且发送SHOW信号出去 void
gtk_widget_show (GtkWidget *widget)
{
if (!GTK_WIDGET_VISIBLE (widget))
{
g_object_ref (widget);
if (!GTK_WIDGET_TOPLEVEL (widget))
gtk_widget_queue_resize (widget);
g_signal_emit (widget, widget_signals[SHOW], 0);
g_object_notify (G_OBJECT (widget), "visible");
g_object_unref (widget);
}
}
看这个SHOW信号的回调:
klass->show = gtk_widget_real_show;
static void
gtk_widget_real_show (GtkWidget *widget)
{
if (!GTK_WIDGET_VISIBLE (widget))
{
GTK_WIDGET_SET_FLAGS (widget, GTK_VISIBLE);
if (widget->parent &&
GTK_WIDGET_MAPPED (widget->parent) &&
GTK_WIDGET_CHILD_VISIBLE (widget) &&
!GTK_WIDGET_MAPPED (widget))
gtk_widget_map (widget);
}
}
void
gtk_widget_map (GtkWidget *widget)
{
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (GTK_WIDGET_VISIBLE (widget));
g_return_if_fail (GTK_WIDGET_CHILD_VISIBLE (widget));
if (!GTK_WIDGET_MAPPED (widget))
{
if (!GTK_WIDGET_REALIZED (widget))
gtk_widget_realize (widget);
g_signal_emit (widget, widget_signals[MAP], 0);
if (GTK_WIDGET_NO_WINDOW (widget))
gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
}
}
如果一个widget没有被realize, 则先realize它。
所谓的realize: 就是给一个widget 创建一个GDK的资源,比如gdk window
这里的realize可能是递归的过程,一直把它的Parent widget都给创建一个gdk资源。
Realize完后,就发送一个realize信号。
然后调用其回调:
klass->realize = gtk_widget_real_realize;
处理完后,再发送一个MAP信号:
调用其回调:
klass->map = gtk_widget_real_map; static void
gtk_widget_real_map (GtkWidget *widget)
{
g_assert (GTK_WIDGET_REALIZED (widget));
if (!GTK_WIDGET_MAPPED (widget))
{
GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);
if (!GTK_WIDGET_NO_WINDOW (widget))
gdk_window_show (widget->window);
}
}
//map 是开始显示的过程。
这个gdk_window_show()有几个版本:Directfb,X11, etc.
这里我们用Directfb的版本: void
gdk_window_show (GdkWindow *window)
{
g_return_if_fail (GDK_IS_WINDOW (window));
show_window_internal (window, TRUE);
}
Show_window_internal()是需要研究的重点:
Gdkwindow-directfb.c 下面的这个函数
1 把窗口放到顶层
2 给该window内的所有的小窗口发送map事件?
3 gdk_window_invalidate_rect
// 该函数让窗口绘图区域失效,并产生窗口重绘制事件(即 expose 事件)。
4 还可以给窗口设置透明
接着看: void
gdk_window_invalidate_rect (GdkWindow *window,
GdkRectangle *rect,
gboolean invalidate_children)
{
GdkRectangle window_rect;
GdkRegion *region;
GdkWindowObject *private = (GdkWindowObject *)window;
g_return_if_fail (window != NULL);
g_return_if_fail (GDK_IS_WINDOW (window));
if (GDK_WINDOW_DESTROYED (window))
return;
if (private->input_only || !GDK_WINDOW_IS_MAPPED (window))
return;
if (!rect)
{
window_rect.x = 0;
window_rect.y = 0;
gdk_drawable_get_size (GDK_DRAWABLE (window),
&window_rect.width,
&window_rect.height);
rect = &window_rect;
}
region = gdk_region_rectangle (rect);
gdk_window_invalidate_region (window, region, invalidate_children); //重画失效区
gdk_region_destroy (region);
}
Go on : 这里的注释要好好理解一下。
主要是递归提取需要更新的窗口,放到一个全局链表中 update_windows ,
/**
* gdk_window_invalidate_maybe_recurse:
* @window: a #GdkWindow
* @region: a #GdkRegion
* @child_func: function to use to decide if to recurse to a child,
* %NULL means never recurse.
* @user_data: data passed to @child_func
*
* Adds @region to the update area for @window. The update area is the
* region that needs to be redrawn, or "dirty region." The call
* gdk_window_process_updates() sends one or more expose events to the
* window, which together cover the entire update area. An
* application would normally redraw the contents of @window in
* response to those expose events.
*
* GDK will call gdk_window_process_all_updates() on your behalf
* whenever your program returns to the main loop and becomes idle, so
* normally there's no need to do that manually, you just need to
* invalidate regions that you know should be redrawn.
*
* The @child_func parameter controls whether the region of
* each child window that intersects @region will also be invalidated.
* Only children for which @child_func returns TRUE will have the area
* invalidated.
**/
void
gdk_window_invalidate_maybe_recurse (GdkWindow *window,
GdkRegion *region,
gboolean (*child_func) (GdkWindow *, gpointer),
gpointer user_data)
{
GdkWindowObject *private = (GdkWindowObject *)window;
GdkRegion *visible_region;
GList *tmp_list;
g_return_if_fail (window != NULL);
g_return_if_fail (GDK_IS_WINDOW (window));
if (GDK_WINDOW_DESTROYED (window))
return;
if (private->input_only || !GDK_WINDOW_IS_MAPPED (window))
return;
if (GDK_IS_PAINTABLE (private->impl) &&
GDK_PAINTABLE_GET_IFACE (private->impl)->invalidate_maybe_recurse)
{
GDK_PAINTABLE_GET_IFACE (private->impl)->invalidate_maybe_recurse (GDK_PAINTABLE (private->impl), region,
child_func, user_data);
return;
}
visible_region = gdk_drawable_get_visible_region (window);
gdk_region_intersect (visible_region, region);
tmp_list = private->children;
while (tmp_list)
{
GdkWindowObject *child = tmp_list->data;
if (!child->input_only)
{
GdkRegion *child_region;
GdkRectangle child_rect;
gdk_window_get_position ((GdkWindow *)child,
&child_rect.x, &child_rect.y);
gdk_drawable_get_size ((GdkDrawable *)child,
&child_rect.width, &child_rect.height);
child_region = gdk_region_rectangle (&child_rect);
/* remove child area from the invalid area of the parent */
if (GDK_WINDOW_IS_MAPPED (child) && !child->shaped)
gdk_region_subtract (visible_region, child_region);
if (child_func && (*child_func) ((GdkWindow *)child, user_data))
{
gdk_region_offset (region, - child_rect.x, - child_rect.y);
gdk_region_offset (child_region, - child_rect.x, - child_rect.y);
gdk_region_intersect (child_region, region);
gdk_window_invalidate_maybe_recurse ((GdkWindow *)child,
child_region, child_func, user_data);
gdk_region_offset (region, child_rect.x, child_rect.y);
}
gdk_region_destroy (child_region);
}
tmp_list = tmp_list->next;
}
if (!gdk_region_empty (visible_region))
{
if (debug_updates)
draw_ugly_color (window, region);
if (private->update_area)
{
gdk_region_union (private->update_area, visible_region);
}
else
{
update_windows = g_slist_prepend (update_windows, window);
private->update_area = gdk_region_copy (visible_region);
gdk_window_schedule_update (window);
}
}
gdk_region_destroy (visible_region);
}
接着会注册一个回调, 在glib main loop空闲时,更新绘制 static void
gdk_window_schedule_update (GdkWindow *window)
{
if (window && GDK_WINDOW_OBJECT (window)->update_freeze_count)
return;
if (!update_idle)
{
update_idle = g_idle_add_full (GDK_PRIORITY_REDRAW,
gdk_window_update_idle, NULL, NULL);
}
}
static gboolean
gdk_window_update_idle (gpointer data)
{
GDK_THREADS_ENTER ();
gdk_window_directfb_process_all_updates ();//这个地方可以调用directfb的后端也可以调用TinyX的后端
GDK_THREADS_LEAVE ();
return FALSE;
}
根据前面链表中的需要更新的窗口,进行更新: void
gdk_window_directfb_process_all_updates (void)
{
GSList *old_update_windows = update_windows;
GSList *tmp_list = update_windows;
if (update_idle)
g_source_remove (update_idle);
update_windows = NULL;
update_idle = 0;
g_slist_foreach (old_update_windows, (GFunc)g_object_ref, NULL);
while (tmp_list)
{
GdkWindowObject *private = (GdkWindowObject *)tmp_list->data;
if (private->update_freeze_count)
update_windows = g_slist_prepend (update_windows, private);
else
gdk_window_process_updates(tmp_list->data,TRUE);
g_object_unref (tmp_list->data);
tmp_list = tmp_list->next;
}
//
g_slist_free (old_update_windows);
flush_all_displays ();
}
主要是让window响应gdk_expose事件,在实践处理函数里面,进行窗口的重新绘制。
static void
gdk_window_process_updates_internal (GdkWindow *window)
{
GdkWindowObject *private = (GdkWindowObject *)window;
gboolean save_region = FALSE;
/* If an update got queued during update processing, we can get a
* window in the update queue that has an empty update_area.
* just ignore it.
*/
if (private->update_area)
{
GdkRegion *update_area = private->update_area;
private->update_area = NULL;
if (_gdk_event_func && gdk_window_is_viewable (window))
{
GdkRectangle window_rect;
GdkRegion *expose_region;
GdkRegion *window_region;
gint width, height;
if (debug_updates)
{
/* Make sure we see the red invalid area before redrawing. */
gdk_display_sync (gdk_drawable_get_display (window));
g_usleep (70000);
}
save_region = _gdk_windowing_window_queue_antiexpose (window, update_area);
if (save_region)
expose_region = gdk_region_copy (update_area);
else
expose_region = update_area;
gdk_drawable_get_size (GDK_DRAWABLE (private), &width, &height);
window_rect.x = 0;
window_rect.y = 0;
window_rect.width = width;
window_rect.height = height;
window_region = gdk_region_rectangle (&window_rect);
gdk_region_intersect (expose_region,
window_region);
gdk_region_destroy (window_region);
if (!gdk_region_empty (expose_region) &&
(private->event_mask & GDK_EXPOSURE_MASK))
{
GdkEvent event;
event.expose.type = GDK_EXPOSE;
event.expose.window = g_object_ref (window);
event.expose.send_event = FALSE;
event.expose.count = 0;
event.expose.region = expose_region;
gdk_region_get_clipbox (expose_region, &event.expose.area);
(*_gdk_event_func) (&event, _gdk_event_data);
g_object_unref (window);
}
if (expose_region != update_area)
gdk_region_destroy (expose_region);
}
if (!save_region)
gdk_region_destroy (update_area);
}
}
反过来查看一下那个全局事件响应函数:
啥时候注册的: void
gdk_event_handler_set (GdkEventFunc func,
gpointer data,
GDestroyNotify notify)
{
if (_gdk_event_notify)
(*_gdk_event_notify) (_gdk_event_data);
_gdk_event_func = func;
_gdk_event_data = data;
_gdk_event_notify = notify;
}
Gtkmain.c (gdk_event_handler_set ((GdkEventFunc)gtk_main_do_event, NULL, NULL);
所以我们到gtk_main_do_event里面看看如何响应GDK_EXPOSE的: case GDK_EXPOSE:
// g_message("gtk_main_do_event(GDK_EXPOSE)/n");
if (event->any.window && GTK_WIDGET_DOUBLE_BUFFERED (event_widget))
{
gdk_window_begin_paint_region (event->any.window, event->expose.region); //开始画
gtk_widget_send_expose (event_widget, event); //把GDK_EXPOSE事件转化为gtk_expose信号发送出去,这样gtk app就可以做些自己的事情了,如果app没有对gtk_expose事件响应,则忽略掉
gdk_window_end_paint (event->any.window);
}
else
gtk_widget_send_expose (event_widget, event);
到这里,gtk show的过程应该很清楚了。
总结一下:
show_all()会激发 "show" -> "realize" -> "map" 一系列的信号,
然后在glib idle时启动窗口的迭代绘制过程,完成窗口的现实。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/cuijpus/archive/2009/08/02/4401687.aspx