的的范德萨的地方地方爱的规格好文章
在窗口内绘图和如何使用后端位图绘制更好的效果。在创建了简单的绘图程序之后,
我们会扩展该程序,通过添加对XInput设备的支持,如手写板。GTK提供用来从这种设备
轻易地获得如压力和倾角这样的扩展信息的例程。
些低级的事件也是有好用的,如鼠标移动或按一个键。在GTK中有信号与这些低级
一个结构指针,包含事件的信息。例如,传递给移动事件处理函数的参数是一个
GdkEventMotion类型的结构指针,如下:
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *window;
guint32 time;
gdouble x;
gdouble y;
...
guint state;
...
};
鼠标键)。它是如下值的位或:
GDK_SHIFT_MASK
GDK_LOCK_MASK
GDK_CONTROL_MASK
GDK_MOD1_MASK
GDK_MOD2_MASK
GDK_MOD3_MASK
GDK_MOD4_MASK
GDK_MOD5_MASK
GDK_BUTTON1_MASK
GDK_BUTTON2_MASK
GDK_BUTTON3_MASK
GDK_BUTTON4_MASK
GDK_BUTTON5_MASK
来决定事件发生时调用的处理函数。但是我们也需要让GTK知道我们想接收的事件。
可以用如下函数:
void gtk_widget_set_events (GtkWidget *widget,
gint events);
事件类型的列表如下:
GDK_EXPOSURE_MASK
GDK_POINTER_MOTION_MASK
GDK_POINTER_MOTION_HINT_MASK
GDK_BUTTON_MOTIO
N_MASK
GDK_BUTTON1_MOTION_MASK
GDK_BUTTON2_MOTION_MASK
GDK_BUTTON3_MOTION_MASK
GDK_BUTTON_PRESS_MASK
GDK_BUTTON_RELEASE_MASK
GDK_KEY_PRESS_MASK
GDK_KEY_RELEASE_MASK
GDK_ENTER_NOTIFY_MASK
GDK_LEAVE_NOTIFY_MASK
GDK_FOCUS_CHANGE_MASK
GDK_STRUCTURE_MASK
GDK_PROPERTY_CHANGE_MASK
GDK_PROXIMITY_IN_MASK
GDK_PROXIMITY_OUT_MASK
首先,该函数必须在一个GTK构件的X窗口创建之前调用。实际上,意味者您应该在创建一个
构件之后立即调用该函数。其次,构件必须有一个相关联的X窗口。为了提高效益,许多构件类型
没有属于自己的窗口,它们绘制在父窗口上。这些构件是:
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkAspectFrame
GtkFrame
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
我们也想知道什么时候窗口需要重新绘制,因此我们也要用
虽然我们也想在窗口尺寸改变时得到消息,不过我们不必用
因为所有的窗口都自动设了该标志。
这会使服务器在每次用户移动鼠标时向事件队列添加一个移动事件。
假设处理一个移动事件需要0.1秒,但是X服务器每0.05秒添加一个新的移动事件。
如果用户绘制要花5秒,那么在释放鼠标键后我们的程序会中断5秒!我们所需要
的只是为我们处理的每个事件的获取一个移动事件。解决这个问题的方法是要用
在指针进入我们的窗口之后、或在一个按钮按下或释放事件之后,
服务器在指针首次移动时向我们发送一个移动事件。后发的移动事件
都会被压制,直到我们用如下函数去获取鼠标指针的位置:
GdkWindow* gdk_window_get_pointer (GdkWindow *window,
gint *x,
gint *y,
GdkModifierType *mask);
它有相似的接口,不过它不是很有用,
因为它仅仅获取鼠标指针的位置,而不
管按下了那个键。)
gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
(GtkSignalFunc) expose_event, NULL);
gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
(GtkSignalFunc) configure_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
(GtkSignalFunc) motion_notify_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
(GtkSignalFunc) button_press_event, NULL);
gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
和"button_press_event"的处理函数很简单:
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 && pixmap != NULL)
draw_brush (widget, event->x, event->y);
return TRUE;
}
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
int x, y;
GdkModifierType state;
if (event->is_hint)
gdk_window_get_pointer (event->window, &x, &y, &state);
else
{
x = event->x;
y = event->y;
state = event->state;
}
if (state & GDK_BUTTON1_MASK && pixmap != NULL)
draw_brush (widget, x, y);
return TRUE;
}
本质上是一个X窗口,没有其它的东西。它是一个空白的画布,我们可以在其上绘制
需要的东西。一个绘图区构件用如下函数创建:
GtkWidget* gtk_drawing_area_new (void);
void gtk_drawing_area_size (GtkDrawingArea *darea,
gint width,
gint height);
用户手动调整包含绘图区的窗口的大小时,默认大小可以无效,这对所有的构件都是一样的。
如果我们的窗口被遮住后暴露出来,我们得到一个暴露事件,我们必须重绘先前被遮住的部分。
为了能正确的重绘,我们必须记住绘制在屏幕上的内容。另外,
这显然很麻烦,如果窗口窗口的一部分被清除了,我们需一步步的重绘。
解决的办法是使用一个
我们用向图象中绘制来代替直接向屏幕绘制,当图象改变或图象的一部分
需要显示,我们复制相应的部分到屏幕上。
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
gint width,
gint height,
gint depth);
如果depth设为
大小时产生,包括窗口创建时。
/* 绘制区的后端位图 */
static GdkPixmap *pixmap = NULL;
/* 创建一个适当大小的后端位图 */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
if (pixmap)
gdk_pixmap_unref(pixmap);
pixmap = gdk_pixmap_new(widget->window,
widget->allocation.width,
widget->allocation.height,
-1);
gdk_draw_rectangle (pixmap,
widget->style->white_gc,
TRUE,
0, 0,
widget->allocation.width,
widget->allocation.height);
return TRUE;
}
后面我们会详细讲解。
event->area来确定重绘区域):
/* Redraw the screen from the backing pixmap */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
{
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
pixmap,
event->area.x, event->area.y,
event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
GTK的GDK库中有许多函数用于在
窗口、图解和黑白图。在上面我们已经见到了两个,
这些函数的完全列表如下:
gdk_draw_line ()
gdk_draw_rectangle ()
gdk_dr
aw_arc ()
gdk_draw_polygon ()
gdk_draw_string ()
gdk_draw_text ()
gdk_draw_pixmap ()
gdk_draw_bitmap ()
gdk_draw_image ()
gdk_draw_points ()
gdk_draw_segments ()
这些函数的头两个参数都相同。第一个参数是可绘区域。第二个参数是
GDK有一组函数用于创建和修改图象关联,但为了方便,我们仅使用预定义的
图象关联。每个构件有一个相关联的风格。(可以在gtkrc文件中修改,详见GTK的rc文件)
其中,存储了许多图象关联。一些访问这些图象关联的例子如下:
widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
和
该类型可以取如下值:
GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE
/* 在屏幕上绘制一个矩形 */
static void
draw_brush (GtkWidget *widget, gdouble x, gdouble y)
{
GdkRectangle update_rect;
update_rect.x = x - 5;
update_rect.y = y - 5;
update_rect.width = 10;
update_rect.height = 10;
gdk_draw_rectangle (pixmap,
widget->style->black_gc,
TRUE,
update_rect.x, update_rect.y,
update_rect.width, update_rect.height);
gtk_widget_draw (widget, &update_rect);
}
void gtk_widget_draw (GtkWidget *widget,
GdkRectangle *area);
X会最终会产生一个暴露事件(混合区域需要多次调用函数
,然后会调用暴露事件处理函数,复制相应的部分到屏幕上。
方便。
它可以用于代替鼠标,但这样失去了这个设备的许多优点:
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *window;
guint32 time;
gdouble x;
gdouble y;
gdouble pressure;
gdouble xtilt;
gdouble ytilt;
guint state;
gint16 is_hint;
GdkInputSource source;
guint32 deviceid;
};
倾斜度数。
设备。
GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
绘制自己的光标。关于绘制光标内容详见linkend="sec-FurtherSophistications">进一步讲解。我们也可以给出值
去允许和配置扩展设备。下面的程序处理一个InputDialog构件。
void
input_dialog_destroy (GtkWidget *w, gpointer data)
{
*((GtkWidget **)data) = NULL;
}
void
create_input_dialog ()
{
static GtkWidget *inputd = NULL;
if (!inputd)
{
inputd = gtk_input_dialog_new();
gtk
_signal_connect (GTK_OBJECT(inputd), "destroy",
(GtkSignalFunc)input_dialog_destroy, &inputd);
gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
"clicked",
(GtkSignalFunc)gtk_widget_hide,
GTK_OBJECT(inputd));
gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
gtk_widget_show (inputd);
}
else
{
if (!GTK_WIDGET_MAPPED(inputd))
gtk_widget_show(inputd);
else
gdk_window_raise(inputd->window);
}
}
在上面的函数,我们用"关闭"隐藏对话框,隐藏"保存"按钮,因为
我们在这个程序里不用它。
扩展设备信息。事实上,总是可以安全的使用这个信息,因为这些域值是
合法的,甚至在扩展事件不允许时。
void gdk_input_window_get_pointer( GdkWindow *window,
guint32 deviceid,
gdouble *x,
gdouble *y,
gdouble *pressure,
gdouble *xtilt,
gdouble *ytilt,
GdkModifierType *mask);
(这样
只需要添加处理扩展信息的代码。
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
print_button_press (event->deviceid);
if (event->button == 1 && pixmap != NULL)
draw_brush (widget, event->source, event->x, event->y, event->pressure);
return TRUE;
}
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
gdouble x, y;
gdouble pressure;
GdkModifierType state;
if (event->is_hint)
gdk_input_window_get_pointer (event->window, event->deviceid,
&x, &
y, &pressure, NULL, NULL, &state);
else
{
x = event->x;
y = event->y;
pressure = event->pressure;
state = event->state;
}
if (state & GDK_BUTTON1_MASK && pixmap != NULL)
draw_brush (widget, event->source, x, y, pressure);
return TRUE;
}
绘制不同的颜色,依据压力改变画刷的大小。
/* 在屏幕上画一个矩形,大小依据压力,颜色依据设备的类型 */
static void
draw_brush (GtkWidget *widget, GdkInputSource source,
gdouble x, gdouble y, gdouble pressure)
{
GdkGC *gc;
GdkRectangle update_rect;
switch (source)
{
case GDK_SOURCE_MOUSE:
gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
break;
case GDK_SOURCE_PEN:
gc = widget->style->black_gc;
break;
case GDK_SOURCE_ERASER:
gc = widget->style->white_gc;
break;
default:
gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
}
update_rect.x = x - 10 * pressure;
update_rect.y = y - 10 * pressure;
update_rect.width = 20 * pressure;
update_rect.height = 20 * pressure;
gdk_draw_rectangle (pixmap, gc, TRUE,
update_rect.x, update_rect.y,
update_rect.width, update_rect.height);
gtk_widget_draw (widget, &update_rect);
}
用如下函数可以得到设备名:
GList *gdk_input_list_devices (void);
GdkDeviceInfo结构定义如下:
struct _GdkDeviceInfo
{
guint32 deviceid;
gchar *name;
GdkInputSource source;
GdkInputMode mode;
gint has_cursor;
gint num_axes;
GdkAxisUse *axes;
gint num_keys;
GdkDeviceKey *keys;
};
我们感兴趣的域是
其它的不是配置信息的域是
是false,我们需要自绘制光标。但因为我们已经指定了
,所以我们不必关心这个。
static void
print_button_press (guint32 deviceid)
{
GList *tmp_list;
/* gdk_input_list_devices返回一个内部列表,因此我们后面不必释放它。*/
tmp_list = gdk_input_list_devices();
while (tmp_list)
{
GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
if (info->deviceid == deviceid)
{
printf("Button press on device '%s'\n", info->name);
return;
}
tmp_list = tmp_list->next;
}
}
全功能的程序。首先,用户不想每次在程序运行时配置设备,因此我们应该允许用户保存设备
配置。这是通过获取
一个文件。
gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()
在绘图程序gsumi中可以发现它的用法。(
其实做这个最好使用所有程序标准的方法。这大概属于稍文风不动GTK的库,也许在GNOME库中。
当前平台XFree86不允许同时用一个设备和主指点设备在一个应用程序中。详见
更好的应用程序应该绘制自己的光标。
确定当前设备是否需要绘制光标,确定当前设备是否"in proximity"。
(如果当前设备是手写板,最好在笔尖离开平板时不显示光标。当设备是
触摸板时,那叫做"in proximity"。)
首先要搜索设备列表,寻找设备名。其次是选择"proximity_out"事件。它的用法见GTK发布中的示例程序"testinput".
啊订单式