📄 gtk_tut-23.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META NAME="GENERATOR" CONTENT="SGML-Tools 1.0.9"> <TITLE>GTK v1.2 Tutorial: Scribble, A Simple Example Drawing Program</TITLE> <LINK HREF="gtk_tut-24.html" REL=next> <LINK HREF="gtk_tut-22.html" REL=previous> <LINK HREF="gtk_tut.html#toc23" REL=contents></HEAD><BODY BGCOLOR="#FFFFFF"><A HREF="gtk_tut-24.html">Next</A><A HREF="gtk_tut-22.html">Previous</A><A HREF="gtk_tut.html#toc23">Contents</A><HR NOSHADE><H2><A NAME="s23">23. Scribble, A Simple Example Drawing Program</A></H2><H2><A NAME="ss23.1">23.1 Overview</A></H2><P>In this section, we will build a simple drawing program. In theprocess, we will examine how to handle mouse events, how to draw in awindow, and how to do drawing better by using a backing pixmap. Aftercreating the simple drawing program, we will extend it by addingsupport for XInput devices, such as drawing tablets. GTK providessupport routines which makes getting extended information, such aspressure and tilt, from such devices quite easy.<P><H2><A NAME="ss23.2">23.2 Event Handling</A></H2><P>The GTK signals we have already discussed are for high-level actions,such as a menu item being selected. However, sometimes it is useful tolearn about lower-level occurrences, such as the mouse being moved, ora key being pressed. There are also GTK signals corresponding to theselow-level <EM>events</EM>. The handlers for these signals have anextra parameter which is a pointer to a structure containinginformation about the event. For instance, motion events handlers arepassed a pointer to a GdkEventMotion structure which looks (in part)like:<P><BLOCKQUOTE><CODE><PRE>struct _GdkEventMotion{ GdkEventType type; GdkWindow *window; guint32 time; gdouble x; gdouble y; ... guint state; ...};</PRE></CODE></BLOCKQUOTE><P><CODE>type</CODE> will be set to the event type, in this case<CODE>GDK_MOTION_NOTIFY</CODE>, window is the window in which the eventoccurred. <CODE>x</CODE> and <CODE>y</CODE> give the coordinates of the event,and <CODE>state</CODE> specifies the modifier state when the eventoccurred (that is, it specifies which modifier keys and mouse buttonswere pressed.) It is the bitwise OR of some of the following:<P><BLOCKQUOTE><CODE><PRE>GDK_SHIFT_MASK GDK_LOCK_MASK GDK_CONTROL_MASKGDK_MOD1_MASK GDK_MOD2_MASK GDK_MOD3_MASK GDK_MOD4_MASK GDK_MOD5_MASK GDK_BUTTON1_MASKGDK_BUTTON2_MASKGDK_BUTTON3_MASKGDK_BUTTON4_MASKGDK_BUTTON5_MASK</PRE></CODE></BLOCKQUOTE><P>As for other signals, to determine what happens when an event occurswe call <CODE>gtk_signal_connect()</CODE>. But we also need let GTKknow which events we want to be notified about. To do this, we callthe function:<P><BLOCKQUOTE><CODE><PRE>void gtk_widget_set_events (GtkWidget *widget, gint events);</PRE></CODE></BLOCKQUOTE><P>The second field specifies the events we are interested in. Itis the bitwise OR of constants that specify different typesof events. For future reference the event types are:<P><BLOCKQUOTE><CODE><PRE>GDK_EXPOSURE_MASKGDK_POINTER_MOTION_MASKGDK_POINTER_MOTION_HINT_MASKGDK_BUTTON_MOTION_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 </PRE></CODE></BLOCKQUOTE><P>There are a few subtle points that have to be observed when calling<CODE>gtk_widget_set_events()</CODE>. First, it must be called before the X windowfor a GTK widget is created. In practical terms, this means youshould call it immediately after creating the widget. Second, thewidget must have an associated X window. For efficiency, many widgettypes do not have their own window, but draw in their parent's window.These widgets are:<P><BLOCKQUOTE><CODE><PRE>GtkAlignmentGtkArrowGtkBinGtkBoxGtkImageGtkItemGtkLabelGtkPixmapGtkScrolledWindowGtkSeparatorGtkTableGtkAspectFrameGtkFrameGtkVBoxGtkHBoxGtkVSeparatorGtkHSeparator</PRE></CODE></BLOCKQUOTE><P>To capture events for these widgets, you need to use an EventBoxwidget. See the section on the <A HREF="gtk_tut-10.html#sec_EventBox">EventBox</A> widget for details.<P>For our drawing program, we want to know when the mouse button ispressed and when the mouse is moved, so we specify<CODE>GDK_POINTER_MOTION_MASK</CODE> and <CODE>GDK_BUTTON_PRESS_MASK</CODE>. We alsowant to know when we need to redraw our window, so we specify<CODE>GDK_EXPOSURE_MASK</CODE>. Although we want to be notified via aConfigure event when our window size changes, we don't have to specifythe corresponding <CODE>GDK_STRUCTURE_MASK</CODE> flag, because it isautomatically specified for all windows.<P>It turns out, however, that there is a problem with just specifying<CODE>GDK_POINTER_MOTION_MASK</CODE>. This will cause the server to add a newmotion event to the event queue every time the user moves the mouse.Imagine that it takes us 0.1 seconds to handle a motion event, but theX server queues a new motion event every 0.05 seconds. We will soonget way behind the users drawing. If the user draws for 5 seconds,it will take us another 5 seconds to catch up after they release the mouse button! What we would like is to only get one motionevent for each event we process. The way to do this is to specify <CODE>GDK_POINTER_MOTION_HINT_MASK</CODE>. <P>When we specify <CODE>GDK_POINTER_MOTION_HINT_MASK</CODE>, the server sendsus a motion event the first time the pointer moves after enteringour window, or after a button press or release event. Subsequent motion events will be suppressed until we explicitly ask forthe position of the pointer using the function:<P><BLOCKQUOTE><CODE><PRE>GdkWindow* gdk_window_get_pointer (GdkWindow *window, gint *x, gint *y, GdkModifierType *mask);</PRE></CODE></BLOCKQUOTE><P>(There is another function, <CODE>gtk_widget_get_pointer()</CODE> whichhas a simpler interface, but turns out not to be very useful, sinceit only retrieves the position of the mouse, not whether the buttonsare pressed.)<P>The code to set the events for our window then looks like:<P><BLOCKQUOTE><CODE><PRE> 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);</PRE></CODE></BLOCKQUOTE><P>We'll save the "expose_event" and "configure_event" handlers forlater. The "motion_notify_event" and "button_press_event" handlerspretty simple:<P><BLOCKQUOTE><CODE><PRE>static gintbutton_press_event (GtkWidget *widget, GdkEventButton *event){ if (event->button == 1 && pixmap != NULL) draw_brush (widget, event->x, event->y); return TRUE;}static gintmotion_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;}</PRE></CODE></BLOCKQUOTE><P><H2><A NAME="ss23.3">23.3 The DrawingArea Widget, And Drawing</A></H2><P>We know turn to the process of drawing on the screen. The widget we use for this is the DrawingArea widget. A drawing areawidget is essentially an X window and nothing more. It is a blankcanvas in which we can draw whatever we like. A drawing areais created using the call:<P><BLOCKQUOTE><CODE><PRE>GtkWidget* gtk_drawing_area_new (void);</PRE></CODE></BLOCKQUOTE><P>A default size for the widget can be specified by calling:<P><BLOCKQUOTE><CODE><PRE>void gtk_drawing_area_size (GtkDrawingArea *darea, gint width, gint height);</PRE></CODE></BLOCKQUOTE><P>This default size can be overridden, as is true for all widgets,by calling <CODE>gtk_widget_set_usize()</CODE>, and that, in turn, canbe overridden if the user manually resizes the the window containingthe drawing area.<P>It should be noted that when we create a DrawingArea widget, we are,<EM>completely</EM> responsible for drawing the contents. If ourwindow is obscured then uncovered, we get an exposure event and mustredraw what was previously hidden.<P>Having to remember everything that was drawn on the screen so wecan properly redraw it can, to say the least, be a nuisance. Inaddition, it can be visually distracting if portions of thewindow are cleared, then redrawn step by step. The solution tothis problem is to use an offscreen <EM>backing pixmap</EM>.Instead of drawing directly to the screen, we draw to an imagestored in server memory but not displayed, then when the imagechanges or new portions of the image are displayed, we copy therelevant portions onto the screen.<P>To create an offscreen pixmap, we call the function:<P><BLOCKQUOTE><CODE><PRE>GdkPixmap* gdk_pixmap_new (GdkWindow *window, gint width, gint height, gint depth);</PRE></CODE></BLOCKQUOTE><P>The <CODE>window</CODE> parameter specifies a GDK window that this pixmaptakes some of its properties from. <CODE>width</CODE> and <CODE>height</CODE>specify the size of the pixmap. <CODE>depth</CODE> specifies the <EM>colordepth</EM>, that is the number of bits per pixel, for the new window.If the depth is specified as <CODE>-1</CODE>, it will match the depthof <CODE>window</CODE>.<P>We create the pixmap in our "configure_event" handler. This eventis generated whenever the window changes size, including when itis originally created.<P><BLOCKQUOTE><CODE><PRE>/* Backing pixmap for drawing area */static GdkPixmap *pixmap = NULL;/* Create a new backing pixmap of the appropriate size */static gintconfigure_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;}</PRE></CODE></BLOCKQUOTE><P>The call to <CODE>gdk_draw_rectangle()</CODE> clears the pixmapinitially to white. We'll say more about that in a moment.<P>Our exposure event handler then simply copies the relevant portionof the pixmap onto the screen (we determine the area we needto redraw by using the event->area field of the exposure event):<P><BLOCKQUOTE><CODE><PRE>/* Redraw the screen from the backing pixmap */static gintexpose_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;}</PRE></CODE></BLOCKQUOTE><P>We've now seen how to keep the screen up to date with our pixmap, buthow do we actually draw interesting stuff on our pixmap? There are alarge number of calls in GTK's GDK library for drawing on<EM>drawables</EM>. A drawable is simply something that can be drawnupon. It can be a window, a pixmap, or a bitmap (a black and whiteimage). We've already seen two such calls above,<CODE>gdk_draw_rectangle()</CODE> and <CODE>gdk_draw_pixmap()</CODE>. Thecomplete list is:<P><BLOCKQUOTE><CODE><PRE>gdk_draw_line ()gdk_draw_rectangle ()gdk_draw_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 ()</PRE></CODE></BLOCKQUOTE><P>See the reference documentation or the header file<CODE><gdk/gdk.h></CODE> for further details on these functions.These functions all share the same first two arguments. The firstargument is the drawable to draw upon, the second argument is a<EM>graphics context</EM> (GC). <P>A graphics context encapsulates information about things such asforeground and background color and line width. GDK has a full set offunctions for creating and modifying graphics contexts, but to keepthings simple we'll just use predefined graphics contexts. Each widgethas an associated style. (Which can be modified in a gtkrc file, seethe section GTK's rc file.) This, among other things, stores a numberof graphics contexts. Some examples of accessing these graphicscontexts are:<P><BLOCKQUOTE><CODE><PRE>widget->style->white_gcwidget->style->black_gcwidget->style->fg_gc[GTK_STATE_NORMAL]widget->style->bg_gc[GTK_WIDGET_STATE(widget)]</PRE></CODE></BLOCKQUOTE><P>The fields <CODE>fg_gc</CODE>, <CODE>bg_gc</CODE>, <CODE>dark_gc</CODE>, and<CODE>light_gc</CODE> are indexed by a parameter of type<CODE>GtkStateType</CODE> which can take on the values:<P><BLOCKQUOTE><CODE><PRE>GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED,GTK_STATE_INSENSITIVE</PRE></CODE></BLOCKQUOTE><P>For instance, the for <CODE>GTK_STATE_SELECTED</CODE> the default foregroundcolor is white and the default background color, dark blue.<P>Our function <CODE>draw_brush()</CODE>, which does the actual drawing
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -