[Gtk-sharp-list] Monodoc tagging for scribble tutorial ?

Daniel Kornhauser dkor@media.mit.edu
Mon, 15 Dec 2003 12:35:53 -0500


--=-VOfLV9Tcy0BD3aZwBM+1
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

Out of frustration for being stuck doing a Gtk# Paint program, I did the
"translation" of the scribble tutorial.

Any advice for tagging for monodoc monkey guide ?
I guess I should do the tagging in Emacs with nXMl mode ?
Is there a nice template where I can see the all the tags, or should I
just look around in the docs ?
How do I check my tagging is correct ?

			Daniel.

--=-VOfLV9Tcy0BD3aZwBM+1
Content-Disposition: attachment; filename=Scribble.txt
Content-Type: text/plain; name=Scribble.txt; charset=
Content-Transfer-Encoding: 8bit

Scribble, A Simple Example Drawing Program
Overview
In this section, we will build a simple drawing program. In the process, we will examine how to handle mouse events, how to draw in a window, and how to do drawing better by using a backing pixmap.

22.2. Event Handling

The GTK# events we have already discussed are for high-level actions, such as a menu item being selected. However, sometimes it is useful to learn about lower-level occurrences, such as the mouse being moved, or a key being pressed. There are also GTK# signals corresponding to these low-level events. The handlers for these signals have an extra property called Event which contains a structure with information about the event. For instance, motion event handlers are contain an EventMotion structure which looks (in part) like:

Fields
is_hint	short. 
send_event	sbyte. TRUE if the event was sent explicitly (e.g. using XSendEvent).
state	uint. 
time	uint. 
type	EventType. 
x	double. 
x_root	double. 
y	double. 
y_root	double. 
Zerostatic 	EventMotion. 

Properties
device	Device. 
window	Window. the window which received the event.`

type will be set to the event type, in this case MotionNotify, window is the window in which the event occurred. x and y give the coordinates of the event. state specifies the modifier state when the event occurred (that is, it specifies which modifier keys and mouse buttons were pressed). It is the bitwise OR of some of the following:

Nothing	
Delete	
Destroy	
Expose	
MotionNotify	
ButtonPress	
TwoButtonPress	
ThreeButtonPress	
ButtonRelease	
KeyPress	
KeyRelease	

As for other signals, to determine what happens when an event occurs we call the event Handlers

 But we also need let GTK know which events we want to be notified about. To do this, we call the Event Method, where we specify the events we are interested in. It is the bitwise OR of constants that specify different types of events. For future reference the event types are:
ExposureMask
PointerMotionMask
PointerMotionHintMask
ButtonMotionMask
Button1MotionMask
Button2MotionMask
Button3MotionMask
ButtonPressMask
ButtonReleaseMask
KeyPressMask
KeyReleaseMask
EnterNotifyMask
LeaveNotifyMask
FocusChangeMask
StructureMask
PropertyChangeMask
VisibilityNotifyMask
ProximityInMask
ProximityOutMask
SubstructureMask
ScrollMask
AllEventsMask	

There are a few subtle points that have to be observed when calling the Events Method. First, it must be called before the X window for a GTK# widget is created. In practical terms, this means you should call it immediately after creating the widget. Second, the widget must have an associated X window. For efficiency, many widget types do not have their own window, but draw in their parent's window. These widgets are:

Alignment
Arrow
Bin
Box
Image
Item
Label
Pixmap
ScrolledWindow
Separator
Table
AspectFrame
Frame
VBox
HBox
VSeparator
HSeparator

To capture events for these widgets, you need to use an EventBox widget. See the section on the EventBox widget for details.

For our drawing program, we want to know when the mouse button is pressed and when the mouse is moved, so we specify PointerMotionMask and ButtonPressMask. We also want to know when we need to redraw our window, so we specify ExposureMask. Although we want to be notified via a ConfigureEvent when our window size changes, we don't have to specify the corresponding StructureMask flag, because it is automatically specified for all windows.

It turns out, however, that there is a problem with just specifying PointerMotionMask. This will cause the server to add a new motion 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 the X Server queues a new motion event every 0.05 seconds. We will soon get 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 motion event for each event we process. The way to do this is to specify PointerMotionHintMask.

When we specify PointerMotionHintMask, the server sends us a motion event the first time the pointer moves after entering our window, or after a button press or release event. Subsequent motion events will be suppressed until we explicitly ask for the position of the pointer using the function:

window.GetPointer (out x, out y, out s);0
(There is another function, Gtk.Widget.GetPointer which has a simpler interface (public void GetPointer (out int x, out int y)), but turns out not to be very useful, since it only retrieves the position of the mouse, not whether the buttons are pressed.)

The code to set the events for our window then looks like:

// The ExposeEvent is thrown each time that the window Pixmap
darea.ExposeEvent += new ExposeEventHandler (ExposeEvent);
// The ConfigureEvent is thrown whenever the size of postion window has changed
darea.ConfigureEvent += new ConfigureEventHandler (ConfigureEvent);
// The MotionNotifyEvent is thrown everytime the mouse moves
darea.MotionNotifyEvent += new MotionNotifyEventHandler (MotionNotifyEvent);
// The ButtonPress is thrown if the Button is pressed
darea.ButtonPressEvent += new ButtonPressEventHandler (ButtonPressEvent);
// Determine the events that will be forwarded to the Drawing Area

darea.Events = (int)EventMask.ExposureMask |
       (int)EventMask.LeaveNotifyMask |
		(int)EventMask.ButtonPressMask |
		(int)EventMask.PointerMotionMask |
		(int)EventMask.PointerMotionHintMask;

We'll save the "ExposeEvent" and "ConfigureEvent" handlers for later. The "MotionNotifyEvent" and "ButtonPressEvent" handlers are pretty simple:


static void ButtonPressEvent (object obj, ButtonPressEventArgs args)
{
	Console.WriteLine ("ButtonPressEvent");
	Gdk.EventButton ev = args.Event;
	// if the button is pressed and the pixmap where to paint exists
	if (ev.button == 1 && pixmap != null)
		// paint the bruz onto the pixmap
		DrawBrush (ev.x, ev.y);
	args.RetVal = true;
}

static void MotionNotifyEvent (object obj, MotionNotifyEventArgs args)
{
	Console.WriteLine ("MotionNotifyEvent");
	int x, y;
	Gdk.ModifierType state;
	Gdk.EventMotion ev = args.Event;
	Gdk.Window window = ev.window;
	// see what whether you got a usual motion
	if (ev.is_hint != 0) {
		Gdk.ModifierType s;
		window.GetPointer (out x, out y, out s);
		state = s;
	// the mouse is only moving
	} else {
		x = (int) ev.x;
		y = (int) ev.y;
		state = (Gdk.ModifierType) ev.state;
	}
	// the mouse is moving and the button is pressed
	if ((state & Gdk.ModifierType.Button1Mask) != 0 && pixmap != null)
		DrawBrush (x, y);

	args.RetVal = true;
}

22.3. The DrawingArea Widget, And Drawing

We now turn to the process of drawing on the screen. The widget we use for this is the DrawingArea widget. A drawing area widget is essentially an X window and nothing more. It is a blank canvas in which we can draw whatever we like. A drawing area is instanced using the DrawingArea Constructor :

new Gtk.DrawingArea ();

A default size for the widget can be specified by calling:

public void SetSizeRequest (int width, int height)

It should be noted that when we create a DrawingArea widget, we are completely responsible for drawing the contents. If our window is obscured then uncovered, we get an exposure event and must redraw what was previously hidden.

Having to remember everything that was drawn on the screen so we can properly redraw it can, to say the least, be a nuisance. In addition, it can be visually distracting if portions of the window are cleared, then redrawn step by step. The solution to this problem is to use an offscreen backing pixmap. Instead of drawing directly to the screen, we draw to an image stored in server memory but not displayed, then when the image changes or new portions of the image are displayed, we copy the relevant portions onto the screen.

To instance an offscreen pixmap, we use its constructor:

public Pixmap (Drawable drawable, int width, int height, int depth)

The constructor parameters state width and height specify the size of the Pixmap, depth specifies the color depth, that is the number of bits per pixel, for the new window. If the depth is specified as -1, it will match the depth of window.

We create the Pixmap in our ConfigureEvent handler. This event is generated whenever the window changes size, including when it is originally created.

static void ConfigureEvent (object obj, ConfigureEventArgs args)
{
	Gdk.EventConfigure ev = args.Event;
	Gdk.Window window = ev.window;
	Gdk.Rectangle allocation = darea.Allocation;

	Console.WriteLine ("Darea=[{0}]" , darea);
	// Create a pixmap the size of the drawing area
	pixmap = new Gdk.Pixmap (window,
			allocation.width,
			allocation.height,
			-1);
	Console.WriteLine ("Darea.Style={0}", darea.Style);
	// paint a white Pixmap into the drawing area
	pixmap.DrawRectangle (darea.Style.WhiteGC, true, 0, 0,
			allocation.width, allocation.height);

	args.RetVal = true;
}¨
The call to DrawRectangle clears the Pixmap initially to white. We'll say more about that in a moment.

Our exposure event handler then simply copies the relevant portion of the Pixmap onto the screen (we determine the area we need to redraw by using the area field of the exposure event):

static void ExposeEvent (object obj, ExposeEventArgs args)
{
	Console.WriteLine ("ExposeEvent");
	Gdk.EventExpose ev = args.Event;
	Gdk.Window window = ev.window;
	// FIXME: mcs bug
	Gdk.Rectangle area = ev.area;
	// FIXME: array marshalling not done yet so no FG */

	// Copy the pixmap into de drawing area.
	window.DrawDrawable (darea.Style.BlackGC,
			pixmap,
			area.x, area.y,
			area.x, area.y,
			area.width, area.height);

	args.RetVal = false;
}

We've now seen how to keep the screen up to date with our Pixmap, but how do we actually draw interesting stuff on our Pixmap? There are a large number of methods in GTK#'s GDK# library for drawing on Drawables. A Drawable is simply something that can be drawn upon. It can be a Window, a Pixmap, or a Bitmap (a black and white image). We've already seen two such methods above, DrawRectangle and DrawPixmap. 

DrawLine(GC, int, int, int, int)
DrawRectangle(GC, bool, Rectangle)
DrawArc(GC, bool, int, int, int, int, int, int)
DrawPolygon(GC, int, Point[] )
DrawImage(GC, Image, int, int, int, int, int, int)
DrawPoint(GC, int, int)
DrawSegments(GC, Segment, int)

See the reference documentation in monodoc for further details on these functions. These functions all share the same first argument, is a graphics context (GC).

A graphics context encapsulates information about things such as foreground and background color and line width. GDK# has a full set of functions for creating and modifying graphics contexts, but to keep things simple we'll just use predefined graphics contexts. Each widget has an associated style. This, among other things, stores a number of graphics contexts. Some examples of accessing these graphics contexts are:
Widget.Style.WhiteGC
Widget.Style.BlackGC

WidgetForeground = red_color;
Widget.Style.ForegroundGCs[StyleType.Normal]
Widget.Style.BackgroundGCs[StyleType.Normal]

The fields ForegroundGCs, BackgroundGC, ( dark_gc, and light_gc didn’t find equivalente in C#) are indexed by a parameter of type StateType which can take on the values:


StateType.Normal	
StateType Active	
StateType Prelight	
StateType Selected
StateType Insensitive

For instance, for StateType Selected the default foreground color is white and the default background color, dark blue.

Our method DrawBrush, which does the actual drawing on the screen, is then:


static void DrawBrush (double x, double y)
{
	Console.WriteLine ("DrawBrush");
	// create a rectangle the size of the brush
	Gdk.Rectangle update_rect = new Gdk.Rectangle ();
	update_rect.x = (int) x - 5;
	update_rect.y = (int) y - 5;
	update_rect.width = 10;
	update_rect.height = 10;
	//  the rectangle onto the pixmap
	pixmap.DrawRectangle (darea.Style.BlackGC, true,
			update_rect.x, update_rect.y,
			update_rect.width, update_rect.height);
	// update the drawing area where the rectangle was painted.
	darea.QueueDrawArea (update_rect.x, update_rect.y,
	update_rect.width, update_rect.height);
}

After we draw the rectangle representing the brush onto the Pixmap, we call the function:

public void QueueDrawArea (int x, int y, int width, int height)

which notifies X that the area given by the area parameter needs to be updated. X will eventually generate an expose event (possibly combining the areas passed in several calls to Gtk.Widget.Draw) which will cause our expose event handler to copy the relevant portions to the screen.

We have now covered the entire drawing program except for a few mundane details like creating the main window.



--=-VOfLV9Tcy0BD3aZwBM+1--