forked from AuroraMiddleware/gtk
9093 lines
295 KiB
Plaintext
9093 lines
295 KiB
Plaintext
|
<!doctype linuxdoc system>
|
|||
|
|
|||
|
<!-- This is the tutorial marked up in SGML
|
|||
|
(just to show how to write a comment)
|
|||
|
-->
|
|||
|
|
|||
|
<article>
|
|||
|
<title>GTK Tutorial
|
|||
|
<author>Ian Main <tt><htmlurl url="mailto:slow@intergate.bc.ca"
|
|||
|
name="<slow@intergate.bc.ca>"></tt>,
|
|||
|
Tony Gale <tt><htmlurl url="mailto:gale@gimp.org"
|
|||
|
name="<gale@gimp.org>"></tt
|
|||
|
<date>March 8th, 1998
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Introduction
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<p>
|
|||
|
GTK (GIMP Toolkit) was originally developed as a toolkit for the GIMP
|
|||
|
(General Image Manipulation Program). GTK is built on top of GDK (GIMP
|
|||
|
Drawing Kit) which is basically wrapper around the Xlib functions. It's
|
|||
|
called the GIMP toolkit because it was original written for developing
|
|||
|
the GIMP, but has now been used in several free software projects. The
|
|||
|
authors are
|
|||
|
<itemize>
|
|||
|
<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
|
|||
|
name="petm@xcf.berkeley.edu"></tt>
|
|||
|
<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu"
|
|||
|
name="spencer@xcf.berkeley.edu"></tt>
|
|||
|
<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu"
|
|||
|
name="jmacd@xcf.berkeley.edu"></tt>
|
|||
|
</itemize>
|
|||
|
|
|||
|
<p>
|
|||
|
GTK is essentially an object oriented application programmers interface (API).
|
|||
|
Although written completely in
|
|||
|
C, it is implemented using the idea of classes and callback functions
|
|||
|
(pointers to functions).
|
|||
|
<p>
|
|||
|
There is also a third component called glib which contains a few
|
|||
|
replacements for some standard calls, as well as some additional functions
|
|||
|
for handling linked lists etc. The replacement functions are used to
|
|||
|
increase GTK's portability, as some of the functions implemented
|
|||
|
here are not available or are nonstandard on other unicies such as
|
|||
|
g_strerror(). Some also contain enhancements to the libc versions such as
|
|||
|
g_malloc has enhanced debugging utilities.
|
|||
|
<p>
|
|||
|
This tutorial is an attempt to document as much as possible of GTK, it is by
|
|||
|
no means complete. This
|
|||
|
tutorial assumes a good understanding of C, and how to create C programs.
|
|||
|
It would be a great benefit for the reader to have previous X programming
|
|||
|
experience, but it shouldn't be necessary. If you are learning GTK as your
|
|||
|
first widget set, please comment on how you found this tutorial, and what
|
|||
|
you had troubles with.
|
|||
|
Note that there is also a C++ API for GTK (GTK--) in the works, so if you
|
|||
|
prefer to use C++, you should look into this instead. There's also an
|
|||
|
Objective C wrapper, and guile bindings available, but I don't follow these.
|
|||
|
<p>
|
|||
|
I would very much like to hear any problems you have learning GTK from this
|
|||
|
document, and would appreciate input as to how it may be improved.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Getting Started
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
The first thing to do of course, is download the GTK source and install
|
|||
|
it. You can always get the latest version from ftp.gimp.org in /pub/gtk.
|
|||
|
You can also view other sources of GTK information on http://www.gimp.org/gtk
|
|||
|
GTK uses GNU autoconf for
|
|||
|
configuration. Once untar'd, type ./configure --help to see a list of options.
|
|||
|
<p>
|
|||
|
To begin our introduction to GTK, we'll start with the simplest program
|
|||
|
possible. This program will
|
|||
|
create a 200x200 pixel window and has no way of exiting except to be
|
|||
|
killed using the shell.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
All programs will of course include the gtk/gtk.h which declares the
|
|||
|
variables, functions, structures etc. that will be used in your GTK
|
|||
|
application.
|
|||
|
<p>
|
|||
|
The next line:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
calls the function gtk_init(gint *argc, gchar ***argv) which will be
|
|||
|
called in all GTK applications. This sets up a few things for us such
|
|||
|
as the default visual and color map and then proceeds to call
|
|||
|
gdk_init(gint *argc, gchar ***argv). This function initializes the
|
|||
|
library for use, sets up default signal handlers, and checks the
|
|||
|
arguments passed to your application on the command line, looking for one
|
|||
|
of the following:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> <tt/--display/
|
|||
|
<item> <tt/--debug-level/
|
|||
|
<item> <tt/--no-xshm/
|
|||
|
<item> <tt/--sync/
|
|||
|
<item> <tt/--show-events/
|
|||
|
<item> <tt/--no-show-events/
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
It removes these from the argument list, leaving anything it does
|
|||
|
not recognize for your application to parse or ignore. This creates a set
|
|||
|
of standard arguments excepted by all GTK applications.
|
|||
|
<p>
|
|||
|
The next two lines of code create and display a window.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_show (window);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The GTK_WINDOW_TOPLEVEL argument specifies that we want the window to
|
|||
|
undergo window manager decoration and placement. Rather than create a
|
|||
|
window of 0x0 size, a window without children is set to 200x200 by default
|
|||
|
so you can still manipulate it.
|
|||
|
<p>
|
|||
|
The gtk_widget_show() function, lets GTK know that we are done setting the
|
|||
|
attributes of this widget, and it can display it.
|
|||
|
<p>
|
|||
|
The last line enters the GTK main processing loop.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_main ();
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
gtk_main() is another call you will see in every GTK application. When
|
|||
|
control reaches this point, GTK will sleep waiting for X events (such as
|
|||
|
button or key presses), timeouts, or file IO notifications to occur.
|
|||
|
In our simple example however, events are ignored.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Hello World in GTK
|
|||
|
<p>
|
|||
|
OK, now for a program with a widget (a button). It's the classic hello
|
|||
|
world ala GTK.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* helloworld.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* this is a callback function. the data arguments are ignored in this example..
|
|||
|
* More on callbacks below. */
|
|||
|
void hello (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
g_print ("Hello World\n");
|
|||
|
}
|
|||
|
|
|||
|
gint delete_event(GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
g_print ("delete event occured\n");
|
|||
|
/* if you return TRUE in the "delete_event" signal handler,
|
|||
|
* GTK will emit the "destroy" signal. Returning FALSE means
|
|||
|
* you don't want the window to be destroyed.
|
|||
|
* This is useful for popping up 'are you sure you want to quit ?'
|
|||
|
* type dialogs. */
|
|||
|
|
|||
|
/* Change FALSE to TRUE and the main window will be destroyed with
|
|||
|
* a "delete_event". */
|
|||
|
|
|||
|
return (FALSE);
|
|||
|
}
|
|||
|
|
|||
|
/* another callback */
|
|||
|
void destroy (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
/* GtkWidget is the storage type for widgets */
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
|
|||
|
/* this is called in all GTK applications. arguments are parsed from
|
|||
|
* the command line and are returned to the application. */
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
/* when the window is given the "delete_event" signal (this is given
|
|||
|
* by the window manager (usually the 'close' option, or on the
|
|||
|
* titlebar), we ask it to call the delete_event () function
|
|||
|
* as defined above. The data passed to the callback
|
|||
|
* function is NULL and is ignored in the callback. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|||
|
|
|||
|
/* here we connect the "destroy" event to a signal handler.
|
|||
|
* This event occurs when we call gtk_widget_destroy() on the window,
|
|||
|
* or if we return 'TRUE' in the "delete_event" callback. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (destroy), NULL);
|
|||
|
|
|||
|
/* sets the border width of the window. */
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* creates a new button with the label "Hello World". */
|
|||
|
button = gtk_button_new_with_label ("Hello World");
|
|||
|
|
|||
|
/* When the button receives the "clicked" signal, it will call the
|
|||
|
* function hello() passing it NULL as it's argument. The hello() function is
|
|||
|
* defined above. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (hello), NULL);
|
|||
|
|
|||
|
/* This will cause the window to be destroyed by calling
|
|||
|
* gtk_widget_destroy(window) when "clicked. Again, the destroy
|
|||
|
* signal could come from here, or the window manager. */
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (gtk_widget_destroy),
|
|||
|
GTK_OBJECT (window));
|
|||
|
|
|||
|
/* this packs the button into the window (a gtk container). */
|
|||
|
gtk_container_add (GTK_CONTAINER (window), button);
|
|||
|
|
|||
|
/* the final step is to display this newly created widget... */
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
/* and the window */
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
/* all GTK applications must have a gtk_main(). Control ends here
|
|||
|
* and waits for an event to occur (like a key press or mouse event). */
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Compiling Hello World
|
|||
|
<p>
|
|||
|
To compile use:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gcc -Wall -g helloworld.c -o hello_world -L/usr/X11R6/lib \
|
|||
|
-lgtk -lgdk -lglib -lX11 -lXext -lm
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
The libraries above must all be in your default search paths, if not, add
|
|||
|
-L<library directory> and gcc will look in these directories for
|
|||
|
the needed
|
|||
|
libraries. For instance, on my Debian Linux system, I have to add
|
|||
|
<tt>-L/usr/X11R6/lib</> for it to find the X11 libraries.
|
|||
|
<p>
|
|||
|
The order of the libraries are significant. The linker has to know what
|
|||
|
functions it needs from a library before it processes it.
|
|||
|
<p>
|
|||
|
The libraries we are linking in are:
|
|||
|
<itemize>
|
|||
|
<item>The GTK library (-lgtk), the widget library, based on top of GDK.
|
|||
|
<item>The GDK library (-lgdk), the Xlib wrapper.
|
|||
|
<item>The glib library (-lglib), containing miscellaneous functions, only
|
|||
|
g_print() is used in this particular example. GTK is built on top
|
|||
|
of glib so you will always require this library. See the section on
|
|||
|
<ref id="sec_glib" name="glib"> for details.
|
|||
|
<item>The Xlib library (-lX11) which is used by GDK.
|
|||
|
<item>The Xext library (-lXext). This contains code for shared memory
|
|||
|
pixmaps and other X extensions.
|
|||
|
<item>The math library (-lm). This is used by GTK for various purposes.
|
|||
|
</itemize>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Theory of Signals and Callbacks
|
|||
|
<p>
|
|||
|
Before we look in detail at hello world, we'll discuss events and callbacks.
|
|||
|
GTK is an event driven toolkit, which means it will sleep in
|
|||
|
gtk_main until an event occurs and control is passed to the appropriate
|
|||
|
function.
|
|||
|
<p>
|
|||
|
This passing of control is done using the idea of "signals". When an
|
|||
|
event occurs, such as the press of a mouse button, the
|
|||
|
appropriate signal will be "emitted" by the widget that was pressed.
|
|||
|
This is how GTK does
|
|||
|
most of its useful work. To make a button perform an action,
|
|||
|
we set up a signal handler to catch these
|
|||
|
signals and call the appropriate function. This is done by using a
|
|||
|
function such as:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_signal_connect (GtkObject *object,
|
|||
|
gchar *name,
|
|||
|
GtkSignalFunc func,
|
|||
|
gpointer func_data);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the first argument is the widget which will be emitting the signal, and
|
|||
|
the second, the name of the signal you wish to catch. The third is the function
|
|||
|
you wish to be called when it is caught, and the fourth, the data you wish
|
|||
|
to have passed to this function.
|
|||
|
<p>
|
|||
|
The function specified in the third argument is called a "callback
|
|||
|
function", and should be of the form:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void callback_func(GtkWidget *widget, gpointer *callback_data);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the first argument will be a pointer to the widget that emitted the signal, and
|
|||
|
the second, a pointer to the data given as the last argument to the
|
|||
|
gtk_signal_connect() function as shown above.
|
|||
|
<p>
|
|||
|
Another call used in the hello world example, is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_signal_connect_object (GtkObject *object,
|
|||
|
gchar *name,
|
|||
|
GtkSignalFunc func,
|
|||
|
GtkObject *slot_object);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
gtk_signal_connect_object() is the same as gtk_signal_connect() except that
|
|||
|
the callback function only uses one argument, a
|
|||
|
pointer to a GTK
|
|||
|
object. So when using this function to connect signals, the callback should be of
|
|||
|
the form:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void callback_func (GtkObject *object);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the object is usually a widget. We usually don't setup callbacks for
|
|||
|
gtk_signal_connect_object however. They are usually used
|
|||
|
to call a GTK function that accept a single widget or object as an
|
|||
|
argument, as is the case in our hello world example.
|
|||
|
|
|||
|
The purpose of having two functions to connect signals is simply to allow
|
|||
|
the callbacks to have a different number of arguments. Many functions in
|
|||
|
the GTK library accept only a single GtkWidget pointer as an argument, so you
|
|||
|
want to use the gtk_signal_connect_object() for these, whereas for your
|
|||
|
functions, you may need to have additional data supplied to the callbacks.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Stepping Through Hello World
|
|||
|
<p>
|
|||
|
Now that we know the theory behind this, lets clarify by walking through
|
|||
|
the example hello world program.
|
|||
|
<p>
|
|||
|
Here is the callback function that will be called when the button is
|
|||
|
"clicked". We ignore both the widget and the data in this example, but it
|
|||
|
is not hard to do things with them. The next example will use the data
|
|||
|
argument to tell us which button was pressed.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void hello (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
g_print ("Hello World\n");
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
This callback is a bit special. The "delete_event" occurs when the
|
|||
|
window manager sends this event to the application. We have a choice here
|
|||
|
as to what to do about these events. We can ignore them, make some sort of
|
|||
|
response, or simply quit the application.
|
|||
|
|
|||
|
The value you return in this callback lets GTK know what action to take.
|
|||
|
By returning FALSE, we let it know that we don't want to have the "destroy"
|
|||
|
signal emitted, keeping our application running. By returning TRUE, we
|
|||
|
ask that "destroy" is emitted, which in turn will call our "destroy"
|
|||
|
signal handler.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint delete_event(GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
g_print ("delete event occured\n");
|
|||
|
|
|||
|
return (FALSE);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
Here is another callback function which just quits by calling
|
|||
|
gtk_main_quit(). Not really much to say about this, it is pretty self
|
|||
|
explanatory.
|
|||
|
<tscreen><verb>
|
|||
|
void destroy (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
I assume you know about the main() function... yes, as with other
|
|||
|
applications, all GTK applications will also have one of these.
|
|||
|
<tscreen><verb>
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This next part, declares a pointer to a structure of type GtkWidget. These
|
|||
|
are used below to create a window and a button.
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Here is our gtk_init again. As before, this initializes the toolkit, and
|
|||
|
parses the arguments found on the command line. Any argument it
|
|||
|
recognizes from the command line, it removes from the list, and modifies
|
|||
|
argc and argv to make it look like they never existed, allowing your
|
|||
|
application to parse the remaining arguments.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Create a new window. This is fairly straight forward. Memory is allocated
|
|||
|
for the GtkWidget *window structure so it now points to a valid structure.
|
|||
|
It sets up a new window, but it is not displayed until below where we call
|
|||
|
gtk_widget_show(window) near the end of our program.
|
|||
|
<tscreen><verb>
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Here is an example of connecting a signal handler to an object, in this case, the
|
|||
|
window. Here, the "destroy" signal is caught. This is emitted when we use
|
|||
|
the window manager to kill the window (and we return TRUE in the
|
|||
|
"delete_event" handler), or when we use the
|
|||
|
gtk_widget_destroy() call passing in the window widget as the object to
|
|||
|
destroy. By setting this up, we handle both cases with a single call.
|
|||
|
Here, it just calls the destroy() function defined above with a NULL
|
|||
|
argument, which quits GTK for us.
|
|||
|
<p>
|
|||
|
The GTK_OBJECT and GTK_SIGNAL_FUNC are macros that perform type casting and
|
|||
|
checking for us, as well as aid the readability of the code.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (destroy), NULL);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This next function is used to set an attribute of a container object.
|
|||
|
This just sets the window
|
|||
|
so it has a blank area along the inside of it 10 pixels wide where no
|
|||
|
widgets will go. There are other similar functions which we will look at
|
|||
|
in the section on
|
|||
|
<ref id="sec_setting_widget_attributes" name="Setting Widget Attributes">
|
|||
|
<p>
|
|||
|
And again, GTK_CONTAINER is a macro to perform type casting.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This call creates a new button. It allocates space for a new GtkWidget
|
|||
|
structure in memory, initializes it, and makes the button pointer point to
|
|||
|
it. It will have the label "Hello World" on it when displayed.
|
|||
|
<tscreen><verb>
|
|||
|
button = gtk_button_new_with_label ("Hello World");
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Here, we take this button, and make it do something useful. We attach a
|
|||
|
signal handler to it so when it emits the "clicked" signal, our hello()
|
|||
|
function is called. The data is ignored, so we simply pass in NULL to the
|
|||
|
hello() callback function. Obviously, the "clicked" signal is emitted when
|
|||
|
we click the button with our mouse pointer.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (hello), NULL);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
We are also going to use this button to exit our program. This will
|
|||
|
illustrate how the "destroy"
|
|||
|
signal may come from either the window manager, or our program. When the
|
|||
|
button is "clicked", same as above, it calls the first hello() callback function,
|
|||
|
and then this one in the order they are set up. You may have as many
|
|||
|
callback function as you need, and all will be executed in the order you
|
|||
|
connected them. Because the gtk_widget_destroy() function accepts only a
|
|||
|
GtkWidget *widget as an argument, we use the gtk_signal_connect_object()
|
|||
|
function here instead of straight gtk_signal_connect().
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (gtk_widget_destroy),
|
|||
|
GTK_OBJECT (window));
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This is a packing call, which will be explained in depth later on. But it
|
|||
|
is fairly easy to understand. It simply tells GTK that the button is to be
|
|||
|
placed in the window where it will be displayed.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_container_add (GTK_CONTAINER (window), button);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Now that we have everything setup the way we want it to be. With all the
|
|||
|
signal handlers in place, and the button placed in the window where it
|
|||
|
should be, we ask GTK to "show" the widgets on the screen. The window
|
|||
|
widget is shown last so the whole window will pop up at once rather than
|
|||
|
seeing the window pop up, and then the button form inside of it. Although
|
|||
|
with such simple example, you'd never notice.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And of course, we call gtk_main() which waits for events to come from the X
|
|||
|
server and will call on the widgets to emit signals when these events come.
|
|||
|
<tscreen><verb>
|
|||
|
gtk_main ();
|
|||
|
</verb></tscreen>
|
|||
|
And the final return. Control returns here after gtk_quit() is called.
|
|||
|
<tscreen><verb>
|
|||
|
return 0;
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Now, when we click the mouse button on a GTK button, the
|
|||
|
widget emits a "clicked" signal. In order for us to use this information, our
|
|||
|
program sets up a signal handler to catch that signal, which dispatches the function
|
|||
|
of our choice. In our example, when the button we created is "clicked", the
|
|||
|
hello() function is called with a NULL
|
|||
|
argument, and then the next handler for this signal is called. This calls
|
|||
|
the gtk_widget_destroy() function, passing it the window widget as it's
|
|||
|
argument, destroying the window widget. This causes the window to emit the
|
|||
|
"destroy" signal, which is
|
|||
|
caught, and calls our destroy() callback function, which simply exits GTK.
|
|||
|
<p>
|
|||
|
Another course of events, is to use the window manager to kill the window.
|
|||
|
This will cause the "delete_event" to be emitted. This will call our
|
|||
|
"delete_event" handler. If we return FALSE here, the window will be left as
|
|||
|
is and nothing will happen. Returning TRUE will cause GTK to emit the
|
|||
|
"destroy" signal which of course, calls the "destroy" callback, exiting GTK.
|
|||
|
<p>
|
|||
|
Note that these signals are not the same as the Unix system
|
|||
|
signals, and are not implemented using them, although the terminology is
|
|||
|
almost identical.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Moving On
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Data Types
|
|||
|
<p>
|
|||
|
There are a few things you probably noticed in the previous examples that
|
|||
|
need explaining. The
|
|||
|
gint, gchar etc. that you see are typedefs to int and char respectively. This is done
|
|||
|
to get around that nasty dependency on the size of simple data types when doing calculations.
|
|||
|
A good example is "gint32" which will be
|
|||
|
typedef'd to a 32 bit integer for any given platform, whether it be the 64 bit
|
|||
|
alpha, or the 32 bit i386. The
|
|||
|
typedefs are very straight forward and intuitive. They are all defined in
|
|||
|
glib/glib.h (which gets included from gtk.h).
|
|||
|
<p>
|
|||
|
You'll also notice the ability to use GtkWidget when the function calls for a GtkObject.
|
|||
|
GTK is an object oriented design, and a widget is an object.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>More on Signal Handlers
|
|||
|
<p>
|
|||
|
Lets take another look at the gtk_signal_connect declaration.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_signal_connect (GtkObject *object, gchar *name,
|
|||
|
GtkSignalFunc func, gpointer func_data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Notice the gint return value ? This is a tag that identifies your callback
|
|||
|
function. As said above, you may have as many callbacks per signal and per
|
|||
|
object as you need, and each will be executed in turn, in the order they were attached.
|
|||
|
This tag allows you to remove this callback from the list by using:
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_signal_disconnect (GtkObject *object,
|
|||
|
gint id);
|
|||
|
</verb></tscreen>
|
|||
|
So, by passing in the widget you wish to remove the handler from, and the
|
|||
|
tag or id returned by one of the signal_connect functions, you can
|
|||
|
disconnect a signal handler.
|
|||
|
<p>
|
|||
|
Another function to remove all the signal handers from an object is:
|
|||
|
<tscreen><verb>
|
|||
|
gtk_signal_handlers_destroy (GtkObject *object);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This call is fairly self explanatory. It simply removes all the current
|
|||
|
signal handlers from the object passed in as the first argument.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>An Upgraded Hello World
|
|||
|
<p>
|
|||
|
Let's take a look at a slightly improved hello world with better examples
|
|||
|
of callbacks. This will also introduce us to our next topic, packing
|
|||
|
widgets.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* helloworld2.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* Our new improved callback. The data passed to this function is printed
|
|||
|
* to stdout. */
|
|||
|
void callback (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
g_print ("Hello again - %s was pressed\n", (char *) data);
|
|||
|
}
|
|||
|
|
|||
|
/* another callback */
|
|||
|
void delete_event (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
/* GtkWidget is the storage type for widgets */
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *box1;
|
|||
|
|
|||
|
/* this is called in all GTK applications. arguments are parsed from
|
|||
|
* the command line and are returned to the application. */
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
/* this is a new call, this just sets the title of our
|
|||
|
* new window to "Hello Buttons!" */
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");
|
|||
|
|
|||
|
/* Here we just set a handler for delete_event that immediately
|
|||
|
* exits GTK. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|||
|
|
|||
|
|
|||
|
/* sets the border width of the window. */
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* we create a box to pack widgets into. this is described in detail
|
|||
|
* in the "packing" section below. The box is not really visible, it
|
|||
|
* is just used as a tool to arrange widgets. */
|
|||
|
box1 = gtk_hbox_new(FALSE, 0);
|
|||
|
|
|||
|
/* put the box into the main window. */
|
|||
|
gtk_container_add (GTK_CONTAINER (window), box1);
|
|||
|
|
|||
|
/* creates a new button with the label "Button 1". */
|
|||
|
button = gtk_button_new_with_label ("Button 1");
|
|||
|
|
|||
|
/* Now when the button is clicked, we call the "callback" function
|
|||
|
* with a pointer to "button 1" as it's argument */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
|
|||
|
|
|||
|
/* instead of gtk_container_add, we pack this button into the invisible
|
|||
|
* box, which has been packed into the window. */
|
|||
|
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
|
|||
|
|
|||
|
/* always remember this step, this tells GTK that our preparation for
|
|||
|
* this button is complete, and it can be displayed now. */
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
/* do these same steps again to create a second button */
|
|||
|
button = gtk_button_new_with_label ("Button 2");
|
|||
|
|
|||
|
/* call the same callback function with a different argument,
|
|||
|
* passing a pointer to "button 2" instead. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
|
|||
|
|
|||
|
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
|
|||
|
|
|||
|
/* The order in which we show the buttons is not really important, but I
|
|||
|
* recommend showing the window last, so it all pops up at once. */
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
gtk_widget_show(box1);
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
/* rest in gtk_main and wait for the fun to begin! */
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Compile this program using the same linking arguments as our first example.
|
|||
|
You'll notice this time there is no easy way to exit the program, you have to use
|
|||
|
your window manager or command line to kill it. A good exercise for the
|
|||
|
reader would be to insert a third "Quit" button that will exit the
|
|||
|
program. You may also wish to play with the options to
|
|||
|
gtk_box_pack_start() while reading the next section.
|
|||
|
Try resizing the window, and observe the behavior.
|
|||
|
<p>
|
|||
|
Just as a side note, there is another useful define for gtk_window_new() -
|
|||
|
GTK_WINDOW_DIALOG. This interacts with the window manager a little
|
|||
|
differently and should be used for transient windows.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Packing Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
When creating an application, you'll want to put more than one button
|
|||
|
inside a window. Our first hello world example only used one widget so we
|
|||
|
could simply use a gtk_container_add call to "pack" the widget into the
|
|||
|
window. But when you want to put more than one widget into a window, how
|
|||
|
do you control where that widget is positioned ? This is where packing
|
|||
|
comes in.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Theory of Packing Boxes
|
|||
|
<p>
|
|||
|
Most packing is done by creating boxes as in the example above. These are
|
|||
|
invisible widget containers that we can pack our widgets into and come in
|
|||
|
two forms, a horizontal box, and a vertical box. When packing widgets
|
|||
|
into a horizontal box, the objects are inserted horizontally from left to
|
|||
|
right or right to left depending on the call used. In a vertical box,
|
|||
|
widgets are packed from top to bottom or vice versa. You may use any
|
|||
|
combination of boxes inside or beside other boxes to create the desired
|
|||
|
effect.
|
|||
|
<p>
|
|||
|
To create a new horizontal box, we use a call to gtk_hbox_new(), and for
|
|||
|
vertical boxes, gtk_vbox_new(). The gtk_box_pack_start() and
|
|||
|
gtk_box_pack_end() functions are used to place objects inside of these
|
|||
|
containers. The gtk_box_pack_start() function will start at the top and
|
|||
|
work its way down in a vbox, and pack left to right in an hbox.
|
|||
|
gtk_box_pack_end() will do the opposite, packing from bottom to top in a
|
|||
|
vbox, and right to left in an hbox. Using these functions allow us to
|
|||
|
right justify or left justify our widgets and may be mixed in any way to
|
|||
|
achieve the desired effect. We will use gtk_box_pack_start() in most of
|
|||
|
our examples. An object may be another container or a widget. And in
|
|||
|
fact, many widgets are actually containers themselves including the
|
|||
|
button, but we usually only use a label inside a button.
|
|||
|
<p>
|
|||
|
By using these calls, GTK knows where you want to place your widgets so it
|
|||
|
can do automatic resizing and other nifty things. there's also a number
|
|||
|
of options as to how your widgets should be packed. As you can imagine,
|
|||
|
this method gives us a quite a bit of flexibility when placing and
|
|||
|
creating widgets.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Details of Boxes
|
|||
|
<p>
|
|||
|
Because of this flexibility, packing boxes in GTK can be confusing at
|
|||
|
first. There are a lot of options, and it's not immediately obvious how
|
|||
|
they all fit together. In the end however, there are basically five
|
|||
|
different styles you can get.
|
|||
|
|
|||
|
<p>
|
|||
|
<? <CENTER> >
|
|||
|
<?
|
|||
|
<IMG SRC="packbox1.gif" VSPACE="15" HSPACE="10" WIDTH="528" HEIGHT="235"
|
|||
|
ALT="Box Packing Example Image">
|
|||
|
>
|
|||
|
<? </CENTER> >
|
|||
|
|
|||
|
Each line contains one horizontal box (hbox) with several buttons. The
|
|||
|
call to gtk_box_pack is shorthand for the call to pack each of the buttons
|
|||
|
into the hbox. Each of the buttons is packed into the hbox the same way
|
|||
|
(i.e. same arguments to the gtk_box_pack_start () function).
|
|||
|
<p>
|
|||
|
This is the declaration of the gtk_box_pack_start function.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_box_pack_start (GtkBox *box,
|
|||
|
GtkWidget *child,
|
|||
|
gint expand,
|
|||
|
gint fill,
|
|||
|
gint padding);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first argument is the box you are packing the object into, the second
|
|||
|
is this object. The objects will all be buttons for now, so we'll be
|
|||
|
packing buttons into boxes.
|
|||
|
<p>
|
|||
|
The expand argument to gtk_box_pack_start() or gtk_box_pack_end() controls
|
|||
|
whether the widgets are laid out in the box to fill in all the extra space
|
|||
|
in the box so the box is expanded to fill the area alloted to it (TRUE).
|
|||
|
Or the box is shrunk to just fit the widgets (FALSE). Setting expand to
|
|||
|
FALSE will allow you to do right and left
|
|||
|
justifying of your widgets. Otherwise, they will all expand to fit in the
|
|||
|
box, and the same effect could be achieved by using only one of
|
|||
|
gtk_box_pack_start or pack_end functions.
|
|||
|
<p>
|
|||
|
The fill argument to the gtk_box_pack functions control whether the extra
|
|||
|
space is allocated to the objects themselves (TRUE), or as extra padding
|
|||
|
in the box around these objects (FALSE). It only has an effect if the
|
|||
|
expand argument is also TRUE.
|
|||
|
<p>
|
|||
|
When creating a new box, the function looks like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget * gtk_hbox_new (gint homogeneous,
|
|||
|
gint spacing);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The homogeneous argument to gtk_hbox_new (and the same for gtk_vbox_new)
|
|||
|
controls whether each object in the box has the same size (i.e. the same
|
|||
|
width in an hbox, or the same height in a vbox). If it is set, the expand
|
|||
|
argument to the gtk_box_pack routines is always turned on.
|
|||
|
<p>
|
|||
|
What's the difference between spacing (set when the box is created) and
|
|||
|
padding (set when elements are packed)? Spacing is added between objects,
|
|||
|
and padding is added on either side of an object. The following figure
|
|||
|
should make it clearer:
|
|||
|
|
|||
|
<? <CENTER> >
|
|||
|
<?
|
|||
|
<IMG ALIGN="center" SRC="packbox2.gif" WIDTH="509" HEIGHT="213"
|
|||
|
VSPACE="15" HSPACE="10" ALT="Box Packing Example Image">
|
|||
|
>
|
|||
|
<? </CENTER> >
|
|||
|
|
|||
|
Here is the code used to create the above images. I've commented it fairly
|
|||
|
heavily so hopefully you won't have any problems following it. Compile it yourself
|
|||
|
and play with it.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Packing Demonstration Program
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
/* packbox.c */
|
|||
|
|
|||
|
#include "gtk/gtk.h"
|
|||
|
|
|||
|
void
|
|||
|
delete_event (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
/* Make a new hbox filled with button-labels. Arguments for the
|
|||
|
* variables we're interested are passed in to this function.
|
|||
|
* We do not show the box, but do show everything inside. */
|
|||
|
GtkWidget *make_box (gint homogeneous, gint spacing,
|
|||
|
gint expand, gint fill, gint padding)
|
|||
|
{
|
|||
|
GtkWidget *box;
|
|||
|
GtkWidget *button;
|
|||
|
char padstr[80];
|
|||
|
|
|||
|
/* create a new hbox with the appropriate homogeneous and spacing
|
|||
|
* settings */
|
|||
|
box = gtk_hbox_new (homogeneous, spacing);
|
|||
|
|
|||
|
/* create a series of buttons with the appropriate settings */
|
|||
|
button = gtk_button_new_with_label ("gtk_box_pack");
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("(box,");
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("button,");
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
/* create a button with the label depending on the value of
|
|||
|
* expand. */
|
|||
|
if (expand == TRUE)
|
|||
|
button = gtk_button_new_with_label ("TRUE,");
|
|||
|
else
|
|||
|
button = gtk_button_new_with_label ("FALSE,");
|
|||
|
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
/* This is the same as the button creation for "expand"
|
|||
|
* above, but uses the shorthand form. */
|
|||
|
button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
sprintf (padstr, "%d);", padding);
|
|||
|
|
|||
|
button = gtk_button_new_with_label (padstr);
|
|||
|
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
return box;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *box1;
|
|||
|
GtkWidget *box2;
|
|||
|
GtkWidget *separator;
|
|||
|
GtkWidget *label;
|
|||
|
GtkWidget *quitbox;
|
|||
|
int which;
|
|||
|
|
|||
|
/* Our init, don't forget this! :) */
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
if (argc != 2) {
|
|||
|
fprintf (stderr, "usage: packbox num, where num is 1, 2, or 3.\n");
|
|||
|
/* this just does cleanup in GTK, and exits with an exit status of 1. */
|
|||
|
gtk_exit (1);
|
|||
|
}
|
|||
|
|
|||
|
which = atoi (argv[1]);
|
|||
|
|
|||
|
/* Create our window */
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
/* You should always remember to connect the destroy signal to the
|
|||
|
* main window. This is very important for proper intuitive
|
|||
|
* behavior */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* We create a vertical box (vbox) to pack the horizontal boxes into.
|
|||
|
* This allows us to stack the horizontal boxes filled with buttons one
|
|||
|
* on top of the other in this vbox. */
|
|||
|
box1 = gtk_vbox_new (FALSE, 0);
|
|||
|
|
|||
|
/* which example to show. These correspond to the pictures above. */
|
|||
|
switch (which) {
|
|||
|
case 1:
|
|||
|
/* create a new label. */
|
|||
|
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
|
|||
|
|
|||
|
/* Align the label to the left side. We'll discuss this function and
|
|||
|
* others in the section on Widget Attributes. */
|
|||
|
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
|
|||
|
|
|||
|
/* Pack the label into the vertical box (vbox box1). Remember that
|
|||
|
* widgets added to a vbox will be packed one on top of the other in
|
|||
|
* order. */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
|
|||
|
|
|||
|
/* show the label */
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* call our make box function - homogeneous = FALSE, spacing = 0,
|
|||
|
* expand = FALSE, fill = FALSE, padding = 0 */
|
|||
|
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* call our make box function - homogeneous = FALSE, spacing = 0,
|
|||
|
* expand = FALSE, fill = FALSE, padding = 0 */
|
|||
|
box2 = make_box (FALSE, 0, TRUE, FALSE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (FALSE, 0, TRUE, TRUE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* creates a separator, we'll learn more about these later,
|
|||
|
* but they are quite simple. */
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
|
|||
|
/* pack the separator into the vbox. Remember each of these
|
|||
|
* widgets are being packed into a vbox, so they'll be stacked
|
|||
|
* vertically. */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|||
|
gtk_widget_show (separator);
|
|||
|
|
|||
|
/* create another new label, and show it. */
|
|||
|
label = gtk_label_new ("gtk_hbox_new (TRUE, 0);");
|
|||
|
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (TRUE, 0, TRUE, FALSE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (TRUE, 0, TRUE, TRUE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* another new separator. */
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|||
|
gtk_widget_show (separator);
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case 2:
|
|||
|
|
|||
|
/* create a new label, remember box1 is a vbox as created
|
|||
|
* near the beginning of main() */
|
|||
|
label = gtk_label_new ("gtk_hbox_new (FALSE, 10);");
|
|||
|
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (FALSE, 10, TRUE, FALSE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (FALSE, 10, TRUE, TRUE, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|||
|
gtk_widget_show (separator);
|
|||
|
|
|||
|
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
|
|||
|
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (FALSE, 0, TRUE, FALSE, 10);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* Args are: homogeneous, spacing, expand, fill, padding */
|
|||
|
box2 = make_box (FALSE, 0, TRUE, TRUE, 10);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
/* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|||
|
gtk_widget_show (separator);
|
|||
|
break;
|
|||
|
|
|||
|
case 3:
|
|||
|
|
|||
|
/* This demonstrates the ability to use gtk_box_pack_end() to
|
|||
|
* right justify widgets. First, we create a new box as before. */
|
|||
|
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
|
|||
|
/* create the label that will be put at the end. */
|
|||
|
label = gtk_label_new ("end");
|
|||
|
/* pack it using gtk_box_pack_end(), so it is put on the right side
|
|||
|
* of the hbox created in the make_box() call. */
|
|||
|
gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
|
|||
|
/* show the label. */
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* pack box2 into box1 (the vbox remember ? :) */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
/* a separator for the bottom. */
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
/* this explicitly sets the separator to 400 pixels wide by 5 pixels
|
|||
|
* high. This is so the hbox we created will also be 400 pixels wide,
|
|||
|
* and the "end" label will be separated from the other labels in the
|
|||
|
* hbox. Otherwise, all the widgets in the hbox would be packed as
|
|||
|
* close together as possible. */
|
|||
|
gtk_widget_set_usize (separator, 400, 5);
|
|||
|
/* pack the separator into the vbox (box1) created near the start
|
|||
|
* of main() */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|||
|
gtk_widget_show (separator);
|
|||
|
}
|
|||
|
|
|||
|
/* Create another new hbox.. remember we can use as many as we need! */
|
|||
|
quitbox = gtk_hbox_new (FALSE, 0);
|
|||
|
|
|||
|
/* Our quit button. */
|
|||
|
button = gtk_button_new_with_label ("Quit");
|
|||
|
|
|||
|
/* setup the signal to destroy the window. Remember that this will send
|
|||
|
* the "destroy" signal to the window which will be caught by our signal
|
|||
|
* handler as defined above. */
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (gtk_main_quit),
|
|||
|
GTK_OBJECT (window));
|
|||
|
/* pack the button into the quitbox.
|
|||
|
* The last 3 arguments to gtk_box_pack_start are: expand, fill, padding. */
|
|||
|
gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
|
|||
|
/* pack the quitbox into the vbox (box1) */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0);
|
|||
|
|
|||
|
/* pack the vbox (box1) which now contains all our widgets, into the
|
|||
|
* main window. */
|
|||
|
gtk_container_add (GTK_CONTAINER (window), box1);
|
|||
|
|
|||
|
/* and show everything left */
|
|||
|
gtk_widget_show (button);
|
|||
|
gtk_widget_show (quitbox);
|
|||
|
|
|||
|
gtk_widget_show (box1);
|
|||
|
/* Showing the window last so everything pops up at once. */
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
/* And of course, our main function. */
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
/* control returns here when gtk_main_quit() is called, but not when
|
|||
|
* gtk_exit is used. */
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Packing Using Tables
|
|||
|
<p>
|
|||
|
Let's take a look at another way of packing - Tables. These can be
|
|||
|
extremely useful in certain situations.
|
|||
|
|
|||
|
Using tables, we create a grid that we can place widgets in. The widgets
|
|||
|
may take up as many spaces as we specify.
|
|||
|
|
|||
|
The first thing to look at of course, is the gtk_table_new function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_table_new (gint rows,
|
|||
|
gint columns,
|
|||
|
gint homogeneous);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
The first argument is the number of rows to make in the table, while the
|
|||
|
second, obviously, the number of columns.
|
|||
|
|
|||
|
The homogeneous argument has to do with how the table's boxes are sized. If homogeneous
|
|||
|
is TRUE, the table boxes are resized to the size of the largest widget in the table.
|
|||
|
If homogeneous is FALSE, the size of a table boxes is dictated by the tallest widget
|
|||
|
in its same row, and the widest widget in its column.
|
|||
|
|
|||
|
The rows and columnts are laid out starting with 0 to n, where n was the
|
|||
|
number specified in the call to gtk_table_new. So, if you specify rows = 2 and
|
|||
|
columns = 2, the layout would look something like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
0 1 2
|
|||
|
0+----------+----------+
|
|||
|
| | |
|
|||
|
1+----------+----------+
|
|||
|
| | |
|
|||
|
2+----------+----------+
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Note that the coordinate system starts in the upper left hand corner. To place a
|
|||
|
widget into a box, use the following function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_attach (GtkTable *table,
|
|||
|
GtkWidget *child,
|
|||
|
gint left_attach,
|
|||
|
gint right_attach,
|
|||
|
gint top_attach,
|
|||
|
gint bottom_attach,
|
|||
|
gint xoptions,
|
|||
|
gint yoptions,
|
|||
|
gint xpadding,
|
|||
|
gint ypadding);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the first argument ("table") is the table you've created and the second
|
|||
|
("child") the widget you wish to place in the table.
|
|||
|
|
|||
|
The left and right attach
|
|||
|
arguments specify where to place the widget, and how many boxes to use. If you want
|
|||
|
a button in the lower right table entry
|
|||
|
of our 2x2 table, and want it to fill that entry ONLY. left_attach would be = 1,
|
|||
|
right_attach = 2, top_attach = 1, bottom_attach = 2.
|
|||
|
|
|||
|
Now, if you wanted a widget to take up the whole
|
|||
|
top row of our 2x2 table, you'd use left_attach = 0, right_attach =2, top_attach = 0,
|
|||
|
bottom_attach = 1.
|
|||
|
|
|||
|
The xoptions and yoptions are used to specify packing options and may be OR'ed
|
|||
|
together to allow multiple options.
|
|||
|
|
|||
|
These options are:
|
|||
|
<itemize>
|
|||
|
<item>GTK_FILL - If the table box is larger than the widget, and GTK_FILL is
|
|||
|
specified, the widget will expand to use all the room available.
|
|||
|
|
|||
|
<item>GTK_SHRINK - If the table widget was allocated less space then was
|
|||
|
requested (usually by the user resizing the window), then the widgets would
|
|||
|
normally just be pushed off the bottom of
|
|||
|
the window and disappear. If GTK_SHRINK is specified, the widgets will
|
|||
|
shrink with the table.
|
|||
|
|
|||
|
<item>GTK_EXPAND - This will cause the table to expand to use up any remaining
|
|||
|
space in the window.
|
|||
|
</itemize>
|
|||
|
|
|||
|
Padding is just like in boxes, creating a clear area around the widget
|
|||
|
specified in pixels.
|
|||
|
|
|||
|
gtk_table_attach() has a LOT of options. So, there's a shortcut:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_attach_defaults (GtkTable *table,
|
|||
|
GtkWidget *widget,
|
|||
|
gint left_attach,
|
|||
|
gint right_attach,
|
|||
|
gint top_attach,
|
|||
|
gint bottom_attach);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The X and Y options default to GTK_FILL | GTK_EXPAND, and X and Y padding
|
|||
|
are set to 0. The rest of the arguments are identical to the previous
|
|||
|
function.
|
|||
|
|
|||
|
We also have gtk_table_set_row_spacing() and gtk_table_set_col_spacing(). This places
|
|||
|
spacing between the rows at the specified row or column.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_set_row_spacing (GtkTable *table,
|
|||
|
gint row,
|
|||
|
gint spacing);
|
|||
|
</verb></tscreen>
|
|||
|
and
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_set_col_spacing (GtkTable *table,
|
|||
|
gint column,
|
|||
|
gint spacing);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Note that for columns, the space goes to the right of the column, and for rows,
|
|||
|
the space goes below the row.
|
|||
|
|
|||
|
You can also set a consistent spacing of all rows and/or columns with:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_set_row_spacings (GtkTable *table,
|
|||
|
gint spacing);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And,
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_table_set_col_spacings (GtkTable *table,
|
|||
|
gint spacing);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Note that with these calls, the last row and last column do not get any spacing
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Table Packing Example
|
|||
|
<p>
|
|||
|
Here we make a window with three buttons in a 2x2 table.
|
|||
|
The first two buttons will be placed in the upper row.
|
|||
|
A third, quit button, is placed in the lower row, spanning both columns.
|
|||
|
Which means it should look something like this:
|
|||
|
<p>
|
|||
|
<? <CENTER> >
|
|||
|
<?
|
|||
|
<IMG SRC="table.gif" VSPACE="15" HSPACE="10"
|
|||
|
ALT="Table Packing Example Image" WIDTH="180" HEIGHT="120">
|
|||
|
>
|
|||
|
<? </CENTER> >
|
|||
|
|
|||
|
Here's the source code:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* table.c */
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* our callback.
|
|||
|
* the data passed to this function is printed to stdout */
|
|||
|
void callback (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
g_print ("Hello again - %s was pressed\n", (char *) data);
|
|||
|
}
|
|||
|
|
|||
|
/* this callback quits the program */
|
|||
|
void delete_event (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *table;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
/* set the window title */
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Table");
|
|||
|
|
|||
|
/* set a handler for delete_event that immediately
|
|||
|
* exits GTK. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|||
|
|
|||
|
/* sets the border width of the window. */
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 20);
|
|||
|
|
|||
|
/* create a 2x2 table */
|
|||
|
table = gtk_table_new (2, 2, TRUE);
|
|||
|
|
|||
|
/* put the table in the main window */
|
|||
|
gtk_container_add (GTK_CONTAINER (window), table);
|
|||
|
|
|||
|
/* create first button */
|
|||
|
button = gtk_button_new_with_label ("button 1");
|
|||
|
|
|||
|
/* when the button is clicked, we call the "callback" function
|
|||
|
* with a pointer to "button 1" as it's argument */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (callback), (gpointer) "button 1");
|
|||
|
|
|||
|
|
|||
|
/* insert button 1 into the upper left quadrant of the table */
|
|||
|
gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 1, 0, 1);
|
|||
|
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
/* create second button */
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("button 2");
|
|||
|
|
|||
|
/* when the button is clicked, we call the "callback" function
|
|||
|
* with a pointer to "button 2" as it's argument */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (callback), (gpointer) "button 2");
|
|||
|
/* insert button 2 into the upper right quadrant of the table */
|
|||
|
gtk_table_attach_defaults (GTK_TABLE(table), button, 1, 2, 0, 1);
|
|||
|
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
/* create "Quit" button */
|
|||
|
button = gtk_button_new_with_label ("Quit");
|
|||
|
|
|||
|
/* when the button is clicked, we call the "delete_event" function
|
|||
|
* and the program exits */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|||
|
|
|||
|
/* insert the quit button into the both
|
|||
|
* lower quadrants of the table */
|
|||
|
gtk_table_attach_defaults (GTK_TABLE(table), button, 0, 2, 1, 2);
|
|||
|
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
gtk_widget_show (table);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
You can compile this program with something like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gcc -g -Wall -ansi -o table table.c -L/usr/X11R6/lib \
|
|||
|
-lgdk -lgtk -lglib -lX11 -lXext -lm
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Widget Overview
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
The general steps to creating a widget in GTK are:
|
|||
|
<enum>
|
|||
|
<item> gtk_*_new - one of various functions to create a new widget. These
|
|||
|
are all detailed in this section.
|
|||
|
|
|||
|
<item> Connect all signals we wish to use to the appropriate handlers.
|
|||
|
|
|||
|
<item> Set the attributes of the widget.
|
|||
|
|
|||
|
<item> Pack the widget into a container using the appropriate call such as
|
|||
|
gtk_container_add() or gtk_box_pack_start().
|
|||
|
|
|||
|
<item> gtk_widget_show() the widget.
|
|||
|
</enum>
|
|||
|
<p>
|
|||
|
gtk_widget_show() lets GTK know that we are done setting the attributes
|
|||
|
of the widget, and it is ready to be displayed. You may also use
|
|||
|
gtk_widget_hide to make it disappear again. The order in which you
|
|||
|
show the widgets is not important, but I suggest showing the window
|
|||
|
last so the whole window pops up at once rather than seeing the individual
|
|||
|
widgets come up on the screen as they're formed. The children of a widget
|
|||
|
(a window is a widget too)
|
|||
|
will not be displayed until the window itself is shown using the
|
|||
|
gtk_widget_show() function.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Casting
|
|||
|
<p>
|
|||
|
You'll notice as you go on, that GTK uses a type casting system. This is
|
|||
|
always done using macros that both test the ability to cast the given item,
|
|||
|
and perform the cast. Some common ones you will see are:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> GTK_WIDGET(widget)
|
|||
|
<item> GTK_OBJECT(object)
|
|||
|
<item> GTK_SIGNAL_FUNC(function)
|
|||
|
<item> GTK_CONTAINER(container)
|
|||
|
<item> GTK_WINDOW(window)
|
|||
|
<item> GTK_BOX(box)
|
|||
|
</itemize>
|
|||
|
|
|||
|
These are all used to cast arguments in functions. You'll see them in the
|
|||
|
examples, and can usually tell when to use them simply by looking at the
|
|||
|
function's declaration.
|
|||
|
|
|||
|
As you can see below in the class hierarchy, all GtkWidgets are derived from
|
|||
|
the GtkObject base class. This means you can use an widget in any place the
|
|||
|
function asks for an object - simply use the GTK_OBJECT() macro.
|
|||
|
|
|||
|
For example:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_signal_connect(GTK_OBJECT(button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC(callback_function), callback_data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This casts the button into an object, and provides a cast for the function
|
|||
|
pointer to the callback.
|
|||
|
|
|||
|
Many widgets are also containers. If you look in the class hierarchy below,
|
|||
|
you'll notice that many widgets drive from the GtkContainer class. Any one
|
|||
|
of those widgets may use with the GTK_CONTAINER macro to
|
|||
|
pass them to functions that ask for containers.
|
|||
|
|
|||
|
Unfortunately, these macros are not extensively covered in the tutorial, but I
|
|||
|
recomend taking a look through the GTK header files. It can be very
|
|||
|
educational. In fact, it's not difficult to learn how a widget works just
|
|||
|
by looking at the function declarations.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Widget Hierarchy
|
|||
|
<p>
|
|||
|
For your reference, here is the class hierarchy tree used to implement widgets.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkObject
|
|||
|
+GtkData
|
|||
|
| +GtkAdjustment
|
|||
|
| `GtkTooltips
|
|||
|
`GtkWidget
|
|||
|
+GtkContainer
|
|||
|
| +GtkBin
|
|||
|
| | +GtkAlignment
|
|||
|
| | +GtkEventBox
|
|||
|
| | +GtkFrame
|
|||
|
| | | `GtkAspectFrame
|
|||
|
| | +GtkHandleBox
|
|||
|
| | +GtkItem
|
|||
|
| | | +GtkListItem
|
|||
|
| | | +GtkMenuItem
|
|||
|
| | | | `GtkCheckMenuItem
|
|||
|
| | | | `GtkRadioMenuItem
|
|||
|
| | | `GtkTreeItem
|
|||
|
| | +GtkViewport
|
|||
|
| | `GtkWindow
|
|||
|
| | +GtkColorSelectionDialog
|
|||
|
| | +GtkDialog
|
|||
|
| | | `GtkInputDialog
|
|||
|
| | `GtkFileSelection
|
|||
|
| +GtkBox
|
|||
|
| | +GtkButtonBox
|
|||
|
| | | +GtkHButtonBox
|
|||
|
| | | `GtkVButtonBox
|
|||
|
| | +GtkHBox
|
|||
|
| | | +GtkCombo
|
|||
|
| | | `GtkStatusbar
|
|||
|
| | `GtkVBox
|
|||
|
| | +GtkColorSelection
|
|||
|
| | `GtkGammaCurve
|
|||
|
| +GtkButton
|
|||
|
| | +GtkOptionMenu
|
|||
|
| | `GtkToggleButton
|
|||
|
| | `GtkCheckButton
|
|||
|
| | `GtkRadioButton
|
|||
|
| +GtkCList
|
|||
|
| +GtkFixed
|
|||
|
| +GtkList
|
|||
|
| +GtkMenuShell
|
|||
|
| | +GtkMenuBar
|
|||
|
| | `GtkMenu
|
|||
|
| +GtkNotebook
|
|||
|
| +GtkPaned
|
|||
|
| | +GtkHPaned
|
|||
|
| | `GtkVPaned
|
|||
|
| +GtkScrolledWindow
|
|||
|
| +GtkTable
|
|||
|
| +GtkToolbar
|
|||
|
| `GtkTree
|
|||
|
+GtkDrawingArea
|
|||
|
| `GtkCurve
|
|||
|
+GtkEditable
|
|||
|
| +GtkEntry
|
|||
|
| | `GtkSpinButton
|
|||
|
| `GtkText
|
|||
|
+GtkMisc
|
|||
|
| +GtkArrow
|
|||
|
| +GtkImage
|
|||
|
| +GtkLabel
|
|||
|
| | `GtkTipsQuery
|
|||
|
| `GtkPixmap
|
|||
|
+GtkPreview
|
|||
|
+GtkProgressBar
|
|||
|
+GtkRange
|
|||
|
| +GtkScale
|
|||
|
| | +GtkHScale
|
|||
|
| | `GtkVScale
|
|||
|
| `GtkScrollbar
|
|||
|
| +GtkHScrollbar
|
|||
|
| `GtkVScrollbar
|
|||
|
+GtkRuler
|
|||
|
| +GtkHRuler
|
|||
|
| `GtkVRuler
|
|||
|
`GtkSeparator
|
|||
|
+GtkHSeparator
|
|||
|
`GtkVSeparator
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Widgets Without Windows
|
|||
|
<p>
|
|||
|
The following widgets do not have an associated window. If you want to
|
|||
|
capture events, you'll have to use the GtkEventBox. See the section on
|
|||
|
<ref id="sec_The_EventBox_Widget" name="The EventBox Widget">
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkAlignment
|
|||
|
GtkArrow
|
|||
|
GtkBin
|
|||
|
GtkBox
|
|||
|
GtkImage
|
|||
|
GtkItem
|
|||
|
GtkLabel
|
|||
|
GtkPaned
|
|||
|
GtkPixmap
|
|||
|
GtkScrolledWindow
|
|||
|
GtkSeparator
|
|||
|
GtkTable
|
|||
|
GtkViewport
|
|||
|
GtkAspectFrame
|
|||
|
GtkFrame
|
|||
|
GtkVPaned
|
|||
|
GtkHPaned
|
|||
|
GtkVBox
|
|||
|
GtkHBox
|
|||
|
GtkVSeparator
|
|||
|
GtkHSeparator
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
We'll further our exploration of GTK by examining each widget in turn,
|
|||
|
creating a few simple functions to display them. Another good source is
|
|||
|
the testgtk.c program that comes with GTK. It can be found in
|
|||
|
gtk/testgtk.c.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>The Button Widget
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Normal Buttons
|
|||
|
<p>
|
|||
|
We've almost seen all there is to see of the button widget. It's pretty
|
|||
|
simple. There is however two ways to create a button. You can use the
|
|||
|
gtk_button_new_with_label() to create a button with a label, or use
|
|||
|
gtk_button_new() to create a blank button. It's then up to you to pack a
|
|||
|
label or pixmap into this new button. To do this, create a new box, and
|
|||
|
then pack your objects into this box using the usual gtk_box_pack_start,
|
|||
|
and then use gtk_container_add to pack the box into the button.
|
|||
|
<p>
|
|||
|
Here's an example of using gtk_button_new to create a button with a
|
|||
|
picture and a label in it. I've broken the code to create a box up from
|
|||
|
the rest so you can use it in your programs.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* buttons.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* create a new hbox with an image and a label packed into it
|
|||
|
* and return the box.. */
|
|||
|
|
|||
|
GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text)
|
|||
|
{
|
|||
|
GtkWidget *box1;
|
|||
|
GtkWidget *label;
|
|||
|
GtkWidget *pixmapwid;
|
|||
|
GdkPixmap *pixmap;
|
|||
|
GdkBitmap *mask;
|
|||
|
GtkStyle *style;
|
|||
|
|
|||
|
/* create box for xpm and label */
|
|||
|
box1 = gtk_hbox_new (FALSE, 0);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (box1), 2);
|
|||
|
|
|||
|
/* get style of button.. I assume it's to get the background color.
|
|||
|
* if someone knows the real reason, please enlighten me. */
|
|||
|
style = gtk_widget_get_style(parent);
|
|||
|
|
|||
|
/* now on to the xpm stuff.. load xpm */
|
|||
|
pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask,
|
|||
|
&style->bg[GTK_STATE_NORMAL],
|
|||
|
xpm_filename);
|
|||
|
pixmapwid = gtk_pixmap_new (pixmap, mask);
|
|||
|
|
|||
|
/* create label for button */
|
|||
|
label = gtk_label_new (label_text);
|
|||
|
|
|||
|
/* pack the pixmap and label into the box */
|
|||
|
gtk_box_pack_start (GTK_BOX (box1),
|
|||
|
pixmapwid, FALSE, FALSE, 3);
|
|||
|
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3);
|
|||
|
|
|||
|
gtk_widget_show(pixmapwid);
|
|||
|
gtk_widget_show(label);
|
|||
|
|
|||
|
return (box1);
|
|||
|
}
|
|||
|
|
|||
|
/* our usual callback function */
|
|||
|
void callback (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
g_print ("Hello again - %s was pressed\n", (char *) data);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
/* GtkWidget is the storage type for widgets */
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *box1;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!");
|
|||
|
|
|||
|
/* It's a good idea to do this for all windows. */
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
|
|||
|
/* sets the border width of the window. */
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
gtk_widget_realize(window);
|
|||
|
|
|||
|
/* create a new button */
|
|||
|
button = gtk_button_new ();
|
|||
|
|
|||
|
/* You should be getting used to seeing most of these functions by now */
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (callback), (gpointer) "cool button");
|
|||
|
|
|||
|
/* this calls our box creating function */
|
|||
|
box1 = xpm_label_box(window, "info.xpm", "cool button");
|
|||
|
|
|||
|
/* pack and show all our widgets */
|
|||
|
gtk_widget_show(box1);
|
|||
|
|
|||
|
gtk_container_add (GTK_CONTAINER (button), box1);
|
|||
|
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
gtk_container_add (GTK_CONTAINER (window), button);
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
/* rest in gtk_main and wait for the fun to begin! */
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The xpm_label_box function could be used to pack xpm's and labels into any
|
|||
|
widget that can be a container.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Toggle Buttons
|
|||
|
<p>
|
|||
|
Toggle buttons are very similar to normal buttons, except they will always
|
|||
|
be in one of two states, alternated by a click. They may be depressed, and
|
|||
|
when you click again, they will pop back up. Click again, and they will pop
|
|||
|
back down.
|
|||
|
|
|||
|
Toggle buttons are the basis for check buttons and radio buttons, as such,
|
|||
|
many of the calls used for toggle buttons are inherited by radio and check
|
|||
|
buttons. I will point these out when we come to them.
|
|||
|
|
|||
|
Creating a new toggle button:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_toggle_button_new (void);
|
|||
|
|
|||
|
GtkWidget* gtk_toggle_button_new_with_label (gchar *label);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
As you can imagine, these work identically to the normal button widget
|
|||
|
calls. The first creates a blank toggle button, and the second, a button
|
|||
|
with a label widget already packed into it.
|
|||
|
<p>
|
|||
|
To retrieve the state of the toggle widget, including radio and check
|
|||
|
buttons, we use a macro as shown in our example below. This tests the state
|
|||
|
of the toggle in a callback. The signal of interest emitted to us by toggle
|
|||
|
buttons (the toggle button, check button, and radio button widgets), is the
|
|||
|
"toggled" signal. To check the state of these buttons, set up a signal
|
|||
|
handler to catch the toggled signal, and use the macro to determine it's
|
|||
|
state. The callback will look something like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void toggle_button_callback (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
if (GTK_TOGGLE_BUTTON (widget)->active)
|
|||
|
{
|
|||
|
/* If control reaches here, the toggle button is down */
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
/* If control reaches here, the toggle button is up */
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!--
|
|||
|
|
|||
|
COMMENTED!
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_toggle_button_get_type (void);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
No idea... they all have this, but I dunno what it is :)
|
|||
|
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button,
|
|||
|
gint draw_indicator);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
No idea.
|
|||
|
-->
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
|
|||
|
gint state);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
The above call can be used to set the state of the toggle button, and it's
|
|||
|
children the radio and check buttons. Passing
|
|||
|
in your created button as the first argument, and a TRUE or FALSE
|
|||
|
for the second state argument to specify whether it should be up (released) or
|
|||
|
down (depressed). Default is up, or FALSE.
|
|||
|
|
|||
|
Note that when you use the gtk_toggle_button_set_state() function, and the
|
|||
|
state is actually changed, it causes
|
|||
|
the "clicked" signal to be emitted from the button.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_toggle_button_toggled (GtkToggleButton *toggle_button);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This simply toggles the button, and emits the "toggled" signal.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Check Buttons
|
|||
|
<p>
|
|||
|
Check buttons inherent many properties and functions from the the toggle buttons above,
|
|||
|
but look a little
|
|||
|
different. Rather than being buttons with text inside them, they are small
|
|||
|
squares with the text to the right of them. These are often seen for
|
|||
|
toggling options on and off in applications.
|
|||
|
|
|||
|
The two creation functions are the same as for the normal button.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_check_button_new (void);
|
|||
|
|
|||
|
GtkWidget* gtk_check_button_new_with_label (gchar *label);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The new_with_label function creates a check button with a label beside it.
|
|||
|
|
|||
|
Checking the state of the check button is identical to that of the toggle
|
|||
|
button.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Radio Buttons
|
|||
|
<p>
|
|||
|
Radio buttons are similar to check buttons except they are grouped so that
|
|||
|
only one may be selected/depressed at a time. This is good for places in
|
|||
|
your application where you need to select from a short list of options.
|
|||
|
|
|||
|
Creating a new radio button is done with one of these calls:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_radio_button_new (GSList *group);
|
|||
|
|
|||
|
GtkWidget* gtk_radio_button_new_with_label (GSList *group,
|
|||
|
gchar *label);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
You'll notice the extra argument to these calls. They require a group to
|
|||
|
perform they're duty properly. The first call should pass NULL as the first
|
|||
|
argument. Then create a group using:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GSList* gtk_radio_button_group (GtkRadioButton *radio_button);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
The important thing to remember is that gtk_radio_button_group must be
|
|||
|
called for each new button added to the group, with the previous button
|
|||
|
passed in as an argument. The result is then passed into the call to
|
|||
|
gtk_radio_button_new or gtk_radio_button_new_with_label. This allows a
|
|||
|
chain of buttons to be established. The example below should make this
|
|||
|
clear.
|
|||
|
|
|||
|
It is also a good idea to explicitly set which button should be the
|
|||
|
default depressed button with:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
|
|||
|
gint state);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This is described in the section on toggle buttons, and works in exactly the
|
|||
|
same way.
|
|||
|
<p>
|
|||
|
The following example creates a radio button group with three buttons.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* radiobuttons.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include <glib.h>
|
|||
|
|
|||
|
void close_application( GtkWidget *widget, gpointer *data ) {
|
|||
|
gtk_main_quit();
|
|||
|
}
|
|||
|
|
|||
|
main(int argc,char *argv[])
|
|||
|
{
|
|||
|
static GtkWidget *window = NULL;
|
|||
|
GtkWidget *box1;
|
|||
|
GtkWidget *box2;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *separator;
|
|||
|
GSList *group;
|
|||
|
|
|||
|
gtk_init(&argc,&argv);
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC(close_application),
|
|||
|
NULL);
|
|||
|
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "radio buttons");
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 0);
|
|||
|
|
|||
|
box1 = gtk_vbox_new (FALSE, 0);
|
|||
|
gtk_container_add (GTK_CONTAINER (window), box1);
|
|||
|
gtk_widget_show (box1);
|
|||
|
|
|||
|
box2 = gtk_vbox_new (FALSE, 10);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (box2), 10);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
button = gtk_radio_button_new_with_label (NULL, "button1");
|
|||
|
gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
group = gtk_radio_button_group (GTK_RADIO_BUTTON (button));
|
|||
|
button = gtk_radio_button_new_with_label(group, "button2");
|
|||
|
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (button), TRUE);
|
|||
|
gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
group = gtk_radio_button_group (GTK_RADIO_BUTTON (button));
|
|||
|
button = gtk_radio_button_new_with_label(group, "button3");
|
|||
|
gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
separator = gtk_hseparator_new ();
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 0);
|
|||
|
gtk_widget_show (separator);
|
|||
|
|
|||
|
box2 = gtk_vbox_new (FALSE, 10);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (box2), 10);
|
|||
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, TRUE, 0);
|
|||
|
gtk_widget_show (box2);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("close");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC(close_application),
|
|||
|
GTK_OBJECT (window));
|
|||
|
gtk_box_pack_start (GTK_BOX (box2), button, TRUE, TRUE, 0);
|
|||
|
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
|
|||
|
gtk_widget_grab_default (button);
|
|||
|
gtk_widget_show (button);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main();
|
|||
|
return(0);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
You can shorten this slightly by using the following syntax, which
|
|||
|
removes the need for a variable to hold the list of buttons:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
button2 = gtk_radio_button_new_with_label(
|
|||
|
gtk_radio_button_group (GTK_RADIO_BUTTON (button1)),
|
|||
|
"button2");
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> Miscallaneous Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Labels
|
|||
|
<p>
|
|||
|
Labels are used a lot in GTK, and are relatively simple. Labels emit no
|
|||
|
signals as they do not have an associated X window. If you need to catch
|
|||
|
signals, or do clipping, use the EventBox widget.
|
|||
|
|
|||
|
To create a new label, use:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_label_new (char *str);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Where the sole argument is the string you wish the label to display.
|
|||
|
|
|||
|
To change the label's text after creation, use the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_label_set (GtkLabel *label,
|
|||
|
char *str);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the first argument is the label you created previously (casted using
|
|||
|
the GTK_LABEL() macro), and the second is the new string.
|
|||
|
|
|||
|
The space needed for the new string will be automatically adjusted if needed.
|
|||
|
|
|||
|
To retrieve the current string, use:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_label_get (GtkLabel *label,
|
|||
|
char **str);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Where the first arguement is the label you've created, and the second, the
|
|||
|
return for the string.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>The Tooltips Widget
|
|||
|
<p>
|
|||
|
These are the little text strings that pop up when you leave your pointer
|
|||
|
over a button or other widget for a few seconds. They are easy to use, so I
|
|||
|
will just explain them without giving an example. If you want to see some
|
|||
|
code, take a look at the testgtk.c program distributed with GDK.
|
|||
|
<p>
|
|||
|
Some widgets (such as the label) will not work with tooltips.
|
|||
|
<p>
|
|||
|
The first call you will use to create a new tooltip. You only need to do
|
|||
|
this once in a given function. The GtkTooltip this function returns can be
|
|||
|
used to create multiple tooltips.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkTooltips *gtk_tooltips_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Once you have created a new tooltip, and the widget you wish to use it on,
|
|||
|
simply use this call to set it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_set_tips (GtkTooltips *tooltips,
|
|||
|
GtkWidget *widget,
|
|||
|
gchar *tips_text);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first argument is the tooltip you've already created, followed by the
|
|||
|
widget you wish to have this tooltip pop up for, and the text you wish it to
|
|||
|
say.
|
|||
|
<p>
|
|||
|
Here's a short example:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkTooltips *tooltips;
|
|||
|
GtkWidget *button;
|
|||
|
...
|
|||
|
tooltips = gtk_tooltips_new ();
|
|||
|
button = gtk_button_new_with_label ("button 1");
|
|||
|
...
|
|||
|
gtk_tooltips_set_tips (tooltips, button, "This is button 1");
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
|
|||
|
There are other calls used with tooltips. I will just list them with a
|
|||
|
brief description of what they do.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_destroy (GtkTooltips *tooltips);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Destroy the created tooltips.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_enable (GtkTooltips *tooltips);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Enable a disabled set of tooltips.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_disable (GtkTooltips *tooltips);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Disable an enabled set of tooltips.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_set_delay (GtkTooltips *tooltips,
|
|||
|
gint delay);
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
Sets how many milliseconds you have to hold you pointer over the widget before the
|
|||
|
tooltip will pop up. The default is 1000 milliseconds or 1 second.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_set_tips (GtkTooltips *tooltips,
|
|||
|
GtkWidget *widget,
|
|||
|
gchar *tips_text);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Change the tooltip text of an already created tooltip.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_tooltips_set_colors (GtkTooltips *tooltips,
|
|||
|
GdkColor *background,
|
|||
|
GdkColor *foreground);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Set the foreground and background color of the tooltips. Again, I have no
|
|||
|
idea how to specify the colors.
|
|||
|
<p>
|
|||
|
And that's all the functions associated with tooltips. More than you'll
|
|||
|
ever want to know :)
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Progress Bars
|
|||
|
<p>
|
|||
|
Progress bars are used to show the status of an operation. They are pretty
|
|||
|
easy to use, as you will see with the code below. But first lets start out
|
|||
|
with the call to create a new progress bar.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_progress_bar_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Now that the progress bar has been created we can use it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first argument is the progress bar you wish to operate on, and the second
|
|||
|
argument is the amount 'completed', meaning the amount the progress bar has
|
|||
|
been filled from 0-100% (a real number between 0 and 1).
|
|||
|
|
|||
|
Progress Bars are usually used with timeouts or other such functions (see
|
|||
|
section on <ref id="sec_timeouts" name="Timeouts, I/O and Idle Functions">)
|
|||
|
to give the illusion of multitasking. All will employ
|
|||
|
the gtk_progress_bar_update function in the same manner.
|
|||
|
|
|||
|
Here is an example of the progress bar, updated using timeouts. This
|
|||
|
code also shows you how to reset the Progress Bar.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* progressbar.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
static int ptimer = 0;
|
|||
|
int pstat = TRUE;
|
|||
|
|
|||
|
/* This function increments and updates the progress bar, it also resets
|
|||
|
the progress bar if pstat is FALSE */
|
|||
|
gint progress (gpointer data)
|
|||
|
{
|
|||
|
gfloat pvalue;
|
|||
|
|
|||
|
/* get the current value of the progress bar */
|
|||
|
pvalue = GTK_PROGRESS_BAR (data)->percentage;
|
|||
|
|
|||
|
if ((pvalue >= 1.0) || (pstat == FALSE)) {
|
|||
|
pvalue = 0.0;
|
|||
|
pstat = TRUE;
|
|||
|
}
|
|||
|
pvalue += 0.01;
|
|||
|
|
|||
|
gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
/* This function signals a reset of the progress bar */
|
|||
|
void progress_r (void)
|
|||
|
{
|
|||
|
pstat = FALSE;
|
|||
|
}
|
|||
|
|
|||
|
void destroy (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *label;
|
|||
|
GtkWidget *table;
|
|||
|
GtkWidget *pbar;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (destroy), NULL);
|
|||
|
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
table = gtk_table_new(3,2,TRUE);
|
|||
|
gtk_container_add (GTK_CONTAINER (window), table);
|
|||
|
|
|||
|
label = gtk_label_new ("Progress Bar Example");
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1);
|
|||
|
gtk_widget_show(label);
|
|||
|
|
|||
|
/* Create a new progress bar, pack it into the table, and show it */
|
|||
|
pbar = gtk_progress_bar_new ();
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2);
|
|||
|
gtk_widget_show (pbar);
|
|||
|
|
|||
|
/* Set the timeout to handle automatic updating of the progress bar */
|
|||
|
ptimer = gtk_timeout_add (100, progress, pbar);
|
|||
|
|
|||
|
/* This button signals the progress bar to be reset */
|
|||
|
button = gtk_button_new_with_label ("Reset");
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (progress_r), NULL);
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("Cancel");
|
|||
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (destroy), NULL);
|
|||
|
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
gtk_widget_show(table);
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
In this small program there are four areas that concern the general operation
|
|||
|
of Progress Bars, we will look at them in the order they are called.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
pbar = gtk_progress_bar_new ();
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This code creates a new progress bar, called pbar.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
ptimer = gtk_timeout_add (100, progress, pbar);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This code, uses timeouts to enable a constant time interval, timeouts are
|
|||
|
not necessary in the use of Progress Bars.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
pvalue = GTK_PROGRESS_BAR (data)->percentage;
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This code assigns the current value of the percentage bar to pvalue.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Finally, this code updates the progress bar with the value of pvalue
|
|||
|
|
|||
|
And that is all there is to know about Progress Bars, enjoy.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Dialogs
|
|||
|
<p>
|
|||
|
|
|||
|
The Dialog widget is very simple, and is actually just a window with a few
|
|||
|
things pre-packed into it for you. The structure for a Dialog is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct GtkDialog
|
|||
|
{
|
|||
|
GtkWindow window;
|
|||
|
|
|||
|
GtkWidget *vbox;
|
|||
|
GtkWidget *action_area;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
So you see, it simple creates a window, and then packs a vbox into the top,
|
|||
|
then a seperator, and then an hbox for the "action_area".
|
|||
|
|
|||
|
The Dialog widget can be used for pop-up messages to the user, and
|
|||
|
other similar tasks. It is really basic, and there is only one
|
|||
|
function for the dialog box, which is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_dialog_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
So to create a new dialog box, use,
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget window;
|
|||
|
window = gtk_dialog_new ();
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This will create the dialog box, and it is now up to you to use it.
|
|||
|
you could pack a button in the action_area by doing something like so:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
button = ...
|
|||
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button,
|
|||
|
TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (button);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
And you could add to the vbox area by packing, for instance, a label
|
|||
|
in it, try something like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
label = gtk_label_new ("Dialogs are groovy");
|
|||
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE,
|
|||
|
TRUE, 0);
|
|||
|
gtk_widget_show (label);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
As an example in using the dialog box, you could put two buttons in
|
|||
|
the action_area, a Cancel button and an Ok button, and a label in the vbox
|
|||
|
area, asking the user a question or giving an error etc. Then you could
|
|||
|
attach a different signal to each of the buttons and perform the
|
|||
|
operation the user selects.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Pixmaps
|
|||
|
<p>
|
|||
|
Pixmaps are data structures that contain pictures. These pictures can be
|
|||
|
used in various places, but most visibly as icons on the X-Windows desktop,
|
|||
|
or as cursors. A bitmap is a 2-color pixmap.
|
|||
|
|
|||
|
To use pixmaps in GTK, we must first build a GdkPixmap structure using
|
|||
|
routines from the GDK layer. Pixmaps can either be created from in-memory
|
|||
|
data, or from data read from a file. We'll go through each of the calls
|
|||
|
to create a pixmap.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window,
|
|||
|
gchar *data,
|
|||
|
gint width,
|
|||
|
gint height );
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This routine is used to create a single-plane pixmap (2 colors) from data in
|
|||
|
memory. Each bit of the data represents whether that pixel is off or on.
|
|||
|
Width and height are in pixels. The GdkWindow pointer is to the current
|
|||
|
window, since a pixmap resources are meaningful only in the context of the
|
|||
|
screen where it is to be displayed.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window,
|
|||
|
gchar *data,
|
|||
|
gint width,
|
|||
|
gint height,
|
|||
|
gint depth,
|
|||
|
GdkColor *fg,
|
|||
|
GdkColor *bg );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This is used to create a pixmap of the given depth (number of colors) from
|
|||
|
the bitmap data specified. fg and bg are the foreground and background
|
|||
|
color to use.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window,
|
|||
|
GdkBitmap **mask,
|
|||
|
GdkColor *transparent_color,
|
|||
|
const gchar *filename );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
XPM format is a readable pixmap representation for the X Window System. It
|
|||
|
is widely used and many different utilities are available for creating image
|
|||
|
files in this format. The file specified by filename must contain an image
|
|||
|
in that format and it is loaded into the pixmap structure. The mask specifies
|
|||
|
what bits of the pixmap are opaque. All other bits are colored using the
|
|||
|
color specified by transparent_color. An example using this follows below.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window,
|
|||
|
GdkBitmap **mask,
|
|||
|
GdkColor *transparent_color,
|
|||
|
gchar **data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Small images can be incorporated into a program as data in the XPM format.
|
|||
|
A pixmap is created using this data, instead of reading it from a file.
|
|||
|
An example of such data is
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* XPM */
|
|||
|
static const char * xpm_data[] = {
|
|||
|
"16 16 3 1",
|
|||
|
" c None",
|
|||
|
". c #000000000000",
|
|||
|
"X c #FFFFFFFFFFFF",
|
|||
|
" ",
|
|||
|
" ...... ",
|
|||
|
" .XXX.X. ",
|
|||
|
" .XXX.XX. ",
|
|||
|
" .XXX.XXX. ",
|
|||
|
" .XXX..... ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" ......... ",
|
|||
|
" ",
|
|||
|
" "};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gdk_pixmap_destroy( GdkPixmap *pixmap );
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
When we're done using a pixmap and not likely to reuse it again soon,
|
|||
|
it is a good idea to release the resource using gdk_pixmap_destroy. Pixmaps
|
|||
|
should be considered a precious resource.
|
|||
|
|
|||
|
|
|||
|
Once we've created a pixmap, we can display it as a GTK widget. We must
|
|||
|
create a pixmap widget to contain the GDK pixmap. This is done using
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap,
|
|||
|
GdkBitmap *mask );
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
The other pixmap widget calls are
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_pixmap_get_type( void );
|
|||
|
void gtk_pixmap_set( GtkPixmap *pixmap,
|
|||
|
GdkPixmap *val,
|
|||
|
GdkBitmap *mask);
|
|||
|
void gtk_pixmap_get( GtkPixmap *pixmap,
|
|||
|
GdkPixmap **val,
|
|||
|
GdkBitmap **mask);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
gtk_pixmap_set is used to change the pixmap that the widget is currently
|
|||
|
managing. Val is the pixmap created using GDK.
|
|||
|
|
|||
|
The following is an example of using a pixmap in a button.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* pixmap.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
|
|||
|
/* XPM data of Open-File icon */
|
|||
|
static const char * xpm_data[] = {
|
|||
|
"16 16 3 1",
|
|||
|
" c None",
|
|||
|
". c #000000000000",
|
|||
|
"X c #FFFFFFFFFFFF",
|
|||
|
" ",
|
|||
|
" ...... ",
|
|||
|
" .XXX.X. ",
|
|||
|
" .XXX.XX. ",
|
|||
|
" .XXX.XXX. ",
|
|||
|
" .XXX..... ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" .XXXXXXX. ",
|
|||
|
" ......... ",
|
|||
|
" ",
|
|||
|
" "};
|
|||
|
|
|||
|
|
|||
|
/* when invoked (via signal delete_event), terminates the application.
|
|||
|
*/
|
|||
|
void close_application( GtkWidget *widget, gpointer *data ) {
|
|||
|
gtk_main_quit();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* is invoked when the button is clicked. It just prints a message.
|
|||
|
*/
|
|||
|
void button_clicked( GtkWidget *widget, gpointer *data ) {
|
|||
|
printf( "button clicked\n" );
|
|||
|
}
|
|||
|
|
|||
|
int main( int argc, char *argv[] )
|
|||
|
{
|
|||
|
/* GtkWidget is the storage type for widgets */
|
|||
|
GtkWidget *window, *pixmapwid, *button;
|
|||
|
GdkPixmap *pixmap;
|
|||
|
GdkBitmap *mask;
|
|||
|
GtkStyle *style;
|
|||
|
|
|||
|
/* create the main window, and attach delete_event signal to terminating
|
|||
|
the application */
|
|||
|
gtk_init( &argc, &argv );
|
|||
|
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
|
|||
|
gtk_signal_connect( GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (close_application), NULL );
|
|||
|
gtk_container_border_width( GTK_CONTAINER (window), 10 );
|
|||
|
gtk_widget_show( window );
|
|||
|
|
|||
|
/* now for the pixmap from gdk */
|
|||
|
style = gtk_widget_get_style( window );
|
|||
|
pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask,
|
|||
|
&style->bg[GTK_STATE_NORMAL],
|
|||
|
(gchar **)xpm_data );
|
|||
|
|
|||
|
/* a pixmap widget to contain the pixmap */
|
|||
|
pixmapwid = gtk_pixmap_new( pixmap, mask );
|
|||
|
gtk_widget_show( pixmapwid );
|
|||
|
|
|||
|
/* a button to contain the pixmap widget */
|
|||
|
button = gtk_button_new();
|
|||
|
gtk_container_add( GTK_CONTAINER(button), pixmapwid );
|
|||
|
gtk_container_add( GTK_CONTAINER(window), button );
|
|||
|
gtk_widget_show( button );
|
|||
|
|
|||
|
gtk_signal_connect( GTK_OBJECT(button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC(button_clicked), NULL );
|
|||
|
|
|||
|
/* show the window */
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
|
|||
|
To load a file from an XPM data file called icon0.xpm in the current
|
|||
|
directory, we would have created the pixmap thus
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* load a pixmap from a file */
|
|||
|
pixmap = gdk_pixmap_create_from_xpm( window->window, &mask,
|
|||
|
&style->bg[GTK_STATE_NORMAL],
|
|||
|
"./icon0.xpm" );
|
|||
|
pixmapwid = gtk_pixmap_new( pixmap, mask );
|
|||
|
gtk_widget_show( pixmapwid );
|
|||
|
gtk_container_add( GTK_CONTAINER(window), pixmapwid );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Using Shapes
|
|||
|
<p>
|
|||
|
A disadvantage of using pixmaps is that the displayed object is always
|
|||
|
rectangular, regardless of the image. We would like to create desktops
|
|||
|
and applications with icons that have more natural shapes. For example,
|
|||
|
for a game interface, we would like to have round buttons to push. The
|
|||
|
way to do this is using shaped windows.
|
|||
|
|
|||
|
A shaped window is simply a pixmap where the background pixels are
|
|||
|
transparent. This way, when the background image is multi-colored, we
|
|||
|
don't overwrite it with a rectangular, non-matching border around our
|
|||
|
icon. The following example displays a full wheelbarrow image on the
|
|||
|
desktop.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* wheelbarrow.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* XPM */
|
|||
|
static char * WheelbarrowFull_xpm[] = {
|
|||
|
"48 48 64 1",
|
|||
|
" c None",
|
|||
|
". c #DF7DCF3CC71B",
|
|||
|
"X c #965875D669A6",
|
|||
|
"o c #71C671C671C6",
|
|||
|
"O c #A699A289A699",
|
|||
|
"+ c #965892489658",
|
|||
|
"@ c #8E38410330C2",
|
|||
|
"# c #D75C7DF769A6",
|
|||
|
"$ c #F7DECF3CC71B",
|
|||
|
"% c #96588A288E38",
|
|||
|
"& c #A69992489E79",
|
|||
|
"* c #8E3886178E38",
|
|||
|
"= c #104008200820",
|
|||
|
"- c #596510401040",
|
|||
|
"; c #C71B30C230C2",
|
|||
|
": c #C71B9A699658",
|
|||
|
"> c #618561856185",
|
|||
|
", c #20811C712081",
|
|||
|
"< c #104000000000",
|
|||
|
"1 c #861720812081",
|
|||
|
"2 c #DF7D4D344103",
|
|||
|
"3 c #79E769A671C6",
|
|||
|
"4 c #861782078617",
|
|||
|
"5 c #41033CF34103",
|
|||
|
"6 c #000000000000",
|
|||
|
"7 c #49241C711040",
|
|||
|
"8 c #492445144924",
|
|||
|
"9 c #082008200820",
|
|||
|
"0 c #69A618611861",
|
|||
|
"q c #B6DA71C65144",
|
|||
|
"w c #410330C238E3",
|
|||
|
"e c #CF3CBAEAB6DA",
|
|||
|
"r c #71C6451430C2",
|
|||
|
"t c #EFBEDB6CD75C",
|
|||
|
"y c #28A208200820",
|
|||
|
"u c #186110401040",
|
|||
|
"i c #596528A21861",
|
|||
|
"p c #71C661855965",
|
|||
|
"a c #A69996589658",
|
|||
|
"s c #30C228A230C2",
|
|||
|
"d c #BEFBA289AEBA",
|
|||
|
"f c #596545145144",
|
|||
|
"g c #30C230C230C2",
|
|||
|
"h c #8E3882078617",
|
|||
|
"j c #208118612081",
|
|||
|
"k c #38E30C300820",
|
|||
|
"l c #30C2208128A2",
|
|||
|
"z c #38E328A238E3",
|
|||
|
"x c #514438E34924",
|
|||
|
"c c #618555555965",
|
|||
|
"v c #30C2208130C2",
|
|||
|
"b c #38E328A230C2",
|
|||
|
"n c #28A228A228A2",
|
|||
|
"m c #41032CB228A2",
|
|||
|
"M c #104010401040",
|
|||
|
"N c #492438E34103",
|
|||
|
"B c #28A2208128A2",
|
|||
|
"V c #A699596538E3",
|
|||
|
"C c #30C21C711040",
|
|||
|
"Z c #30C218611040",
|
|||
|
"A c #965865955965",
|
|||
|
"S c #618534D32081",
|
|||
|
"D c #38E31C711040",
|
|||
|
"F c #082000000820",
|
|||
|
" ",
|
|||
|
" .XoO ",
|
|||
|
" +@#$%o& ",
|
|||
|
" *=-;#::o+ ",
|
|||
|
" >,<12#:34 ",
|
|||
|
" 45671#:X3 ",
|
|||
|
" +89<02qwo ",
|
|||
|
"e* >,67;ro ",
|
|||
|
"ty> 459@>+&& ",
|
|||
|
"$2u+ ><ipas8* ",
|
|||
|
"%$;=* *3:.Xa.dfg> ",
|
|||
|
"Oh$;ya *3d.a8j,Xe.d3g8+ ",
|
|||
|
" Oh$;ka *3d$a8lz,,xxc:.e3g54 ",
|
|||
|
" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ",
|
|||
|
" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ",
|
|||
|
" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ",
|
|||
|
" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ",
|
|||
|
" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ",
|
|||
|
" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
|
|||
|
" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
|
|||
|
" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
|
|||
|
" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
|
|||
|
" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en",
|
|||
|
" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
|
|||
|
" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
|
|||
|
" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ",
|
|||
|
" @26MvzxNzvlbwfpdettttttttttt.c,n& ",
|
|||
|
" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ",
|
|||
|
" p;<69BvwwsszslllbBlllllllu<5+ ",
|
|||
|
" OS0y6FBlvvvzvzss,u=Blllj=54 ",
|
|||
|
" c1-699Blvlllllu7k96MMMg4 ",
|
|||
|
" *10y8n6FjvllllB<166668 ",
|
|||
|
" S-kg+>666<M<996-y6n<8* ",
|
|||
|
" p71=4 m69996kD8Z-66698&& ",
|
|||
|
" &i0ycm6n4 ogk17,0<6666g ",
|
|||
|
" N-k-<> >=01-kuu666> ",
|
|||
|
" ,6ky& &46-10ul,66, ",
|
|||
|
" Ou0<> o66y<ulw<66& ",
|
|||
|
" *kk5 >66By7=xu664 ",
|
|||
|
" <<M4 466lj<Mxu66o ",
|
|||
|
" *>> +66uv,zN666* ",
|
|||
|
" 566,xxj669 ",
|
|||
|
" 4666FF666> ",
|
|||
|
" >966666M ",
|
|||
|
" oM6668+ ",
|
|||
|
" *4 ",
|
|||
|
" ",
|
|||
|
" "};
|
|||
|
|
|||
|
|
|||
|
/* when invoked (via signal delete_event), terminates the application.
|
|||
|
*/
|
|||
|
void close_application( GtkWidget *widget, gpointer *data ) {
|
|||
|
gtk_main_quit();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
/* GtkWidget is the storage type for widgets */
|
|||
|
GtkWidget *window, *pixmap, *fixed;
|
|||
|
GdkPixmap *gdk_pixmap;
|
|||
|
GdkBitmap *mask;
|
|||
|
GtkStyle *style;
|
|||
|
GdkGC *gc;
|
|||
|
|
|||
|
/* create the main window, and attach delete_event signal to terminate
|
|||
|
the application. Note that the main window will not have a titlebar
|
|||
|
since we're making it a popup. */
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
window = gtk_window_new( GTK_WINDOW_POPUP );
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (close_application), NULL);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
/* now for the pixmap and the pixmap widget */
|
|||
|
style = gtk_widget_get_default_style();
|
|||
|
gc = style->black_gc;
|
|||
|
gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask,
|
|||
|
&style->bg[GTK_STATE_NORMAL],
|
|||
|
WheelbarrowFull_xpm );
|
|||
|
pixmap = gtk_pixmap_new( gdk_pixmap, mask );
|
|||
|
gtk_widget_show( pixmap );
|
|||
|
|
|||
|
/* To display the pixmap, we use a fixed widget to place the pixmap */
|
|||
|
fixed = gtk_fixed_new();
|
|||
|
gtk_widget_set_usize( fixed, 200, 200 );
|
|||
|
gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 );
|
|||
|
gtk_container_add( GTK_CONTAINER(window), fixed );
|
|||
|
gtk_widget_show( fixed );
|
|||
|
|
|||
|
/* This masks out everything except for the image itself */
|
|||
|
gtk_widget_shape_combine_mask( window, mask, 0, 0 );
|
|||
|
|
|||
|
/* show the window */
|
|||
|
gtk_widget_set_uposition( window, 20, 400 );
|
|||
|
gtk_widget_show( window );
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
To make the wheelbarrow image sensitive, we could attach the button press
|
|||
|
event signal to make it do something. The following few lines would make
|
|||
|
the picture sensitive to a mouse button being pressed which makes the
|
|||
|
application terminate.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_widget_set_events( window,
|
|||
|
gtk_widget_get_events( window ) |
|
|||
|
GDK_BUTTON_PRESS_MASK );
|
|||
|
|
|||
|
gtk_signal_connect( GTK_OBJECT(window), "button_press_event",
|
|||
|
GTK_SIGNAL_FUNC(close_application), NULL );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Rulers
|
|||
|
<p>
|
|||
|
Ruler widgets are used to indicate the location of the mouse pointer
|
|||
|
in a given window. A window can have a vertical ruler spanning across
|
|||
|
the width and a horizontal ruler spanning down the height. A small
|
|||
|
triangular indicator on the ruler shows the exact location of the
|
|||
|
pointer relative to the ruler.
|
|||
|
|
|||
|
A ruler must first be created. Horizontal and vertical rulers are
|
|||
|
created using
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_hruler_new(void); /* horizontal ruler */
|
|||
|
GtkWidget *gtk_vruler_new(void); /* vertical ruler */
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Once a ruler is created, we can define the unit of measurement. Units
|
|||
|
of measure for rulers can be GTK_PIXELS, GTK_INCHES or
|
|||
|
GTK_CENTIMETERS. This is set using
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_ruler_set_metric( GtkRuler *ruler,
|
|||
|
GtkMetricType metric );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The default measure is GTK_PIXELS.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_ruler_set_metric( GTK_RULER(ruler), GTK_PIXELS );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Other important characteristics of a ruler are how to mark the units
|
|||
|
of scale and where the position indicator is initially placed. These
|
|||
|
are set for a ruler using
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_ruler_set_range (GtkRuler *ruler,
|
|||
|
gfloat lower,
|
|||
|
gfloat upper,
|
|||
|
gfloat position,
|
|||
|
gfloat max_size);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The lower and upper arguments define the extents of the ruler, and
|
|||
|
max_size is the largest possible number that will be displayed.
|
|||
|
Position defines the initial position of the pointer indicator within
|
|||
|
the ruler.
|
|||
|
|
|||
|
A vertical ruler can span an 800 pixel wide window thus
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_ruler_set_range( GTK_RULER(vruler), 0, 800, 0, 800);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The markings displayed on the ruler will be from 0 to 800, with
|
|||
|
a number for every 100 pixels. If instead we wanted the ruler to
|
|||
|
range from 7 to 16, we would code
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_ruler_set_range( GTK_RULER(vruler), 7, 16, 0, 20);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The indicator on the ruler is a small triangular mark that indicates
|
|||
|
the position of the pointer relative to the ruler. If the ruler is
|
|||
|
used to follow the mouse pointer, the motion_notify_event signal
|
|||
|
should be connected to the motion_notify_event method of the ruler.
|
|||
|
To follow all mouse movements within a window area, we would use
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x
|
|||
|
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
|
|||
|
(GtkSignalFunc)EVENT_METHOD(ruler, motion_notify_event),
|
|||
|
GTK_OBJECT(ruler) );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The following example creates a drawing area with a horizontal ruler
|
|||
|
above it and a vertical ruler to the left of it. The size of the
|
|||
|
drawing area is 600 pixels wide by 400 pixels high. The horizontal
|
|||
|
ruler spans from 7 to 13 with a mark every 100 pixels, while the
|
|||
|
vertical ruler spans from 0 to 400 with a mark every 100 pixels.
|
|||
|
Placement of the drawing area and the rulers are done using a table.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* rulers.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
#define EVENT_METHOD(i, x) GTK_WIDGET_CLASS(GTK_OBJECT(i)->klass)->x
|
|||
|
|
|||
|
#define XSIZE 600
|
|||
|
#define YSIZE 400
|
|||
|
|
|||
|
/* this routine gets control when the close button is clicked
|
|||
|
*/
|
|||
|
void close_application( GtkWidget *widget, gpointer *data ) {
|
|||
|
gtk_main_quit();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* the main routine
|
|||
|
*/
|
|||
|
int main( int argc, char *argv[] ) {
|
|||
|
GtkWidget *window, *table, *area, *hrule, *vrule;
|
|||
|
|
|||
|
/* initialize gtk and create the main window */
|
|||
|
gtk_init( &argc, &argv );
|
|||
|
|
|||
|
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC( close_application ), NULL);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* create a table for placing the ruler and the drawing area */
|
|||
|
table = gtk_table_new( 3, 2, FALSE );
|
|||
|
gtk_container_add( GTK_CONTAINER(window), table );
|
|||
|
|
|||
|
area = gtk_drawing_area_new();
|
|||
|
gtk_drawing_area_size( (GtkDrawingArea *)area, XSIZE, YSIZE );
|
|||
|
gtk_table_attach( GTK_TABLE(table), area, 1, 2, 1, 2,
|
|||
|
GTK_EXPAND|GTK_FILL, GTK_FILL, 0, 0 );
|
|||
|
gtk_widget_set_events( area, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK );
|
|||
|
|
|||
|
/* The horizontal ruler goes on top. As the mouse moves across the drawing area,
|
|||
|
a motion_notify_event is passed to the appropriate event handler for the ruler. */
|
|||
|
hrule = gtk_hruler_new();
|
|||
|
gtk_ruler_set_metric( GTK_RULER(hrule), GTK_PIXELS );
|
|||
|
gtk_ruler_set_range( GTK_RULER(hrule), 7, 13, 0, 20 );
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
|
|||
|
(GtkSignalFunc)EVENT_METHOD(hrule, motion_notify_event),
|
|||
|
GTK_OBJECT(hrule) );
|
|||
|
/* GTK_WIDGET_CLASS(GTK_OBJECT(hrule)->klass)->motion_notify_event, */
|
|||
|
gtk_table_attach( GTK_TABLE(table), hrule, 1, 2, 0, 1,
|
|||
|
GTK_EXPAND|GTK_SHRINK|GTK_FILL, GTK_FILL, 0, 0 );
|
|||
|
|
|||
|
/* The vertical ruler goes on the left. As the mouse moves across the drawing area,
|
|||
|
a motion_notify_event is passed to the appropriate event handler for the ruler. */
|
|||
|
vrule = gtk_vruler_new();
|
|||
|
gtk_ruler_set_metric( GTK_RULER(vrule), GTK_PIXELS );
|
|||
|
gtk_ruler_set_range( GTK_RULER(vrule), 0, YSIZE, 10, YSIZE );
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(area), "motion_notify_event",
|
|||
|
(GtkSignalFunc)
|
|||
|
GTK_WIDGET_CLASS(GTK_OBJECT(vrule)->klass)->motion_notify_event,
|
|||
|
GTK_OBJECT(vrule) );
|
|||
|
gtk_table_attach( GTK_TABLE(table), vrule, 0, 1, 1, 2,
|
|||
|
GTK_FILL, GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0 );
|
|||
|
|
|||
|
/* now show everything */
|
|||
|
gtk_widget_show( area );
|
|||
|
gtk_widget_show( hrule );
|
|||
|
gtk_widget_show( vrule );
|
|||
|
gtk_widget_show( table );
|
|||
|
gtk_widget_show( window );
|
|||
|
gtk_main();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Statusbars
|
|||
|
<p>
|
|||
|
Statusbars are simple widgets used to display a text message. They keep a stack
|
|||
|
of the messages pushed onto them, so that popping the current message
|
|||
|
will re-display the previous text message.
|
|||
|
|
|||
|
In order to allow different parts of an application to use the same statusbar to display
|
|||
|
messages, the statusbar widget issues Context Identifiers which are used to identify
|
|||
|
different 'users'. The message on top of the stack is the one displayed, no matter what context
|
|||
|
it is in. Messages are stacked in last-in-first-out order, not context identifier order.
|
|||
|
|
|||
|
A statusbar is created with a call to:
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_statusbar_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
A new Context Identifier is requested using a call to the following function with a short
|
|||
|
textual description of the context:
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_statusbar_get_context_id (GtkStatusbar *statusbar,
|
|||
|
const gchar *context_description);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
There are three functions that can operate on statusbars.
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_statusbar_push (GtkStatusbar *statusbar,
|
|||
|
guint context_id,
|
|||
|
gchar *text);
|
|||
|
|
|||
|
void gtk_statusbar_pop (GtkStatusbar *statusbar)
|
|||
|
guint context_id);
|
|||
|
void gtk_statusbar_remove (GtkStatusbar *statusbar,
|
|||
|
guint context_id,
|
|||
|
guint message_id);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first, gtk_statusbar_push, is used to add a new message to the statusbar.
|
|||
|
It returns a Message Identifier, which can be passed later to the function gtk_statusbar_remove
|
|||
|
to remove the message with the given Message and Context Identifiers from the statusbar's stack.
|
|||
|
|
|||
|
The function gtk_statusbar_pop removes the message highest in the stack with the given
|
|||
|
Context Identifier.
|
|||
|
|
|||
|
The following example creates a statusbar and two buttons, one for pushing items
|
|||
|
onto the statusbar, and one for popping the last item back off.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* statusbar.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include <glib.h>
|
|||
|
|
|||
|
GtkWidget *status_bar;
|
|||
|
|
|||
|
void push_item (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
static int count = 1;
|
|||
|
char buff[20];
|
|||
|
|
|||
|
g_snprintf(buff, 20, "Item %d", count++);
|
|||
|
gtk_statusbar_push( GTK_STATUSBAR(status_bar), (guint) &data, buff);
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
void pop_item (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_statusbar_pop( GTK_STATUSBAR(status_bar), (guint) &data );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *vbox;
|
|||
|
GtkWidget *button;
|
|||
|
|
|||
|
int context_id;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
|
|||
|
gtk_window_set_title(GTK_WINDOW (window), "GTK Statusbar Example");
|
|||
|
gtk_signal_connect(GTK_OBJECT (window), "delete_event",
|
|||
|
(GtkSignalFunc) gtk_exit, NULL);
|
|||
|
|
|||
|
vbox = gtk_vbox_new(FALSE, 1);
|
|||
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|||
|
gtk_widget_show(vbox);
|
|||
|
|
|||
|
status_bar = gtk_statusbar_new();
|
|||
|
gtk_box_pack_start (GTK_BOX (vbox), status_bar, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (status_bar);
|
|||
|
|
|||
|
context_id = gtk_statusbar_get_context_id( GTK_STATUSBAR(status_bar), "Statusbar example");
|
|||
|
|
|||
|
button = gtk_button_new_with_label("push item");
|
|||
|
gtk_signal_connect(GTK_OBJECT(button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (push_item), &context_id);
|
|||
|
gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label("pop last item");
|
|||
|
gtk_signal_connect(GTK_OBJECT(button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (pop_item), &context_id);
|
|||
|
gtk_box_pack_start(GTK_BOX(vbox), button, TRUE, TRUE, 2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
/* always display the window as the last step so it all splashes on
|
|||
|
* the screen at once. */
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Text Entries
|
|||
|
<p>
|
|||
|
The Entry widget allows text to be typed and displayed in a single line text box.
|
|||
|
The text may be set with functions calls that allow new text to replace,
|
|||
|
prepend or append the current contents of the Entry widget.
|
|||
|
|
|||
|
There are two functions for creating Entry widgets:
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_entry_new (void);
|
|||
|
|
|||
|
GtkWidget* gtk_entry_new_with_max_length (guint16 max);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first just creates a new Entry widget, whilst the second creates a new Entry and
|
|||
|
sets a limit on the length of the text within the Entry..
|
|||
|
|
|||
|
The maximum length of the text within an entry widget may be changed by a call to the following
|
|||
|
function. If the current text is longer than this maximum, then it is upto us to alter the Entries
|
|||
|
contents appropriately.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_set_max_length (GtkEntry *entry,
|
|||
|
guint16 max);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
There are several functions for altering the text which is currently within the Entry widget.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_set_text (GtkEntry *entry,
|
|||
|
const gchar *text);
|
|||
|
void gtk_entry_append_text (GtkEntry *entry,
|
|||
|
const gchar *text);
|
|||
|
void gtk_entry_prepend_text (GtkEntry *entry,
|
|||
|
const gchar *text);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The function gtk_entry_set_text sets the contents of the Entry widget, replacing the
|
|||
|
current contents. The functions gtk_entry_append_text and gtk_entry_prepend_text allow
|
|||
|
the current contents to be appended and prepended to.
|
|||
|
|
|||
|
The next function allows the current insertion point to be set.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_set_position (GtkEntry *entry,
|
|||
|
gint position);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The contents of the Entry can be retrieved by using a call to the following function. This
|
|||
|
is useful in the callback functions described below.
|
|||
|
<tscreen><verb>
|
|||
|
gchar* gtk_entry_get_text (GtkEntry *entry);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
If we don't want the contents of the Entry to be changed by someone typing into it, we
|
|||
|
can change it's edittable state.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_set_editable (GtkEntry *entry,
|
|||
|
gboolean editable);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function allows us to toggle the edittable state of the Entry widget by passing in
|
|||
|
TRUE or FALSE values for the editable argument.
|
|||
|
|
|||
|
If we are using the Entry where we don't want the text entered to be visible, for
|
|||
|
example when a password is being entered, we can use the following function, which
|
|||
|
also takes a boolean flag.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_set_visibility (GtkEntry *entry,
|
|||
|
gboolean visible);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
A region of the text may be set as selected by using the following function. This would
|
|||
|
most often be used after setting some default text in an Entry, making it easy for the user
|
|||
|
to remove it.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_entry_select_region (GtkEntry *entry,
|
|||
|
gint start,
|
|||
|
gint end);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
If we want to catch when the user has entered text, we can connect to the
|
|||
|
<tt/activate/ or <tt/changed/ signal. Activate is raised when the user hits
|
|||
|
the enter key within the Entry widget. Changed is raised when the text changes at all,
|
|||
|
e.g. for every character entered or removed.
|
|||
|
|
|||
|
The following code is an example of using an Entry widget.
|
|||
|
<tscreen><verb>
|
|||
|
/* entry.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
void enter_callback(GtkWidget *widget, GtkWidget *entry)
|
|||
|
{
|
|||
|
gchar *entry_text;
|
|||
|
entry_text = gtk_entry_get_text(GTK_ENTRY(entry));
|
|||
|
printf("Entry contents: %s\n", entry_text);
|
|||
|
}
|
|||
|
|
|||
|
void entry_toggle_editable (GtkWidget *checkbutton,
|
|||
|
GtkWidget *entry)
|
|||
|
{
|
|||
|
gtk_entry_set_editable(GTK_ENTRY(entry),
|
|||
|
GTK_TOGGLE_BUTTON(checkbutton)->active);
|
|||
|
}
|
|||
|
|
|||
|
void entry_toggle_visibility (GtkWidget *checkbutton,
|
|||
|
GtkWidget *entry)
|
|||
|
{
|
|||
|
gtk_entry_set_visibility(GTK_ENTRY(entry),
|
|||
|
GTK_TOGGLE_BUTTON(checkbutton)->active);
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *vbox, *hbox;
|
|||
|
GtkWidget *entry;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *check;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
|
|||
|
gtk_window_set_title(GTK_WINDOW (window), "GTK Entry");
|
|||
|
gtk_signal_connect(GTK_OBJECT (window), "delete_event",
|
|||
|
(GtkSignalFunc) gtk_exit, NULL);
|
|||
|
|
|||
|
vbox = gtk_vbox_new (FALSE, 0);
|
|||
|
gtk_container_add (GTK_CONTAINER (window), vbox);
|
|||
|
gtk_widget_show (vbox);
|
|||
|
|
|||
|
entry = gtk_entry_new_with_max_length (50);
|
|||
|
gtk_signal_connect(GTK_OBJECT(entry), "activate",
|
|||
|
GTK_SIGNAL_FUNC(enter_callback),
|
|||
|
entry);
|
|||
|
gtk_entry_set_text (GTK_ENTRY (entry), "hello");
|
|||
|
gtk_entry_append_text (GTK_ENTRY (entry), " world");
|
|||
|
gtk_entry_select_region (GTK_ENTRY (entry),
|
|||
|
0, GTK_ENTRY(entry)->text_length);
|
|||
|
gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (entry);
|
|||
|
|
|||
|
hbox = gtk_hbox_new (FALSE, 0);
|
|||
|
gtk_container_add (GTK_CONTAINER (vbox), hbox);
|
|||
|
gtk_widget_show (hbox);
|
|||
|
|
|||
|
check = gtk_check_button_new_with_label("Editable");
|
|||
|
gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0);
|
|||
|
gtk_signal_connect (GTK_OBJECT(check), "toggled",
|
|||
|
GTK_SIGNAL_FUNC(entry_toggle_editable), entry);
|
|||
|
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
|
|||
|
gtk_widget_show (check);
|
|||
|
|
|||
|
check = gtk_check_button_new_with_label("Visible");
|
|||
|
gtk_box_pack_start (GTK_BOX (hbox), check, TRUE, TRUE, 0);
|
|||
|
gtk_signal_connect (GTK_OBJECT(check), "toggled",
|
|||
|
GTK_SIGNAL_FUNC(entry_toggle_visibility), entry);
|
|||
|
gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(check), TRUE);
|
|||
|
gtk_widget_show (check);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("Close");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC(gtk_exit),
|
|||
|
GTK_OBJECT (window));
|
|||
|
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);
|
|||
|
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
|
|||
|
gtk_widget_grab_default (button);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
gtk_main();
|
|||
|
return(0);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> Container Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Notebooks
|
|||
|
<p>
|
|||
|
The NoteBook Widget is a collection of 'pages' that overlap each other,
|
|||
|
each page contains different information. This widget has become more common
|
|||
|
lately in GUI programming, and it is a good way to show blocks similar
|
|||
|
information that warrant separation in their display.
|
|||
|
|
|||
|
The first function call you will need to know, as you can probably
|
|||
|
guess by now, is used to create a new notebook widget.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_notebook_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Once the notebook has been created, there are 12 functions that
|
|||
|
operate on the notebook widget. Let's look at them individually.
|
|||
|
|
|||
|
The first one we will look at is how to position the page indicators.
|
|||
|
These page indicators or 'tabs' as they are referred to, can be positioned
|
|||
|
in four ways; top, bottom, left, or right.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
GtkPostionType will be one of the following, and they are pretty self explanatory.
|
|||
|
<itemize>
|
|||
|
<item> GTK_POS_LEFT
|
|||
|
<item> GTK_POS_RIGHT
|
|||
|
<item> GTK_POS_TOP
|
|||
|
<item> GTK_POS_BOTTOM
|
|||
|
</itemize>
|
|||
|
|
|||
|
GTK_POS_TOP is the default.
|
|||
|
|
|||
|
Next we will look at how to add pages to the notebook. There are three
|
|||
|
ways to add pages to the NoteBook. Let's look at the first two together as
|
|||
|
they are quite similar.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);
|
|||
|
|
|||
|
void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
These functions add pages to the notebook by inserting them from the
|
|||
|
back of the notebook (append), or the front of the notebook (prepend).
|
|||
|
*child is the widget that is placed within the notebook page, and *tab_label is
|
|||
|
the label for the page being added.
|
|||
|
|
|||
|
The final function for adding a page to the notebook contains all of
|
|||
|
the properties of the previous two, but it allows you to specify what position
|
|||
|
you want the page to be in the notebook.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The parameters are the same as _append_ and _prepend_ except it
|
|||
|
contains an extra parameter, position. This parameter is used to specify what
|
|||
|
place this page will inserted to.
|
|||
|
|
|||
|
Now that we know how to add a page, lets see how we can remove a page
|
|||
|
from the notebook.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function takes the page specified by page_num and removes it from
|
|||
|
the widget *notebook.
|
|||
|
|
|||
|
To find out what the current page is in a notebook use the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_notebook_current_page (GtkNotebook *notebook);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
These next two functions are simple calls to move the notebook page
|
|||
|
forward or backward. Simply provide the respective function call with the
|
|||
|
notebook widget you wish to operate on. Note: When the NoteBook is currently
|
|||
|
on the last page, and gtk_notebook_next_page is called, the notebook will
|
|||
|
wrap back to the first page. Likewise, if the NoteBook is on the first page,
|
|||
|
and gtk_notebook_prev_page is called, the notebook will wrap to the last page.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_next_page (GtkNoteBook *notebook);
|
|||
|
void gtk_notebook_prev_page (GtkNoteBook *notebook);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This next function sets the 'active' page. If you wish the
|
|||
|
notebook to be opened to page 5 for example, you would use this function.
|
|||
|
Without using this function, the notebook defaults to the first page.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The next two functions add or remove the notebook page tabs and the
|
|||
|
notebook border respectively.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs);
|
|||
|
void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
show_tabs and show_border can both be either TRUE or FALSE (0 or 1).
|
|||
|
|
|||
|
Now lets look at an example, it is expanded from the testgtk.c code
|
|||
|
that comes with the GTK distribution, and it shows all 13 functions. This
|
|||
|
small program, creates a window with a notebook and six buttons. The notebook
|
|||
|
contains 11 pages, added in three different ways, appended, inserted, and
|
|||
|
prepended. The buttons allow you rotate the tab positions, add/remove the tabs
|
|||
|
and border, remove a page, change pages in both a forward and backward manner,
|
|||
|
and exit the program.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* notebook.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* This function rotates the position of the tabs */
|
|||
|
void rotate_book (GtkButton *button, GtkNotebook *notebook)
|
|||
|
{
|
|||
|
gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4);
|
|||
|
}
|
|||
|
|
|||
|
/* Add/Remove the page tabs and the borders */
|
|||
|
void tabsborder_book (GtkButton *button, GtkNotebook *notebook)
|
|||
|
{
|
|||
|
gint tval = FALSE;
|
|||
|
gint bval = FALSE;
|
|||
|
if (notebook->show_tabs == 0)
|
|||
|
tval = TRUE;
|
|||
|
if (notebook->show_border == 0)
|
|||
|
bval = TRUE;
|
|||
|
|
|||
|
gtk_notebook_set_show_tabs (notebook, tval);
|
|||
|
gtk_notebook_set_show_border (notebook, bval);
|
|||
|
}
|
|||
|
|
|||
|
/* Remove a page from the notebook */
|
|||
|
void remove_book (GtkButton *button, GtkNotebook *notebook)
|
|||
|
{
|
|||
|
gint page;
|
|||
|
|
|||
|
page = gtk_notebook_current_page(notebook);
|
|||
|
gtk_notebook_remove_page (notebook, page);
|
|||
|
/* Need to refresh the widget --
|
|||
|
This forces the widget to redraw itself. */
|
|||
|
gtk_widget_draw(GTK_WIDGET(notebook), NULL);
|
|||
|
}
|
|||
|
|
|||
|
void delete (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *table;
|
|||
|
GtkWidget *notebook;
|
|||
|
GtkWidget *frame;
|
|||
|
GtkWidget *label;
|
|||
|
GtkWidget *checkbutton;
|
|||
|
int i;
|
|||
|
char bufferf[32];
|
|||
|
char bufferl[32];
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|||
|
GTK_SIGNAL_FUNC (delete), NULL);
|
|||
|
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
table = gtk_table_new(2,6,TRUE);
|
|||
|
gtk_container_add (GTK_CONTAINER (window), table);
|
|||
|
|
|||
|
/* Create a new notebook, place the position of the tabs */
|
|||
|
notebook = gtk_notebook_new ();
|
|||
|
gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1);
|
|||
|
gtk_widget_show(notebook);
|
|||
|
|
|||
|
/* lets append a bunch of pages to the notebook */
|
|||
|
for (i=0; i < 5; i++) {
|
|||
|
sprintf(bufferf, "Append Frame %d", i+1);
|
|||
|
sprintf(bufferl, "Page %d", i+1);
|
|||
|
|
|||
|
frame = gtk_frame_new (bufferf);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (frame), 10);
|
|||
|
gtk_widget_set_usize (frame, 100, 75);
|
|||
|
gtk_widget_show (frame);
|
|||
|
|
|||
|
label = gtk_label_new (bufferf);
|
|||
|
gtk_container_add (GTK_CONTAINER (frame), label);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
label = gtk_label_new (bufferl);
|
|||
|
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* now lets add a page to a specific spot */
|
|||
|
checkbutton = gtk_check_button_new_with_label ("Check me please!");
|
|||
|
gtk_widget_set_usize(checkbutton, 100, 75);
|
|||
|
gtk_widget_show (checkbutton);
|
|||
|
|
|||
|
label = gtk_label_new ("Add spot");
|
|||
|
gtk_container_add (GTK_CONTAINER (checkbutton), label);
|
|||
|
gtk_widget_show (label);
|
|||
|
label = gtk_label_new ("Add page");
|
|||
|
gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2);
|
|||
|
|
|||
|
/* Now finally lets prepend pages to the notebook */
|
|||
|
for (i=0; i < 5; i++) {
|
|||
|
sprintf(bufferf, "Prepend Frame %d", i+1);
|
|||
|
sprintf(bufferl, "PPage %d", i+1);
|
|||
|
|
|||
|
frame = gtk_frame_new (bufferf);
|
|||
|
gtk_container_border_width (GTK_CONTAINER (frame), 10);
|
|||
|
gtk_widget_set_usize (frame, 100, 75);
|
|||
|
gtk_widget_show (frame);
|
|||
|
|
|||
|
label = gtk_label_new (bufferf);
|
|||
|
gtk_container_add (GTK_CONTAINER (frame), label);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
label = gtk_label_new (bufferl);
|
|||
|
gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label);
|
|||
|
}
|
|||
|
|
|||
|
/* Set what page to start at (page 4) */
|
|||
|
gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3);
|
|||
|
|
|||
|
|
|||
|
/* create a bunch of buttons */
|
|||
|
button = gtk_button_new_with_label ("close");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (delete), NULL);
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("next page");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) gtk_notebook_next_page,
|
|||
|
GTK_OBJECT (notebook));
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("prev page");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) gtk_notebook_prev_page,
|
|||
|
GTK_OBJECT (notebook));
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("tab position");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) rotate_book, GTK_OBJECT(notebook));
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("tabs/border on/off");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) tabsborder_book,
|
|||
|
GTK_OBJECT (notebook));
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("remove page");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) remove_book,
|
|||
|
GTK_OBJECT(notebook));
|
|||
|
gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
gtk_widget_show(table);
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Hopefully this helps you on your way with creating notebooks for your
|
|||
|
GTK applications.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Scrolled Windows
|
|||
|
<p>
|
|||
|
Scrolled windows are used to create a scrollable area inside a real window.
|
|||
|
You may insert any types of widgets to these scrolled windows, and they will
|
|||
|
all be accessable regardless of the size by using the scrollbars.
|
|||
|
|
|||
|
The following function is used to create a new scolled window.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment,
|
|||
|
GtkAdjustment *vadjustment);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the first argument is the adjustment for the horizontal
|
|||
|
direction, and the second, the adjustment for the vertical direction.
|
|||
|
These are almost always set to NULL.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
|
|||
|
GtkPolicyType hscrollbar_policy,
|
|||
|
GtkPolicyType vscrollbar_policy);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This sets the policy to be used with respect to the scrollbars.
|
|||
|
The first arguement is the scrolled window you wish to change. The second
|
|||
|
sets the policiy for the horizontal scrollbar, and the third,
|
|||
|
the vertical scrollbar.
|
|||
|
|
|||
|
The policy may be one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS.
|
|||
|
GTK_POLICY_AUTOMATIC will automatically decide whether you need
|
|||
|
scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars
|
|||
|
there.
|
|||
|
|
|||
|
Here is a simple example that packs 100 toggle buttons into a scrolled window.
|
|||
|
I've only commented on the parts that may be new to you.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* scrolledwin.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
void destroy(GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
static GtkWidget *window;
|
|||
|
GtkWidget *scrolled_window;
|
|||
|
GtkWidget *table;
|
|||
|
GtkWidget *button;
|
|||
|
char buffer[32];
|
|||
|
int i, j;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* Create a new dialog window for the scrolled window to be
|
|||
|
* packed into. A dialog is just like a normal window except it has a
|
|||
|
* vbox and a horizontal seperator packed into it. It's just a shortcut
|
|||
|
* for creating dialogs */
|
|||
|
window = gtk_dialog_new ();
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
(GtkSignalFunc) destroy, NULL);
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "dialog");
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 0);
|
|||
|
gtk_widget_set_usize(window, 300, 300);
|
|||
|
|
|||
|
/* create a new scrolled window. */
|
|||
|
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
|
|||
|
|
|||
|
gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10);
|
|||
|
|
|||
|
/* the policy is one of GTK_POLICY AUTOMATIC, or GTK_POLICY_ALWAYS.
|
|||
|
* GTK_POLICY_AUTOMATIC will automatically decide whether you need
|
|||
|
* scrollbars, wheras GTK_POLICY_ALWAYS will always leave the scrollbars
|
|||
|
* there. The first one is the horizontal scrollbar, the second,
|
|||
|
* the vertical. */
|
|||
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
|
|||
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
|
|||
|
/* The dialog window is created with a vbox packed into it. */
|
|||
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window,
|
|||
|
TRUE, TRUE, 0);
|
|||
|
gtk_widget_show (scrolled_window);
|
|||
|
|
|||
|
/* create a table of 10 by 10 squares. */
|
|||
|
table = gtk_table_new (10, 10, FALSE);
|
|||
|
|
|||
|
/* set the spacing to 10 on x and 10 on y */
|
|||
|
gtk_table_set_row_spacings (GTK_TABLE (table), 10);
|
|||
|
gtk_table_set_col_spacings (GTK_TABLE (table), 10);
|
|||
|
|
|||
|
/* pack the table into the scrolled window */
|
|||
|
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
|
|||
|
gtk_widget_show (table);
|
|||
|
|
|||
|
/* this simply creates a grid of toggle buttons on the table
|
|||
|
* to demonstrate the scrolled window. */
|
|||
|
for (i = 0; i < 10; i++)
|
|||
|
for (j = 0; j < 10; j++) {
|
|||
|
sprintf (buffer, "button (%d,%d)\n", i, j);
|
|||
|
button = gtk_toggle_button_new_with_label (buffer);
|
|||
|
gtk_table_attach_defaults (GTK_TABLE (table), button,
|
|||
|
i, i+1, j, j+1);
|
|||
|
gtk_widget_show (button);
|
|||
|
}
|
|||
|
|
|||
|
/* Add a "close" button to the bottom of the dialog */
|
|||
|
button = gtk_button_new_with_label ("close");
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|||
|
(GtkSignalFunc) gtk_widget_destroy,
|
|||
|
GTK_OBJECT (window));
|
|||
|
|
|||
|
/* this makes it so the button is the default. */
|
|||
|
|
|||
|
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
|
|||
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0);
|
|||
|
|
|||
|
/* This grabs this button to be the default button. Simply hitting
|
|||
|
* the "Enter" key will cause this button to activate. */
|
|||
|
gtk_widget_grab_default (button);
|
|||
|
gtk_widget_show (button);
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main();
|
|||
|
|
|||
|
return(0);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Try playing with resizing the window. You'll notice how the scrollbars
|
|||
|
react. You may also wish to use the gtk_widget_set_usize() call to set the default
|
|||
|
size of the window or other widgets.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> List Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
The GtkList widget is designed to act as a vertical container for widgets
|
|||
|
that should be of the type GtkListItem.
|
|||
|
|
|||
|
A GtkList widget has its own window to receive events and it's own
|
|||
|
background color which is usualy white. As it is directly derived from a
|
|||
|
GtkContainer it can be treated as such by using the GTK_CONTAINER(List)
|
|||
|
macro, see the GtkContainer widget for more on this.
|
|||
|
One should already be familar whith the usage of a GList and its
|
|||
|
related functions g_list_*() to be able to use the GtkList widget to
|
|||
|
its fully extends.
|
|||
|
|
|||
|
There is one field inside the structure definition of the GtkList widget
|
|||
|
that will be of greater interest to us, this is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GtkList
|
|||
|
{
|
|||
|
...
|
|||
|
GList *selection;
|
|||
|
guint selection_mode;
|
|||
|
...
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The selection field of a GtkList points to a linked list of all items
|
|||
|
that are cureently selected, or `NULL' if the selection is empty.
|
|||
|
So to learn about the current selection we read the GTK_LIST()->selection
|
|||
|
field, but do not modify it since the internal fields are maintained by
|
|||
|
the gtk_list_*() functions.
|
|||
|
|
|||
|
The selection_mode of the GtkList determines the selection facilities
|
|||
|
of a GtkList and therefore the contents of the GTK_LIST()->selection
|
|||
|
field:
|
|||
|
|
|||
|
The selection_mode may be one of the following:
|
|||
|
<itemize>
|
|||
|
<item> GTK_SELECTION_SINGLE - The selection is either `NULL'
|
|||
|
or contains a GList* pointer
|
|||
|
for a single selected item.
|
|||
|
|
|||
|
<item> GTK_SELECTION_BROWSE - The selection is `NULL' if the list
|
|||
|
contains no widgets or insensitive
|
|||
|
ones only, otherwise it contains
|
|||
|
a GList pointer for one GList
|
|||
|
structure, and therefore exactly
|
|||
|
one list item.
|
|||
|
|
|||
|
<item> GTK_SELECTION_MULTIPLE - The selection is `NULL' if no list
|
|||
|
items are selected or a GList pointer
|
|||
|
for the first selected item. That
|
|||
|
in turn points to a GList structure
|
|||
|
for the second selected item and so
|
|||
|
on.
|
|||
|
|
|||
|
<item> GTK_SELECTION_EXTENDED - The selection is always `NULL'.
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
The default is GTK_SELECTION_MULTIPLE.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Signals
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
void selection_changed (GtkList *LIST)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This signal will be invoked whenever a the selection field
|
|||
|
of a GtkList has changed. This happens when a child of
|
|||
|
the GtkList got selected or unselected.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void select_child (GtkList *LIST, GtkWidget *CHILD)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This signal is invoked when a child of the GtkList is about
|
|||
|
to get selected. This happens mainly on calls to
|
|||
|
gtk_list_select_item(), gtk_list_select_child(), button presses
|
|||
|
and sometimes indirectly triggered on some else occasions where
|
|||
|
children get added to or removed from the GtkList.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void unselect_child (GtkList *LIST, GtkWidget *CHILD)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This signal is invoked when a child of the GtkList is about
|
|||
|
to get unselected. This happens mainly on calls to
|
|||
|
gtk_list_unselect_item(), gtk_list_unselect_child(), button presses
|
|||
|
and sometimes indirectly triggered on some else occasions where
|
|||
|
children get added to or removed from the GtkList.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Functions
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_list_get_type (void)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Returns the `GtkList' type identifier.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_list_new (void)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Create a new `GtkList' object. The new widget is
|
|||
|
returned as a pointer to a `GtkWidget' object.
|
|||
|
`NULL' is returned on failure.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Insert list items into the LIST, starting at POSITION.
|
|||
|
ITEMS is a doubly linked list where each nodes data
|
|||
|
pointer is expected to point to a newly created GtkListItem.
|
|||
|
The GList nodes of ITEMS are taken over by the LIST.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_append_items (GtkList *LIST, GList *ITEMS)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Insert list items just like gtk_list_insert_items() at the end
|
|||
|
of the LIST. The GList nodes of ITEMS are taken over by the LIST.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Insert list items just like gtk_list_insert_items() at the very
|
|||
|
beginning of the LIST. The GList nodes of ITEMS are taken over
|
|||
|
by the LIST.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_remove_items (GtkList *LIST, GList *ITEMS)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Remove list items from the LIST. ITEMS is a doubly linked
|
|||
|
list where each nodes data pointer is expected to point to a
|
|||
|
direct child of LIST. It is the callers responsibility to make a
|
|||
|
call to g_list_free(ITEMS) afterwards. Also the caller has to
|
|||
|
destroy the list items himself.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_clear_items (GtkList *LIST, gint START, gint END)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Remove and destroy list items from the LIST. a widget is affected if
|
|||
|
its current position within LIST is in the range specified by START
|
|||
|
and END.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_select_item (GtkList *LIST, gint ITEM)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Invoke the select_child signal for a list item
|
|||
|
specified through its current position within LIST.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_unselect_item (GtkList *LIST, gint ITEM)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Invoke the unselect_child signal for a list item
|
|||
|
specified through its current position within LIST.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Invoke the select_child signal for the specified CHILD.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Invoke the unselect_child signal for the specified CHILD.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Return the position of CHILD within LIST. `-1' is returned on failure.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Set LIST to the selection mode MODE wich can be of GTK_SELECTION_SINGLE,
|
|||
|
GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE or GTK_SELECTION_EXTENDED.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkList* GTK_LIST (gpointer OBJ)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Cast a generic pointer to `GtkList*'. *Note Standard Macros::, for
|
|||
|
more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkListClass* GTK_LIST_CLASS (gpointer CLASS)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Cast a generic pointer to `GtkListClass*'. *Note Standard Macros::,
|
|||
|
for more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint GTK_IS_LIST (gpointer OBJ)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Determine if a generic pointer refers to a `GtkList' object. *Note
|
|||
|
Standard Macros::, for more info.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Example
|
|||
|
<p>
|
|||
|
Following is an example program that will print out the changes
|
|||
|
of the selection of a GtkList, and lets you "arrest" list items
|
|||
|
into a prison by selecting them with the rightmost mouse button:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* list.c */
|
|||
|
|
|||
|
/* include the gtk+ header files
|
|||
|
* include stdio.h, we need that for the printf() function
|
|||
|
*/
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include <stdio.h>
|
|||
|
|
|||
|
/* this is our data identification string to store
|
|||
|
* data in list items
|
|||
|
*/
|
|||
|
const gchar *list_item_data_key="list_item_data";
|
|||
|
|
|||
|
|
|||
|
/* prototypes for signal handler that we are going to connect
|
|||
|
* to the GtkList widget
|
|||
|
*/
|
|||
|
static void sigh_print_selection (GtkWidget *gtklist,
|
|||
|
gpointer func_data);
|
|||
|
static void sigh_button_event (GtkWidget *gtklist,
|
|||
|
GdkEventButton *event,
|
|||
|
GtkWidget *frame);
|
|||
|
|
|||
|
|
|||
|
/* main function to set up the user interface */
|
|||
|
|
|||
|
gint main (int argc, gchar *argv[])
|
|||
|
{
|
|||
|
GtkWidget *separator;
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *vbox;
|
|||
|
GtkWidget *scrolled_window;
|
|||
|
GtkWidget *frame;
|
|||
|
GtkWidget *gtklist;
|
|||
|
GtkWidget *button;
|
|||
|
GtkWidget *list_item;
|
|||
|
GList *dlist;
|
|||
|
guint i;
|
|||
|
gchar buffer[64];
|
|||
|
|
|||
|
|
|||
|
/* initialize gtk+ (and subsequently gdk) */
|
|||
|
|
|||
|
gtk_init(&argc, &argv);
|
|||
|
|
|||
|
|
|||
|
/* create a window to put all the widgets in
|
|||
|
* connect gtk_main_quit() to the "destroy" event of
|
|||
|
* the window to handle window manager close-window-events
|
|||
|
*/
|
|||
|
window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_window_set_title(GTK_WINDOW(window), "GtkList Example");
|
|||
|
gtk_signal_connect(GTK_OBJECT(window),
|
|||
|
"destroy",
|
|||
|
GTK_SIGNAL_FUNC(gtk_main_quit),
|
|||
|
NULL);
|
|||
|
|
|||
|
|
|||
|
/* inside the window we need a box to arrange the widgets
|
|||
|
* vertically */
|
|||
|
vbox=gtk_vbox_new(FALSE, 5);
|
|||
|
gtk_container_border_width(GTK_CONTAINER(vbox), 5);
|
|||
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|||
|
gtk_widget_show(vbox);
|
|||
|
|
|||
|
/* this is the scolled window to put the GtkList widget inside */
|
|||
|
scrolled_window=gtk_scrolled_window_new(NULL, NULL);
|
|||
|
gtk_widget_set_usize(scrolled_window, 250, 150);
|
|||
|
gtk_container_add(GTK_CONTAINER(vbox), scrolled_window);
|
|||
|
gtk_widget_show(scrolled_window);
|
|||
|
|
|||
|
/* create the GtkList widget
|
|||
|
* connect the sigh_print_selection() signal handler
|
|||
|
* function to the "selection_changed" signal of the GtkList
|
|||
|
* to print out the selected items each time the selection
|
|||
|
* has changed */
|
|||
|
gtklist=gtk_list_new();
|
|||
|
gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist);
|
|||
|
gtk_widget_show(gtklist);
|
|||
|
gtk_signal_connect(GTK_OBJECT(gtklist),
|
|||
|
"selection_changed",
|
|||
|
GTK_SIGNAL_FUNC(sigh_print_selection),
|
|||
|
NULL);
|
|||
|
|
|||
|
/* we create a "Prison" to put a list item in ;)
|
|||
|
*/
|
|||
|
frame=gtk_frame_new("Prison");
|
|||
|
gtk_widget_set_usize(frame, 200, 50);
|
|||
|
gtk_container_border_width(GTK_CONTAINER(frame), 5);
|
|||
|
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
|
|||
|
gtk_container_add(GTK_CONTAINER(vbox), frame);
|
|||
|
gtk_widget_show(frame);
|
|||
|
|
|||
|
/* connect the sigh_button_event() signal handler to the GtkList
|
|||
|
* wich will handle the "arresting" of list items
|
|||
|
*/
|
|||
|
gtk_signal_connect(GTK_OBJECT(gtklist),
|
|||
|
"button_release_event",
|
|||
|
GTK_SIGNAL_FUNC(sigh_button_event),
|
|||
|
frame);
|
|||
|
|
|||
|
/* create a separator
|
|||
|
*/
|
|||
|
separator=gtk_hseparator_new();
|
|||
|
gtk_container_add(GTK_CONTAINER(vbox), separator);
|
|||
|
gtk_widget_show(separator);
|
|||
|
|
|||
|
/* finaly create a button and connect it<69>s "clicked" signal
|
|||
|
* to the destroyment of the window
|
|||
|
*/
|
|||
|
button=gtk_button_new_with_label("Close");
|
|||
|
gtk_container_add(GTK_CONTAINER(vbox), button);
|
|||
|
gtk_widget_show(button);
|
|||
|
gtk_signal_connect_object(GTK_OBJECT(button),
|
|||
|
"clicked",
|
|||
|
GTK_SIGNAL_FUNC(gtk_widget_destroy),
|
|||
|
GTK_OBJECT(window));
|
|||
|
|
|||
|
|
|||
|
/* now we create 5 list items, each having it<69>s own
|
|||
|
* label and add them to the GtkList using gtk_container_add()
|
|||
|
* also we query the text string from the label and
|
|||
|
* associate it with the list_item_data_key for each list item
|
|||
|
*/
|
|||
|
for (i=0; i<5; i++) {
|
|||
|
GtkWidget *label;
|
|||
|
gchar *string;
|
|||
|
|
|||
|
sprintf(buffer, "ListItemContainer with Label #%d", i);
|
|||
|
label=gtk_label_new(buffer);
|
|||
|
list_item=gtk_list_item_new();
|
|||
|
gtk_container_add(GTK_CONTAINER(list_item), label);
|
|||
|
gtk_widget_show(label);
|
|||
|
gtk_container_add(GTK_CONTAINER(gtklist), list_item);
|
|||
|
gtk_widget_show(list_item);
|
|||
|
gtk_label_get(GTK_LABEL(label), &string);
|
|||
|
gtk_object_set_data(GTK_OBJECT(list_item),
|
|||
|
list_item_data_key,
|
|||
|
string);
|
|||
|
}
|
|||
|
/* here, we are creating another 5 labels, this time
|
|||
|
* we use gtk_list_item_new_with_label() for the creation
|
|||
|
* we can<61>t query the text string from the label because
|
|||
|
* we don<6F>t have the labels pointer and therefore
|
|||
|
* we just associate the list_item_data_key of each
|
|||
|
* list item with the same text string
|
|||
|
* for adding of the list items we put them all into a doubly
|
|||
|
* linked list (GList), and then add them by a single call to
|
|||
|
* gtk_list_append_items()
|
|||
|
* because we use g_list_prepend() to put the items into the
|
|||
|
* doubly linked list, their order will be descending (instead
|
|||
|
* of ascending when using g_list_append())
|
|||
|
*/
|
|||
|
dlist=NULL;
|
|||
|
for (; i<10; i++) {
|
|||
|
sprintf(buffer, "List Item with Label %d", i);
|
|||
|
list_item=gtk_list_item_new_with_label(buffer);
|
|||
|
dlist=g_list_prepend(dlist, list_item);
|
|||
|
gtk_widget_show(list_item);
|
|||
|
gtk_object_set_data(GTK_OBJECT(list_item),
|
|||
|
list_item_data_key,
|
|||
|
"ListItem with integrated Label");
|
|||
|
}
|
|||
|
gtk_list_append_items(GTK_LIST(gtklist), dlist);
|
|||
|
|
|||
|
/* finaly we want to see the window, don<6F>t we? ;)
|
|||
|
*/
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
/* fire up the main event loop of gtk
|
|||
|
*/
|
|||
|
gtk_main();
|
|||
|
|
|||
|
/* we get here after gtk_main_quit() has been called which
|
|||
|
* happens if the main window gets destroyed
|
|||
|
*/
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/* this is the signal handler that got connected to button
|
|||
|
* press/release events of the GtkList
|
|||
|
*/
|
|||
|
void
|
|||
|
sigh_button_event (GtkWidget *gtklist,
|
|||
|
GdkEventButton *event,
|
|||
|
GtkWidget *frame)
|
|||
|
{
|
|||
|
/* we only do something if the third (rightmost mouse button
|
|||
|
* was released
|
|||
|
*/
|
|||
|
if (event->type==GDK_BUTTON_RELEASE &&
|
|||
|
event->button==3) {
|
|||
|
GList *dlist, *free_list;
|
|||
|
GtkWidget *new_prisoner;
|
|||
|
|
|||
|
/* fetch the currently selected list item which
|
|||
|
* will be our next prisoner ;)
|
|||
|
*/
|
|||
|
dlist=GTK_LIST(gtklist)->selection;
|
|||
|
if (dlist)
|
|||
|
new_prisoner=GTK_WIDGET(dlist->data);
|
|||
|
else
|
|||
|
new_prisoner=NULL;
|
|||
|
|
|||
|
/* look for already prisoned list items, we
|
|||
|
* will put them back into the list
|
|||
|
* remember to free the doubly linked list that
|
|||
|
* gtk_container_children() returns
|
|||
|
*/
|
|||
|
dlist=gtk_container_children(GTK_CONTAINER(frame));
|
|||
|
free_list=dlist;
|
|||
|
while (dlist) {
|
|||
|
GtkWidget *list_item;
|
|||
|
|
|||
|
list_item=dlist->data;
|
|||
|
|
|||
|
gtk_widget_reparent(list_item, gtklist);
|
|||
|
|
|||
|
dlist=dlist->next;
|
|||
|
}
|
|||
|
g_list_free(free_list);
|
|||
|
|
|||
|
/* if we have a new prisoner, remove him from the
|
|||
|
* GtkList and put him into the frame "Prison"
|
|||
|
* we need to unselect the item before
|
|||
|
*/
|
|||
|
if (new_prisoner) {
|
|||
|
GList static_dlist;
|
|||
|
|
|||
|
static_dlist.data=new_prisoner;
|
|||
|
static_dlist.next=NULL;
|
|||
|
static_dlist.prev=NULL;
|
|||
|
|
|||
|
gtk_list_unselect_child(GTK_LIST(gtklist),
|
|||
|
new_prisoner);
|
|||
|
gtk_widget_reparent(new_prisoner, frame);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* this is the signal handler that gets called if GtkList
|
|||
|
* emits the "selection_changed" signal
|
|||
|
*/
|
|||
|
void
|
|||
|
sigh_print_selection (GtkWidget *gtklist,
|
|||
|
gpointer func_data)
|
|||
|
{
|
|||
|
GList *dlist;
|
|||
|
|
|||
|
/* fetch the doubly linked list of selected items
|
|||
|
* of the GtkList, remember to treat this as read-only!
|
|||
|
*/
|
|||
|
dlist=GTK_LIST(gtklist)->selection;
|
|||
|
|
|||
|
/* if there are no selected items there is nothing more
|
|||
|
* to do than just telling the user so
|
|||
|
*/
|
|||
|
if (!dlist) {
|
|||
|
g_print("Selection cleared\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
/* ok, we got a selection and so we print it
|
|||
|
*/
|
|||
|
g_print("The selection is a ");
|
|||
|
|
|||
|
/* get the list item from the doubly linked list
|
|||
|
* and then query the data associated with list_item_data_key
|
|||
|
* we then just print it
|
|||
|
*/
|
|||
|
while (dlist) {
|
|||
|
GtkObject *list_item;
|
|||
|
gchar *item_data_string;
|
|||
|
|
|||
|
list_item=GTK_OBJECT(dlist->data);
|
|||
|
item_data_string=gtk_object_get_data(list_item,
|
|||
|
list_item_data_key);
|
|||
|
g_print("%s ", item_data_string);
|
|||
|
|
|||
|
dlist=dlist->next;
|
|||
|
}
|
|||
|
g_print("\n");
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> List Item Widget
|
|||
|
<p>
|
|||
|
The GtkListItem widget is designed to act as a container holding up
|
|||
|
to one child, providing functions for selection/deselection just like
|
|||
|
the GtkList widget requires them for its children.
|
|||
|
|
|||
|
A GtkListItem has its own window to receive events and has its own
|
|||
|
background color which is usualy white.
|
|||
|
|
|||
|
As it is directly derived from a
|
|||
|
GtkItem it can be treated as such by using the GTK_ITEM(ListItem)
|
|||
|
macro, see the GtkItem widget for more on this.
|
|||
|
Usualy a GtkListItem just holds a label to identify e.g. a filename
|
|||
|
within a GtkList -- therefore the convenient function
|
|||
|
gtk_list_item_new_with_label() is provided. The same effect can be
|
|||
|
achieved by creating a GtkLabel on its own, setting its alignment
|
|||
|
to xalign=0 and yalign=0.5 with a subsequent container addition
|
|||
|
to the GtkListItem.
|
|||
|
|
|||
|
As one is not forced to add a GtkLabel to a GtkListItem, you could
|
|||
|
also add a GtkVBox or a GtkArrow etc. to the GtkListItem.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Signals
|
|||
|
<p>
|
|||
|
A GtkListItem does not create new signals on its own, but inherits
|
|||
|
the signals of a GtkItem. *Note GtkItem::, for more info.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Functions
|
|||
|
<p>
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
guint gtk_list_item_get_type (void)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Returns the `GtkListItem' type identifier.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_list_item_new (void)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Create a new `GtkListItem' object. The new widget is
|
|||
|
returned as a pointer to a `GtkWidget' object.
|
|||
|
`NULL' is returned on failure.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Create a new `GtkListItem' object, having a single GtkLabel as
|
|||
|
the sole child. The new widget is returned as a pointer to a
|
|||
|
`GtkWidget' object.
|
|||
|
`NULL' is returned on failure.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_item_select (GtkListItem *LIST_ITEM)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function is basicaly a wrapper around a call to
|
|||
|
gtk_item_select (GTK_ITEM (list_item)) which will emit the
|
|||
|
select signal.
|
|||
|
*Note GtkItem::, for more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_list_item_deselect (GtkListItem *LIST_ITEM)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function is basicaly a wrapper around a call to
|
|||
|
gtk_item_deselect (GTK_ITEM (list_item)) which will emit the
|
|||
|
deselect signal.
|
|||
|
*Note GtkItem::, for more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkListItem* GTK_LIST_ITEM (gpointer OBJ)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Cast a generic pointer to `GtkListItem*'. *Note Standard Macros::,
|
|||
|
for more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Cast a generic pointer to `GtkListItemClass*'. *Note Standard
|
|||
|
Macros::, for more info.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint GTK_IS_LIST_ITEM (gpointer OBJ)
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Determine if a generic pointer refers to a `GtkListItem' object.
|
|||
|
*Note Standard Macros::, for more info.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Example
|
|||
|
<p>
|
|||
|
Please see the GtkList example on this, which covers the usage of a
|
|||
|
GtkListItem as well.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> File Selections
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<p>
|
|||
|
The file selection widget is a quick and simple way to display a File
|
|||
|
dialog box. It comes complete with Ok, Cancel, and Help buttons, a great way
|
|||
|
to cut down on programming time.
|
|||
|
|
|||
|
To create a new file selection box use:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_file_selection_new (gchar *title);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
To set the filename, for example to bring up a specific directory, or
|
|||
|
give a default filename, use this function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
To grab the text that the user has entered or clicked on, use this
|
|||
|
function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
There are also pointers to the widgets contained within the file
|
|||
|
selection widget. These are:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item>dir_list
|
|||
|
<item>file_list
|
|||
|
<item>selection_entry
|
|||
|
<item>selection_text
|
|||
|
<item>main_vbox
|
|||
|
<item>ok_button
|
|||
|
<item>cancel_button
|
|||
|
<item>help_button
|
|||
|
</itemize>
|
|||
|
|
|||
|
Most likely you will want to use the ok_button, cancel_button, and
|
|||
|
help_button pointers in signaling their use.
|
|||
|
|
|||
|
Included here is an example stolen from testgtk.c, modified to run
|
|||
|
on it's own. As you will see, there is nothing much to creating a file
|
|||
|
selection widget. While, in this example, the Help button appears on the
|
|||
|
screen, it does nothing as there is not a signal attached to it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* filesel.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
/* Get the selected filename and print it to the console */
|
|||
|
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
|
|||
|
{
|
|||
|
g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
|
|||
|
}
|
|||
|
|
|||
|
void destroy (GtkWidget *widget, gpointer *data)
|
|||
|
{
|
|||
|
gtk_main_quit ();
|
|||
|
}
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *filew;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* Create a new file selection widget */
|
|||
|
filew = gtk_file_selection_new ("File selection");
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (filew), "destroy",
|
|||
|
(GtkSignalFunc) destroy, &filew);
|
|||
|
/* Connect the ok_button to file_ok_sel function */
|
|||
|
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
|
|||
|
"clicked", (GtkSignalFunc) file_ok_sel, filew );
|
|||
|
|
|||
|
/* Connect the cancel_button to destroy the widget */
|
|||
|
gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button),
|
|||
|
"clicked", (GtkSignalFunc) gtk_widget_destroy,
|
|||
|
GTK_OBJECT (filew));
|
|||
|
|
|||
|
/* Lets set the filename, as if this were a save dialog, and we are giving
|
|||
|
a default filename */
|
|||
|
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew),
|
|||
|
"penguin.png");
|
|||
|
|
|||
|
gtk_widget_show(filew);
|
|||
|
gtk_main ();
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Menu Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
There are two ways to create menus, there's the easy way, and there's the
|
|||
|
hard way. Both have their uses, but you can usually use the menufactory
|
|||
|
(the easy way). The "hard" way is to create all the menus using the calls
|
|||
|
directly. The easy way is to use the gtk_menu_factory calls. This is
|
|||
|
much simpler, but there are advantages and disadvantages to each approach.
|
|||
|
|
|||
|
The menufactory is much easier to use, and to add new menus to, although
|
|||
|
writing a few wrapper functions to create menus using the manual method
|
|||
|
could go a long way towards usability. With the menufactory, it is not
|
|||
|
possible to add images or the character '/' to the menus.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Manual Menu Creation
|
|||
|
<p>
|
|||
|
In the true tradition of teaching, we'll show you the hard
|
|||
|
way first. <tt>:)</>
|
|||
|
<p>
|
|||
|
There are three widgets that go into making a menubar and submenus:
|
|||
|
<itemize>
|
|||
|
<item>a menu item, which is what the user wants to select, e.g. 'Save'
|
|||
|
<item>a menu, which acts as a container for the menu items, and
|
|||
|
<item>a menubar, which is a container for each of the individual menus,
|
|||
|
</itemize>
|
|||
|
|
|||
|
This is slightly complicated by the fact that menu item widgets are used for two different things. They are
|
|||
|
both the widets that are packed into the menu, and the widget that is packed into the menubar, which,
|
|||
|
when selected, activiates the menu.
|
|||
|
|
|||
|
Let's look at the functions that are used to create menus and menubars.
|
|||
|
This first function is used to create a new menubar.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_menu_bar_new(void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This rather self explanatory function creates a new menubar. You use
|
|||
|
gtk_container_add to pack this into a window, or the box_pack functions to
|
|||
|
pack it into a box - the same as buttons.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_menu_new();
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function returns a pointer to a new menu, it is never actually shown
|
|||
|
(with gtk_widget_show), it is just a container for the menu items. Hopefully this will
|
|||
|
become more clear when you look at the example below.
|
|||
|
<p>
|
|||
|
The next two calls are used to create menu items that are packed into
|
|||
|
the menu (and menubar).
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_menu_item_new();
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
and
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget *gtk_menu_item_new_with_label(const char *label);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
These calls are used to create the menu items that are to be displayed.
|
|||
|
Remember to differentiate between a "menu" as created with gtk_menu_new
|
|||
|
and a "menu item" as created by the gtk_menu_item_new functions. The
|
|||
|
menu item will be an actual button with an associated action,
|
|||
|
whereas a menu will be a container holding menu items.
|
|||
|
|
|||
|
The gtk_menu_new_with_label and gtk_menu_new functions are just as you'd expect after
|
|||
|
reading about the buttons. One creates a new menu item with a label
|
|||
|
already packed into it, and the other just creates a blank menu item.
|
|||
|
|
|||
|
Once you've created a menu item you have to put it into a menu. This is done using the function
|
|||
|
gtk_menu_append. In order to capture when the item is selected by the user, we need to connect
|
|||
|
to the <tt/activate/ signal in the usual way.
|
|||
|
So, if we wanted to create a standard <tt/File/ menu, with the options <tt/Open/,
|
|||
|
<tt/Save/ and <tt/Quit/ the code would look something like
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
file_menu = gtk_menu_new(); /* Don't need to show menus */
|
|||
|
|
|||
|
/* Create the menu items */
|
|||
|
open_item = gtk_menu_item_new_with_label("Open");
|
|||
|
save_item = gtk_menu_item_new_with_label("Save");
|
|||
|
quit_item = gtk_menu_item_new_with_label("Quit");
|
|||
|
|
|||
|
/* Add them to the menu */
|
|||
|
gtk_menu_append( GTK_MENU(file_menu), open_item);
|
|||
|
gtk_menu_append( GTK_MENU(file_menu), save_item);
|
|||
|
gtk_menu_append( GTK_MENU(file_menu), quit_item);
|
|||
|
|
|||
|
/* Attach the callback functions to the activate signal */
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(open_items), "activate",
|
|||
|
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.open");
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(save_items), "activate",
|
|||
|
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) "file.save");
|
|||
|
|
|||
|
/* We can attach the Quit menu item to our exit function */
|
|||
|
gtk_signal_connect_object( GTK_OBJECT(quit_items), "activate",
|
|||
|
GTK_SIGNAL_FUNC(destroy), (gpointer) "file.quit");
|
|||
|
|
|||
|
/* We do need to show menu items */
|
|||
|
gtk_widget_show( open_item );
|
|||
|
gtk_widget_show( save_item );
|
|||
|
gtk_widget_show( quit_item );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
At this point we have our menu. Now we need to create a menubar and a menu item for the <tt/File/ entry,
|
|||
|
to which we add our menu. The code looks like this
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
menu_bar = gtk_menu_bar_new();
|
|||
|
gtk_container_add( GTK_CONTAINER(window), menu_bar);
|
|||
|
gtk_widget_show( menu_bar );
|
|||
|
|
|||
|
file_item = gtk_menu_item_new_with_label("File");
|
|||
|
gtk_widget_show(file_item);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Now we need to associate the menu with <tt/file_item/. This is done with the function
|
|||
|
|
|||
|
<tscreen>
|
|||
|
void gtk_menu_item_set_submenu( GtkMenuItem *menu_item,
|
|||
|
GtkWidget *submenu);
|
|||
|
</tscreen>
|
|||
|
|
|||
|
So, our example would continue with
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_menu_item_set_submenu( GTK_MENU_ITEM(file_item), file_menu);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
All that is left to do is to add the menu to the menubar, which is accomplished using the function
|
|||
|
|
|||
|
<tscreen>
|
|||
|
void gtk_menu_bar_append( GtkMenuBar *menu_bar, GtkWidget *menu_item);
|
|||
|
</tscreen>
|
|||
|
|
|||
|
which in our case looks like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_menu_bar_append( menu_bar, file_item );
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
If we wanted the menu right justified on the menubar, such as help menus often are, we can
|
|||
|
use the following function (again on <tt/file_item/ in the current example) before attaching
|
|||
|
it to the menubar.
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_menu_item_right_justify (GtkMenuItem *menu_item);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Here is a summary of the steps needed to create a menu bar with menus attached:
|
|||
|
<itemize>
|
|||
|
<item> Create a new menu using gtk_menu_new()
|
|||
|
<item> Use multiple calls to gtk_menu_item_new() for each item you wish to have on
|
|||
|
your menu. And use gtk_menu_append() to put each of these new items on
|
|||
|
to the menu.
|
|||
|
<item> Create a menu item using gtk_menu_item_new(). This will be the root of
|
|||
|
the menu, the text appearing here will be on the menubar itself.
|
|||
|
<item> Use gtk_menu_item_set_submenu() to attach the menu to
|
|||
|
the root menu item (The one created in the above step).
|
|||
|
<item> Create a new menubar using gtk_menu_bar_new. This step only needs
|
|||
|
to be done once when creating a series of menus on one menu bar.
|
|||
|
<item> Use gtk_menu_bar_append to put the root menu onto the menubar.
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
Creating a popup menu is nearly the same. The difference is that the
|
|||
|
menu is not posted `automatically' by a menubar, but explicitly
|
|||
|
by calling the function gtk_menu_popup() from a button-press event, for example.
|
|||
|
Take these steps:
|
|||
|
<itemize>
|
|||
|
<item>Create an event handling function. It needs to have the prototype
|
|||
|
<tscreen>
|
|||
|
static gint handler(GtkWidget *widget, GdkEvent *event);
|
|||
|
</tscreen>
|
|||
|
and it will use the event to find out where to pop up the menu.
|
|||
|
<item>In the event handler, if event is a mouse button press, treat
|
|||
|
<tt>event</tt> as a button event (which it is) and use it as
|
|||
|
shown in the sample code to pass information to gtk_menu_popup().
|
|||
|
<item>Bind that event handler to a widget with
|
|||
|
<tscreen>
|
|||
|
gtk_signal_connect_object(GTK_OBJECT(widget), "event",
|
|||
|
GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
|
|||
|
</tscreen>
|
|||
|
where <tt>widget</tt> is the widget you are binding to, <tt>handler</tt>
|
|||
|
is the handling function, and <tt>menu</tt> is a menu created with
|
|||
|
gtk_menu_new(). This can be a menu which is also posted by a menu bar,
|
|||
|
as shown in the sample code.
|
|||
|
</itemize>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Manual Menu Example
|
|||
|
<p>
|
|||
|
That should about do it. Let's take a look at an example to help clarify.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* menu.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
static gint button_press (GtkWidget *, GdkEvent *);
|
|||
|
static void menuitem_response (gchar *);
|
|||
|
|
|||
|
int main (int argc, char *argv[])
|
|||
|
{
|
|||
|
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *menu;
|
|||
|
GtkWidget *menu_bar;
|
|||
|
GtkWidget *root_menu;
|
|||
|
GtkWidget *menu_items;
|
|||
|
GtkWidget *vbox;
|
|||
|
GtkWidget *button;
|
|||
|
char buf[128];
|
|||
|
int i;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* create a new window */
|
|||
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_set_usize( GTK_WIDGET (window), 200, 100);
|
|||
|
gtk_window_set_title(GTK_WINDOW (window), "GTK Menu Test");
|
|||
|
gtk_signal_connect(GTK_OBJECT (window), "delete_event",
|
|||
|
(GtkSignalFunc) gtk_exit, NULL);
|
|||
|
|
|||
|
/* Init the menu-widget, and remember -- never
|
|||
|
* gtk_show_widget() the menu widget!!
|
|||
|
* This is the menu that holds the menu items, the one that
|
|||
|
* will pop up when you click on the "Root Menu" in the app */
|
|||
|
menu = gtk_menu_new();
|
|||
|
|
|||
|
/* Next we make a little loop that makes three menu-entries for "test-menu".
|
|||
|
* Notice the call to gtk_menu_append. Here we are adding a list of
|
|||
|
* menu items to our menu. Normally, we'd also catch the "clicked"
|
|||
|
* signal on each of the menu items and setup a callback for it,
|
|||
|
* but it's omitted here to save space. */
|
|||
|
|
|||
|
for(i = 0; i < 3; i++)
|
|||
|
{
|
|||
|
/* Copy the names to the buf. */
|
|||
|
sprintf(buf, "Test-undermenu - %d", i);
|
|||
|
|
|||
|
/* Create a new menu-item with a name... */
|
|||
|
menu_items = gtk_menu_item_new_with_label(buf);
|
|||
|
|
|||
|
/* ...and add it to the menu. */
|
|||
|
gtk_menu_append(GTK_MENU (menu), menu_items);
|
|||
|
|
|||
|
/* Do something interesting when the menuitem is selected */
|
|||
|
gtk_signal_connect_object(GTK_OBJECT(menu_items), "activate",
|
|||
|
GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf));
|
|||
|
|
|||
|
/* Show the widget */
|
|||
|
gtk_widget_show(menu_items);
|
|||
|
}
|
|||
|
|
|||
|
/* This is the root menu, and will be the label
|
|||
|
* displayed on the menu bar. There won't be a signal handler attached,
|
|||
|
* as it only pops up the rest of the menu when pressed. */
|
|||
|
root_menu = gtk_menu_item_new_with_label("Root Menu");
|
|||
|
|
|||
|
gtk_widget_show(root_menu);
|
|||
|
|
|||
|
/* Now we specify that we want our newly created "menu" to be the menu
|
|||
|
* for the "root menu" */
|
|||
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
|
|||
|
|
|||
|
/* A vbox to put a menu and a button in: */
|
|||
|
vbox = gtk_vbox_new(FALSE, 0);
|
|||
|
gtk_container_add(GTK_CONTAINER(window), vbox);
|
|||
|
gtk_widget_show(vbox);
|
|||
|
|
|||
|
/* Create a menu-bar to hold the menus and add it to our main window */
|
|||
|
menu_bar = gtk_menu_bar_new();
|
|||
|
gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
|
|||
|
gtk_widget_show(menu_bar);
|
|||
|
|
|||
|
/* Create a button to which to attach menu as a popup */
|
|||
|
button = gtk_button_new_with_label("press me");
|
|||
|
gtk_signal_connect_object(GTK_OBJECT(button), "event",
|
|||
|
GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu));
|
|||
|
gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2);
|
|||
|
gtk_widget_show(button);
|
|||
|
|
|||
|
/* And finally we append the menu-item to the menu-bar -- this is the
|
|||
|
* "root" menu-item I have been raving about =) */
|
|||
|
gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
|
|||
|
|
|||
|
/* always display the window as the last step so it all splashes on
|
|||
|
* the screen at once. */
|
|||
|
gtk_widget_show(window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/* Respond to a button-press by posting a menu passed in as widget.
|
|||
|
*
|
|||
|
* Note that the "widget" argument is the menu being posted, NOT
|
|||
|
* the button that was pressed.
|
|||
|
*/
|
|||
|
|
|||
|
static gint button_press (GtkWidget *widget, GdkEvent *event)
|
|||
|
{
|
|||
|
|
|||
|
if (event->type == GDK_BUTTON_PRESS) {
|
|||
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|||
|
gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
|
|||
|
bevent->button, bevent->time);
|
|||
|
/* Tell calling code that we have handled this event; the buck
|
|||
|
* stops here. */
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
/* Tell calling code that we have not handled this event; pass it on. */
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Print a string when a menu item is selected */
|
|||
|
|
|||
|
static void menuitem_response (gchar *string)
|
|||
|
{
|
|||
|
printf("%s\n", string);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
You may also set a menu item to be insensitive and, using an accelerator
|
|||
|
table, bind keys to menu functions.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Using GtkMenuFactory
|
|||
|
<p>
|
|||
|
Now that we've shown you the hard way, here's how you do it using the
|
|||
|
gtk_menu_factory calls.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Menu Factory Example
|
|||
|
<p>
|
|||
|
Here is an example using the GTK menu factory. This is the first file,
|
|||
|
menufactory.h. We keep a separate menufactory.c and mfmain.c because of the global variables used
|
|||
|
in the menufactory.c file.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* menufactory.h */
|
|||
|
|
|||
|
#ifndef __MENUFACTORY_H__
|
|||
|
#define __MENUFACTORY_H__
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
extern "C" {
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table);
|
|||
|
void menus_create(GtkMenuEntry *entries, int nmenu_entries);
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
}
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
#endif /* __MENUFACTORY_H__ */
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And here is the menufactory.c file.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* menufactory.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include <strings.h>
|
|||
|
|
|||
|
#include "mfmain.h"
|
|||
|
|
|||
|
|
|||
|
static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path);
|
|||
|
static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path);
|
|||
|
void menus_init(void);
|
|||
|
void menus_create(GtkMenuEntry * entries, int nmenu_entries);
|
|||
|
|
|||
|
|
|||
|
/* this is the GtkMenuEntry structure used to create new menus. The
|
|||
|
* first member is the menu definition string. The second, the
|
|||
|
* default accelerator key used to access this menu function with
|
|||
|
* the keyboard. The third is the callback function to call when
|
|||
|
* this menu item is selected (by the accelerator key, or with the
|
|||
|
* mouse.) The last member is the data to pass to your callback function.
|
|||
|
*/
|
|||
|
|
|||
|
static GtkMenuEntry menu_items[] =
|
|||
|
{
|
|||
|
{"<Main>/File/New", "<control>N", NULL, NULL},
|
|||
|
{"<Main>/File/Open", "<control>O", NULL, NULL},
|
|||
|
{"<Main>/File/Save", "<control>S", NULL, NULL},
|
|||
|
{"<Main>/File/Save as", NULL, NULL, NULL},
|
|||
|
{"<Main>/File/<separator>", NULL, NULL, NULL},
|
|||
|
{"<Main>/File/Quit", "<control>Q", file_quit_cmd_callback, "OK, I'll quit"},
|
|||
|
{"<Main>/Options/Test", NULL, NULL, NULL}
|
|||
|
};
|
|||
|
|
|||
|
/* calculate the number of menu_item's */
|
|||
|
static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
|
|||
|
|
|||
|
static int initialize = TRUE;
|
|||
|
static GtkMenuFactory *factory = NULL;
|
|||
|
static GtkMenuFactory *subfactory[1];
|
|||
|
static GHashTable *entry_ht = NULL;
|
|||
|
|
|||
|
void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table)
|
|||
|
{
|
|||
|
if (initialize)
|
|||
|
menus_init();
|
|||
|
|
|||
|
if (menubar)
|
|||
|
*menubar = subfactory[0]->widget;
|
|||
|
if (table)
|
|||
|
*table = subfactory[0]->table;
|
|||
|
}
|
|||
|
|
|||
|
void menus_init(void)
|
|||
|
{
|
|||
|
if (initialize) {
|
|||
|
initialize = FALSE;
|
|||
|
|
|||
|
factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
|
|||
|
subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR);
|
|||
|
|
|||
|
gtk_menu_factory_add_subfactory(factory, subfactory[0], "<Main>");
|
|||
|
menus_create(menu_items, nmenu_items);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void menus_create(GtkMenuEntry * entries, int nmenu_entries)
|
|||
|
{
|
|||
|
char *accelerator;
|
|||
|
int i;
|
|||
|
|
|||
|
if (initialize)
|
|||
|
menus_init();
|
|||
|
|
|||
|
if (entry_ht)
|
|||
|
for (i = 0; i < nmenu_entries; i++) {
|
|||
|
accelerator = g_hash_table_lookup(entry_ht, entries[i].path);
|
|||
|
if (accelerator) {
|
|||
|
if (accelerator[0] == '\0')
|
|||
|
entries[i].accelerator = NULL;
|
|||
|
else
|
|||
|
entries[i].accelerator = accelerator;
|
|||
|
}
|
|||
|
}
|
|||
|
gtk_menu_factory_add_entries(factory, entries, nmenu_entries);
|
|||
|
|
|||
|
for (i = 0; i < nmenu_entries; i++)
|
|||
|
if (entries[i].widget) {
|
|||
|
gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator",
|
|||
|
(GtkSignalFunc) menus_install_accel,
|
|||
|
entries[i].path);
|
|||
|
gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator",
|
|||
|
(GtkSignalFunc) menus_remove_accel,
|
|||
|
entries[i].path);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path)
|
|||
|
{
|
|||
|
char accel[64];
|
|||
|
char *t1, t2[2];
|
|||
|
|
|||
|
accel[0] = '\0';
|
|||
|
if (modifiers & GDK_CONTROL_MASK)
|
|||
|
strcat(accel, "<control>");
|
|||
|
if (modifiers & GDK_SHIFT_MASK)
|
|||
|
strcat(accel, "<shift>");
|
|||
|
if (modifiers & GDK_MOD1_MASK)
|
|||
|
strcat(accel, "<alt>");
|
|||
|
|
|||
|
t2[0] = key;
|
|||
|
t2[1] = '\0';
|
|||
|
strcat(accel, t2);
|
|||
|
|
|||
|
if (entry_ht) {
|
|||
|
t1 = g_hash_table_lookup(entry_ht, path);
|
|||
|
g_free(t1);
|
|||
|
} else
|
|||
|
entry_ht = g_hash_table_new(g_str_hash, g_str_equal);
|
|||
|
|
|||
|
g_hash_table_insert(entry_ht, path, g_strdup(accel));
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path)
|
|||
|
{
|
|||
|
char *t;
|
|||
|
|
|||
|
if (entry_ht) {
|
|||
|
t = g_hash_table_lookup(entry_ht, path);
|
|||
|
g_free(t);
|
|||
|
|
|||
|
g_hash_table_insert(entry_ht, path, g_strdup(""));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void menus_set_sensitive(char *path, int sensitive)
|
|||
|
{
|
|||
|
GtkMenuPath *menu_path;
|
|||
|
|
|||
|
if (initialize)
|
|||
|
menus_init();
|
|||
|
|
|||
|
menu_path = gtk_menu_factory_find(factory, path);
|
|||
|
if (menu_path)
|
|||
|
gtk_widget_set_sensitive(menu_path->widget, sensitive);
|
|||
|
else
|
|||
|
g_warning("Unable to set sensitivity for menu which doesn't exist: %s", path);
|
|||
|
}
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And here's the mfmain.h
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* mfmain.h */
|
|||
|
|
|||
|
#ifndef __MFMAIN_H__
|
|||
|
#define __MFMAIN_H__
|
|||
|
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
extern "C" {
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
void file_quit_cmd_callback(GtkWidget *widget, gpointer data);
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
}
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
#endif /* __MFMAIN_H__ */
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And mfmain.c
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* mfmain.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
#include "mfmain.h"
|
|||
|
#include "menufactory.h"
|
|||
|
|
|||
|
|
|||
|
int main(int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *main_vbox;
|
|||
|
GtkWidget *menubar;
|
|||
|
|
|||
|
GtkAcceleratorTable *accel;
|
|||
|
|
|||
|
gtk_init(&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_signal_connect(GTK_OBJECT(window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC(file_quit_cmd_callback),
|
|||
|
"WM destroy");
|
|||
|
gtk_window_set_title(GTK_WINDOW(window), "Menu Factory");
|
|||
|
gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
|
|||
|
|
|||
|
main_vbox = gtk_vbox_new(FALSE, 1);
|
|||
|
gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
|
|||
|
gtk_container_add(GTK_CONTAINER(window), main_vbox);
|
|||
|
gtk_widget_show(main_vbox);
|
|||
|
|
|||
|
get_main_menu(&menubar, &accel);
|
|||
|
gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
|
|||
|
gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
|
|||
|
gtk_widget_show(menubar);
|
|||
|
|
|||
|
gtk_widget_show(window);
|
|||
|
gtk_main();
|
|||
|
|
|||
|
return(0);
|
|||
|
}
|
|||
|
|
|||
|
/* This is just to demonstrate how callbacks work when using the
|
|||
|
* menufactory. Often, people put all the callbacks from the menus
|
|||
|
* in a separate file, and then have them call the appropriate functions
|
|||
|
* from there. Keeps it more organized. */
|
|||
|
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
g_print ("%s\n", (char *) data);
|
|||
|
gtk_exit(0);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And a makefile so it'll be easier to compile it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
# Makefile.mf
|
|||
|
|
|||
|
CC = gcc
|
|||
|
PROF = -g
|
|||
|
C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG
|
|||
|
L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib
|
|||
|
L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm
|
|||
|
PROGNAME = menufactory
|
|||
|
|
|||
|
O_FILES = menufactory.o mfmain.o
|
|||
|
|
|||
|
$(PROGNAME): $(O_FILES)
|
|||
|
rm -f $(PROGNAME)
|
|||
|
$(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS)
|
|||
|
|
|||
|
.c.o:
|
|||
|
$(CC) -c $(C_FLAGS) $<
|
|||
|
|
|||
|
clean:
|
|||
|
rm -f core *.o $(PROGNAME) nohup.out
|
|||
|
distclean: clean
|
|||
|
rm -f *~
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
For now, there's only this example. An explanation and lots 'o' comments
|
|||
|
will follow later.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> Undocumented Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
These all require authors! :) Please consider contributing to our tutorial.
|
|||
|
|
|||
|
If you must use one of these widgets that are undocumented, I strongly
|
|||
|
suggest you take a look at their respective header files in the GTK distro.
|
|||
|
GTK's function names are very descriptive. Once you have an understanding
|
|||
|
of how things work, it's not easy to figure out how to use a widget simply
|
|||
|
by looking at it's function declarations. This, along with a few examples
|
|||
|
from others' code, and it should be no problem.
|
|||
|
|
|||
|
When you do come to understand all the functions of a new undocumented
|
|||
|
widget, please consider writing a tutorial on it so others may benifit from
|
|||
|
your time.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Color Selections
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Range Controls
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Text Boxes
|
|||
|
<p>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Previews
|
|||
|
<p>
|
|||
|
|
|||
|
(This may need to be rewritten to follow the style of the rest of the tutorial)
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
|
|||
|
Previews serve a number of purposes in GIMP/GTK. The most important one is
|
|||
|
this. High quality images may take up to tens of megabytes of memory - easy!
|
|||
|
Any operation on an image that big is bound to take a long time. If it takes
|
|||
|
you 5-10 trial-and-errors (i.e. 10-20 steps, since you have to revert after
|
|||
|
you make an error) to choose the desired modification, it make take you
|
|||
|
literally hours to make the right one - if you don't run out of memory
|
|||
|
first. People who have spent hours in color darkrooms know the feeling.
|
|||
|
Previews to the rescue!
|
|||
|
|
|||
|
But the annoyance of the delay is not the only issue. Oftentimes it is
|
|||
|
helpful to compare the Before and After versions side-by-side or at least
|
|||
|
back-to-back. If you're working with big images and 10 second delays,
|
|||
|
obtaining the Before and After impressions is, to say the least, difficult.
|
|||
|
For 30M images (4"x6", 600dpi, 24 bit) the side-by-side comparison is right
|
|||
|
out for most people, while back-to-back is more like back-to-1001, 1002,
|
|||
|
..., 1010-back! Previews to the rescue!
|
|||
|
|
|||
|
But there's more. Previews allow for side-by-side pre-previews. In other
|
|||
|
words, you write a plug-in (e.g. the filterpack simulation) which would have
|
|||
|
a number of here's-what-it-would-look-like-if-you-were-to-do-this previews.
|
|||
|
An approach like this acts as a sort of a preview palette and is very
|
|||
|
effective fow subtle changes. Let's go previews!
|
|||
|
|
|||
|
There's more. For certain plug-ins real-time image-specific human
|
|||
|
intervention maybe necessary. In the SuperNova plug-in, for example, the
|
|||
|
user is asked to enter the coordinates of the center of the future
|
|||
|
supernova. The easiest way to do this, really, is to present the user with a
|
|||
|
preview and ask him to intereactively select the spot. Let's go previews!
|
|||
|
|
|||
|
Finally, a couple of misc uses. One can use previews even when not working
|
|||
|
with big images. For example, they are useful when rendering compicated
|
|||
|
patterns. (Just check out the venerable Diffraction plug-in + many other
|
|||
|
ones!) As another example, take a look at the colormap rotation plug-in
|
|||
|
(work in progress). You can also use previews for little logo's inside you
|
|||
|
plug-ins and even for an image of yourself, The Author. Let's go previews!
|
|||
|
|
|||
|
When Not to Use Previews
|
|||
|
|
|||
|
Don't use previews for graphs, drawing etc. GDK is much faster for that. Use
|
|||
|
previews only for rendered images!
|
|||
|
|
|||
|
Let's go previews!
|
|||
|
|
|||
|
You can stick a preview into just about anything. In a vbox, an hbox, a
|
|||
|
table, a button, etc. But they look their best in tight frames around them.
|
|||
|
Previews by themselves do not have borders and look flat without them. (Of
|
|||
|
course, if the flat look is what you want...) Tight frames provide the
|
|||
|
necessary borders.
|
|||
|
|
|||
|
[Image][Image]
|
|||
|
|
|||
|
Previews in many ways are like any other widgets in GTK (whatever that
|
|||
|
means) except they possess an addtional feature: they need to be filled with
|
|||
|
some sort of an image! First, we will deal exclusively with the GTK aspect
|
|||
|
of previews and then we'll discuss how to fill them.
|
|||
|
|
|||
|
GtkWidget *preview!
|
|||
|
|
|||
|
Without any ado:
|
|||
|
|
|||
|
/* Create a preview widget,
|
|||
|
set its size, an show it */
|
|||
|
GtkWidget *preview;
|
|||
|
preview=gtk_preview_new(GTK_PREVIEW_COLOR)
|
|||
|
/*Other option:
|
|||
|
GTK_PREVIEW_GRAYSCALE);*/
|
|||
|
gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT);
|
|||
|
gtk_widget_show(preview);
|
|||
|
my_preview_rendering_function(preview);
|
|||
|
|
|||
|
Oh yeah, like I said, previews look good inside frames, so how about:
|
|||
|
|
|||
|
GtkWidget *create_a_preview(int Width,
|
|||
|
int Height,
|
|||
|
int Colorfulness)
|
|||
|
{
|
|||
|
GtkWidget *preview;
|
|||
|
GtkWidget *frame;
|
|||
|
|
|||
|
frame = gtk_frame_new(NULL);
|
|||
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|||
|
gtk_container_border_width (GTK_CONTAINER(frame),0);
|
|||
|
gtk_widget_show(frame);
|
|||
|
|
|||
|
preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
|
|||
|
:GTK_PREVIEW_GRAYSCALE);
|
|||
|
gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
|
|||
|
gtk_container_add(GTK_CONTAINER(frame),preview);
|
|||
|
gtk_widget_show(preview);
|
|||
|
|
|||
|
my_preview_rendering_function(preview);
|
|||
|
return frame;
|
|||
|
}
|
|||
|
|
|||
|
That's my basic preview. This routine returns the "parent" frame so you can
|
|||
|
place it somewhere else in your interface. Of course, you can pass the
|
|||
|
parent frame to this routine as a parameter. In many situations, however,
|
|||
|
the contents of the preview are changed continually by your application. In
|
|||
|
this case you may want to pass a pointer to the preview to a
|
|||
|
"create_a_preview()" and thus have control of it later.
|
|||
|
|
|||
|
One more important note that may one day save you a lot of time. Sometimes
|
|||
|
it is desirable to label you preview. For example, you may label the preview
|
|||
|
containing the original image as "Original" and the one containing the
|
|||
|
modified image as "Less Original". It might occure to you to pack the
|
|||
|
preview along with the appropriate label into a vbox. The unexpected caveat
|
|||
|
is that if the label is wider than the preview (which may happen for a
|
|||
|
variety of reasons unforseeable to you, from the dynamic decision on the
|
|||
|
size of the preview to the size of the font) the frame expands and no longer
|
|||
|
fits tightly over the preview. The same problem can probably arise in other
|
|||
|
situations as well.
|
|||
|
|
|||
|
[Image]
|
|||
|
|
|||
|
The solution is to place the preview and the label into a 2x1 table and by
|
|||
|
attaching them with the following paramters (this is one possible variations
|
|||
|
of course. The key is no GTK_FILL in the second attachment):
|
|||
|
|
|||
|
gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
|
|||
|
0,
|
|||
|
GTK_EXPAND|GTK_FILL,
|
|||
|
0,0);
|
|||
|
gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
|
|||
|
GTK_EXPAND,
|
|||
|
GTK_EXPAND,
|
|||
|
0,0);
|
|||
|
|
|||
|
|
|||
|
And here's the result:
|
|||
|
|
|||
|
[Image]
|
|||
|
|
|||
|
Misc
|
|||
|
|
|||
|
Making a preview clickable is achieved most easily by placing it in a
|
|||
|
button. It also adds a nice border around the preview and you may not even
|
|||
|
need to place it in a frame. See the Filter Pack Simulation plug-in for an
|
|||
|
example.
|
|||
|
|
|||
|
This is pretty much it as far as GTK is concerned.
|
|||
|
|
|||
|
Filling In a Preview
|
|||
|
|
|||
|
In order to familiarize ourselves with the basics of filling in previews,
|
|||
|
let's create the following pattern (contrived by trial and error):
|
|||
|
|
|||
|
[Image]
|
|||
|
|
|||
|
void
|
|||
|
my_preview_rendering_function(GtkWidget *preview)
|
|||
|
{
|
|||
|
#define SIZE 100
|
|||
|
#define HALF (SIZE/2)
|
|||
|
|
|||
|
guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits per dot */
|
|||
|
gint i, j; /* Coordinates */
|
|||
|
double r, alpha, x, y;
|
|||
|
|
|||
|
if (preview==NULL) return; /* I usually add this when I want */
|
|||
|
/* to avoid silly crashes. You */
|
|||
|
/* should probably make sure that */
|
|||
|
/* everything has been nicely */
|
|||
|
/* initialized! */
|
|||
|
for (j=0; j < ABS(cos(2*alpha)) ) { /* Are we inside the shape? */
|
|||
|
/* glib.h contains ABS(x). */
|
|||
|
row[i*3+0] = sqrt(1-r)*255; /* Define Red */
|
|||
|
row[i*3+1] = 128; /* Define Green */
|
|||
|
row[i*3+2] = 224; /* Define Blue */
|
|||
|
} /* "+0" is for alignment! */
|
|||
|
else {
|
|||
|
row[i*3+0] = r*255;
|
|||
|
row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
|
|||
|
row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
|
|||
|
}
|
|||
|
}
|
|||
|
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
|
|||
|
/* Insert "row" into "preview" starting at the point with */
|
|||
|
/* coordinates (0,j) first column, j_th row extending SIZE */
|
|||
|
/* pixels to the right */
|
|||
|
}
|
|||
|
|
|||
|
free(row); /* save some space */
|
|||
|
gtk_widget_draw(preview,NULL); /* what does this do? */
|
|||
|
gdk_flush(); /* or this? */
|
|||
|
}
|
|||
|
|
|||
|
Non-GIMP users can have probably seen enough to do a lot of things already.
|
|||
|
For the GIMP users I have a few pointers to add.
|
|||
|
|
|||
|
Image Preview
|
|||
|
|
|||
|
It is probably wize to keep a reduced version of the image around with just
|
|||
|
enough pixels to fill the preview. This is done by selecting every n'th
|
|||
|
pixel where n is the ratio of the size of the image to the size of the
|
|||
|
preview. All further operations (including filling in the previews) are then
|
|||
|
performed on the reduced number of pixels only. The following is my
|
|||
|
implementation of reducing the image. (Keep in mind that I've had only basic
|
|||
|
C!)
|
|||
|
|
|||
|
(UNTESTED CODE ALERT!!!)
|
|||
|
|
|||
|
typedef struct {
|
|||
|
gint width;
|
|||
|
gint height;
|
|||
|
gint bbp;
|
|||
|
guchar *rgb;
|
|||
|
guchar *mask;
|
|||
|
} ReducedImage;
|
|||
|
|
|||
|
enum {
|
|||
|
SELECTION_ONLY,
|
|||
|
SELCTION_IN_CONTEXT,
|
|||
|
ENTIRE_IMAGE
|
|||
|
};
|
|||
|
|
|||
|
ReducedImage *Reduce_The_Image(GDrawable *drawable,
|
|||
|
GDrawable *mask,
|
|||
|
gint LongerSize,
|
|||
|
gint Selection)
|
|||
|
{
|
|||
|
/* This function reduced the image down to the the selected preview size */
|
|||
|
/* The preview size is determine by LongerSize, i.e. the greater of the */
|
|||
|
/* two dimentions. Works for RGB images only! */
|
|||
|
gint RH, RW; /* Reduced height and reduced width */
|
|||
|
gint width, height; /* Width and Height of the area being reduced */
|
|||
|
gint bytes=drawable->bpp;
|
|||
|
ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage));
|
|||
|
|
|||
|
guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B;
|
|||
|
gint i, j, whichcol, whichrow, x1, x2, y1, y2;
|
|||
|
GPixelRgn srcPR, srcMask;
|
|||
|
gint NoSelectionMade=TRUE; /* Assume that we're dealing with the entire */
|
|||
|
/* image. */
|
|||
|
|
|||
|
gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
|
|||
|
width = x2-x1;
|
|||
|
height = y2-y1;
|
|||
|
/* If there's a SELECTION, we got its bounds!)
|
|||
|
|
|||
|
if (width != drawable->width && height != drawable->height)
|
|||
|
NoSelectionMade=FALSE;
|
|||
|
/* Become aware of whether the user has made an active selection */
|
|||
|
/* This will become important later, when creating a reduced mask. */
|
|||
|
|
|||
|
/* If we want to preview the entire image, overrule the above! */
|
|||
|
/* Of course, if no selection has been made, this does nothing! */
|
|||
|
if (Selection==ENTIRE_IMAGE) {
|
|||
|
x1=0;
|
|||
|
x2=drawable->width;
|
|||
|
y1=0;
|
|||
|
y2=drawable->height;
|
|||
|
}
|
|||
|
|
|||
|
/* If we want to preview a selection with some surronding area we */
|
|||
|
/* have to expand it a little bit. Consider it a bit of a riddle. */
|
|||
|
if (Selection==SELECTION_IN_CONTEXT) {
|
|||
|
x1=MAX(0, x1-width/2.0);
|
|||
|
x2=MIN(drawable->width, x2+width/2.0);
|
|||
|
y1=MAX(0, y1-height/2.0);
|
|||
|
y2=MIN(drawable->height, y2+height/2.0);
|
|||
|
}
|
|||
|
|
|||
|
/* How we can determine the width and the height of the area being */
|
|||
|
/* reduced. */
|
|||
|
width = x2-x1;
|
|||
|
height = y2-y1;
|
|||
|
|
|||
|
/* The lines below determine which dimension is to be the longer */
|
|||
|
/* side. The idea borrowed from the supernova plug-in. I suspect I */
|
|||
|
/* could've thought of it myself, but the truth must be told. */
|
|||
|
/* Plagiarism stinks! */
|
|||
|
if (width>height) {
|
|||
|
RW=LongerSize;
|
|||
|
RH=(float) height * (float) LongerSize/ (float) width;
|
|||
|
}
|
|||
|
else {
|
|||
|
RH=LongerSize;
|
|||
|
RW=(float)width * (float) LongerSize/ (float) height;
|
|||
|
}
|
|||
|
|
|||
|
/* The intire image is stretched into a string! */
|
|||
|
tempRGB = (guchar *) malloc(RW*RH*bytes);
|
|||
|
tempmask = (guchar *) malloc(RW*RH);
|
|||
|
|
|||
|
gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
|
|||
|
gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
|
|||
|
|
|||
|
/* Grab enough to save a row of image and a row of mask. */
|
|||
|
src_row = (guchar *) malloc (width*bytes);
|
|||
|
src_mask_row = (guchar *) malloc (width);
|
|||
|
|
|||
|
for (i=0; i < RH; i++) {
|
|||
|
whichrow=(float)i*(float)height/(float)RH;
|
|||
|
gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width);
|
|||
|
gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
|
|||
|
|
|||
|
for (j=0; j < RW; j++) {
|
|||
|
whichcol=(float)j*(float)width/(float)RW;
|
|||
|
|
|||
|
/* No selection made = each point is completely selected! */
|
|||
|
if (NoSelectionMade)
|
|||
|
tempmask[i*RW+j]=255;
|
|||
|
else
|
|||
|
tempmask[i*RW+j]=src_mask_row[whichcol];
|
|||
|
|
|||
|
/* Add the row to the one long string which now contains the image! */
|
|||
|
tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0];
|
|||
|
tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1];
|
|||
|
tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2];
|
|||
|
|
|||
|
/* Hold on to the alpha as well */
|
|||
|
if (bytes==4)
|
|||
|
tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3];
|
|||
|
}
|
|||
|
}
|
|||
|
temp->bpp=bytes;
|
|||
|
temp->width=RW;
|
|||
|
temp->height=RH;
|
|||
|
temp->rgb=tempRGB;
|
|||
|
temp->mask=tempmask;
|
|||
|
return temp;
|
|||
|
}
|
|||
|
|
|||
|
The following is a preview function which used the same ReducedImage type!
|
|||
|
Note that it uses fakes transparancy (if one is present by means of
|
|||
|
fake_transparancy which is defined as follows:
|
|||
|
|
|||
|
gint fake_transparency(gint i, gint j)
|
|||
|
{
|
|||
|
if ( ((i%20)- 10) * ((j%20)- 10)>0 )
|
|||
|
return 64;
|
|||
|
else
|
|||
|
return 196;
|
|||
|
}
|
|||
|
|
|||
|
Now here's the preview function:
|
|||
|
|
|||
|
void
|
|||
|
my_preview_render_function(GtkWidget *preview,
|
|||
|
gint changewhat,
|
|||
|
gint changewhich)
|
|||
|
{
|
|||
|
gint Inten, bytes=drawable->bpp;
|
|||
|
gint i, j, k;
|
|||
|
float partial;
|
|||
|
gint RW=reduced->width;
|
|||
|
gint RH=reduced->height;
|
|||
|
guchar *row=malloc(bytes*RW);;
|
|||
|
|
|||
|
|
|||
|
for (i=0; i < RH; i++) {
|
|||
|
for (j=0; j < RW; j++) {
|
|||
|
|
|||
|
row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0];
|
|||
|
row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1];
|
|||
|
row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2];
|
|||
|
|
|||
|
if (bytes==4)
|
|||
|
for (k=0; k<3; k++) {
|
|||
|
float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0;
|
|||
|
row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j);
|
|||
|
}
|
|||
|
}
|
|||
|
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW);
|
|||
|
}
|
|||
|
|
|||
|
free(a);
|
|||
|
gtk_widget_draw(preview,NULL);
|
|||
|
gdk_flush();
|
|||
|
}
|
|||
|
|
|||
|
Applicable Routines
|
|||
|
|
|||
|
guint gtk_preview_get_type (void);
|
|||
|
/* No idea */
|
|||
|
void gtk_preview_uninit (void);
|
|||
|
/* No idea */
|
|||
|
GtkWidget* gtk_preview_new (GtkPreviewType type);
|
|||
|
/* Described above */
|
|||
|
void gtk_preview_size (GtkPreview *preview,
|
|||
|
gint width,
|
|||
|
gint height);
|
|||
|
/* Allows you to resize an existing preview. */
|
|||
|
/* Apparantly there's a bug in GTK which makes */
|
|||
|
/* this process messy. A way to clean up a mess */
|
|||
|
/* is to manually resize the window containing */
|
|||
|
/* the preview after resizing the preview. */
|
|||
|
|
|||
|
void gtk_preview_put (GtkPreview *preview,
|
|||
|
GdkWindow *window,
|
|||
|
GdkGC *gc,
|
|||
|
gint srcx,
|
|||
|
gint srcy,
|
|||
|
gint destx,
|
|||
|
gint desty,
|
|||
|
gint width,
|
|||
|
gint height);
|
|||
|
/* No idea */
|
|||
|
|
|||
|
void gtk_preview_put_row (GtkPreview *preview,
|
|||
|
guchar *src,
|
|||
|
guchar *dest,
|
|||
|
gint x,
|
|||
|
gint y,
|
|||
|
gint w);
|
|||
|
/* No idea */
|
|||
|
|
|||
|
void gtk_preview_draw_row (GtkPreview *preview,
|
|||
|
guchar *data,
|
|||
|
gint x,
|
|||
|
gint y,
|
|||
|
gint w);
|
|||
|
/* Described in the text */
|
|||
|
|
|||
|
void gtk_preview_set_expand (GtkPreview *preview,
|
|||
|
gint expand);
|
|||
|
/* No idea */
|
|||
|
|
|||
|
/* No clue for any of the below but */
|
|||
|
/* should be standard for most widgets */
|
|||
|
void gtk_preview_set_gamma (double gamma);
|
|||
|
void gtk_preview_set_color_cube (guint nred_shades,
|
|||
|
guint ngreen_shades,
|
|||
|
guint nblue_shades,
|
|||
|
guint ngray_shades);
|
|||
|
void gtk_preview_set_install_cmap (gint install_cmap);
|
|||
|
void gtk_preview_set_reserved (gint nreserved);
|
|||
|
GdkVisual* gtk_preview_get_visual (void);
|
|||
|
GdkColormap* gtk_preview_get_cmap (void);
|
|||
|
GtkPreviewInfo* gtk_preview_get_info (void);
|
|||
|
|
|||
|
That's all, folks!
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Curves
|
|||
|
<p>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>The EventBox Widget<label id="sec_The_EventBox_Widget">
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
Some gtk widgets don't have associated X windows, so they just draw on
|
|||
|
thier parents. Because of this, they cannot recieve events
|
|||
|
and if they are incorrectly sized, they don't clip so you can get
|
|||
|
messy overwritting etc. If you require more from these widgets, the
|
|||
|
EventBox is for you.
|
|||
|
|
|||
|
At first glance, the EventBox widget might appear to be totally
|
|||
|
useless. It draws nothing on the screen and responds to no
|
|||
|
events. However, it does serve a function - it provides an X window for
|
|||
|
its child widget. This is important as many GTK widgets do not
|
|||
|
have an associated X window. Not having an X window saves memory and
|
|||
|
improves performance, but also has some drawbacks. A widget without an
|
|||
|
X window cannot receive events, and does not perform any clipping on
|
|||
|
it's contents. Although the name ``EventBox'' emphasizes the
|
|||
|
event-handling function, the widget also can be used for clipping.
|
|||
|
(And more ... see the example below.)
|
|||
|
|
|||
|
<p>
|
|||
|
To create a new EventBox widget, use:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_event_box_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
A child widget can then be added to this EventBox:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_container_add (GTK_CONTAINER(event_box), widget);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
The following example demonstrates both uses of an EventBox - a label
|
|||
|
is created that clipped to a small box, and set up so that a
|
|||
|
mouse-click on the label causes the program to exit.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* eventbox.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
int
|
|||
|
main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *event_box;
|
|||
|
GtkWidget *label;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* Create an EventBox and add it to our toplevel window */
|
|||
|
|
|||
|
event_box = gtk_event_box_new ();
|
|||
|
gtk_container_add (GTK_CONTAINER(window), event_box);
|
|||
|
gtk_widget_show (event_box);
|
|||
|
|
|||
|
/* Create a long label */
|
|||
|
|
|||
|
label = gtk_label_new ("Click here to quit, quit, quit, quit, quit");
|
|||
|
gtk_container_add (GTK_CONTAINER (event_box), label);
|
|||
|
gtk_widget_show (label);
|
|||
|
|
|||
|
/* Clip it short. */
|
|||
|
gtk_widget_set_usize (label, 110, 20);
|
|||
|
|
|||
|
/* And bind an action to it */
|
|||
|
gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
|
|||
|
gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
/* Yet one more thing you need an X window for ... */
|
|||
|
|
|||
|
gtk_widget_realize (event_box);
|
|||
|
gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1));
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Setting Widget Attributes<label id="sec_setting_widget_attributes">
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
This describes the functions used to operate on widgets. These can be used
|
|||
|
to set style, padding, size etc.
|
|||
|
|
|||
|
(Maybe I should make a whole section on accelerators.)
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_widget_install_accelerator (GtkWidget *widget,
|
|||
|
GtkAcceleratorTable *table,
|
|||
|
gchar *signal_name,
|
|||
|
gchar key,
|
|||
|
guint8 modifiers);
|
|||
|
|
|||
|
void gtk_widget_remove_accelerator (GtkWidget *widget,
|
|||
|
GtkAcceleratorTable *table,
|
|||
|
gchar *signal_name);
|
|||
|
|
|||
|
void gtk_widget_activate (GtkWidget *widget);
|
|||
|
|
|||
|
void gtk_widget_set_name (GtkWidget *widget,
|
|||
|
gchar *name);
|
|||
|
gchar* gtk_widget_get_name (GtkWidget *widget);
|
|||
|
|
|||
|
void gtk_widget_set_sensitive (GtkWidget *widget,
|
|||
|
gint sensitive);
|
|||
|
|
|||
|
void gtk_widget_set_style (GtkWidget *widget,
|
|||
|
GtkStyle *style);
|
|||
|
|
|||
|
GtkStyle* gtk_widget_get_style (GtkWidget *widget);
|
|||
|
|
|||
|
GtkStyle* gtk_widget_get_default_style (void);
|
|||
|
|
|||
|
void gtk_widget_set_uposition (GtkWidget *widget,
|
|||
|
gint x,
|
|||
|
gint y);
|
|||
|
void gtk_widget_set_usize (GtkWidget *widget,
|
|||
|
gint width,
|
|||
|
gint height);
|
|||
|
|
|||
|
void gtk_widget_grab_focus (GtkWidget *widget);
|
|||
|
|
|||
|
void gtk_widget_show (GtkWidget *widget);
|
|||
|
|
|||
|
void gtk_widget_hide (GtkWidget *widget);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Timeouts, IO and Idle Functions<label id="sec_timeouts">
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Timeouts
|
|||
|
<p>
|
|||
|
You may be wondering how you make GTK do useful work when in gtk_main.
|
|||
|
Well, you have several options. Using the following functions you can
|
|||
|
create a timeout function that will be called every "interval" milliseconds.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_timeout_add (guint32 interval,
|
|||
|
GtkFunction function,
|
|||
|
gpointer data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The first argument is the number of milliseconds
|
|||
|
between calls to your function. The second argument is the function
|
|||
|
you wish to have called, and
|
|||
|
the third, the data passed to this callback function. The return value is
|
|||
|
an integer "tag" which may be used to stop the timeout by calling:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_timeout_remove (gint tag);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
You may also stop the timeout function by returning zero or FALSE from
|
|||
|
your callback function. Obviously this means if you want your function to
|
|||
|
continue to be called, it should return a non-zero value, ie TRUE.
|
|||
|
|
|||
|
The declaration of your callback should look something like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint timeout_callback (gpointer data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Monitoring IO
|
|||
|
<p>
|
|||
|
Another nifty feature of GTK, is the ability to have it check for data on a
|
|||
|
file descriptor for you (as returned by open(2) or socket(2)). This is
|
|||
|
especially useful for networking applications. The function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gdk_input_add (gint source,
|
|||
|
GdkInputCondition condition,
|
|||
|
GdkInputFunction function,
|
|||
|
gpointer data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Where the first argument is the file descriptor you wish to have watched,
|
|||
|
and the second specifies what you want GDK to look for. This may be one of:
|
|||
|
<p>
|
|||
|
GDK_INPUT_READ - Call your function when there is data ready for reading on
|
|||
|
your file descriptor.
|
|||
|
<p>
|
|||
|
GDK_INPUT_WRITE - Call your function when the file descriptor is ready for
|
|||
|
writing.
|
|||
|
<p>
|
|||
|
As I'm sure you've figured out already, the third argument is the function
|
|||
|
you wish to have called when the above conditions are satisfied, and the
|
|||
|
fourth is the data to pass to this function.
|
|||
|
<p>
|
|||
|
The return value is a tag that may be used to stop GDK from monitoring this
|
|||
|
file descriptor using the following function.
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
void gdk_input_remove (gint tag);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
The callback function should be declared:
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
void input_callback (gpointer data, gint source,
|
|||
|
GdkInputCondition condition);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Idle Functions
|
|||
|
<p>
|
|||
|
What if you have a function you want called when nothing else is
|
|||
|
happening ?
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_idle_add (GtkFunction function,
|
|||
|
gpointer data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This causes GTK to call the specified function whenever nothing else is
|
|||
|
happening.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_idle_remove (gint tag);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
I won't explain the meaning of the arguments as they follow very much like
|
|||
|
the ones above. The function pointed to by the first argument to
|
|||
|
gtk_idle_add will be called whenever the opportunity arises. As with the
|
|||
|
others, returning FALSE will stop the idle function from being called.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Managing Selections
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Overview
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
One type of interprocess communication supported by GTK is
|
|||
|
<em>selections</em>. A selection identifies a chunk of data, for
|
|||
|
instance, a portion of text, selected by the user in some fashion, for
|
|||
|
instance, by dragging with the mouse. Only one application on a
|
|||
|
display, (he <em>owner</em>_ can own a particular selection at one
|
|||
|
time, so when a selection is claimed by one application, the previous
|
|||
|
owner must indicate to the user that selection has been
|
|||
|
relinquished. Other applications can request the contents of a
|
|||
|
selection in different forms, called <em>targets</em>. There can be
|
|||
|
any number of selections, but most X applications only handle one, the
|
|||
|
<em>primary selection</em>.
|
|||
|
|
|||
|
<p>
|
|||
|
In most cases, it isn't necessary for a GTK application to deal with
|
|||
|
selections itself. The standard widgets, such as the Entry widget,
|
|||
|
already have the capability to claim the selection when appropriate
|
|||
|
(e.g., when the user drags over text), and to retrieve the contents of
|
|||
|
the selection owned by another widget, or another application (e.g.,
|
|||
|
when the user clicks the second mouse button). However, there may be
|
|||
|
cases in which you want to give other widgets the ability to supply
|
|||
|
the selection, or you wish to retrieve targets not supported by
|
|||
|
default.
|
|||
|
|
|||
|
<p>
|
|||
|
A fundamental concept needed to understand selection handling is that
|
|||
|
of the <em>atom</em>. An atom is an integer that uniquely identifies a
|
|||
|
string (on a certain display). Certain atoms are predefined by the X
|
|||
|
server, and in some cases there are constants in in <tt>gtk.h</tt>
|
|||
|
corresponding to these atoms. For instance the constant
|
|||
|
<tt>GDK_PRIMARY_SELECTION</tt> corresponds to the string "PRIMARY".
|
|||
|
In other cases, you should use the functions
|
|||
|
<tt>gdk_atom_intern()</tt>, to get the atom corresponding to a string,
|
|||
|
and <tt>gdk_atom_name()</tt>, to get the name of an atom. Both
|
|||
|
selections and targets are identifed by atoms.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Retrieving the selection
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
Retrieving the selection is an asynchronous process. To start the
|
|||
|
process, you call:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_selection_convert (GtkWidget *widget,
|
|||
|
GdkAtom selection,
|
|||
|
GdkAtom target,
|
|||
|
guint32 time)
|
|||
|
</verb</tscreen>
|
|||
|
|
|||
|
This <em>converts</em> the selection into the form specified by
|
|||
|
<tt/target/. If it all possible, the time field should be the time
|
|||
|
from the event that triggered the selection. This helps make sure that
|
|||
|
events occur in the order that the user requested them.
|
|||
|
However, if it is not available (for instance, if the conversion was
|
|||
|
triggered by a "clicked" signal), then you can use the constant
|
|||
|
<tt>GDK_CURRENT_TIME</tt>.
|
|||
|
|
|||
|
<p>
|
|||
|
When the selection owner responds to the request, a
|
|||
|
"selection_received" signal is sent to your application. The handler
|
|||
|
for this signal receives a pointer to a <tt>GtkSelectionData</tt>
|
|||
|
structure, which is defined as:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GtkSelectionData
|
|||
|
{
|
|||
|
GdkAtom selection;
|
|||
|
GdkAtom target;
|
|||
|
GdkAtom type;
|
|||
|
gint format;
|
|||
|
guchar *data;
|
|||
|
gint length;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tt>selection</tt> and <tt>target</tt> are the values you gave in your
|
|||
|
<tt>gtk_selection_convert()</tt> call. <tt>type</tt> is an atom that
|
|||
|
identifies the type of data returned by the selection owner. Some
|
|||
|
possible values are "STRING", a string of latin-1 characters, "ATOM",
|
|||
|
a series of atoms, "INTEGER", an integer, etc. Most targets can only
|
|||
|
return one type. <tt/format/ gives the length of the units (for
|
|||
|
instance characters) in bits. Usually, you don't care about this when
|
|||
|
receiving data. <tt>data</tt> is a pointer to the returned data, and
|
|||
|
<tt>length</tt> gives the length of the returned data, in bytes. If
|
|||
|
<tt>length</tt> is negative, then an error occurred and the selection
|
|||
|
could not be retrieved. This might happen if no application owned the
|
|||
|
selection, or if you requested a target that the application didn't
|
|||
|
support. The buffer is actually guaranteed to be one byte longer than
|
|||
|
<tt>length</tt>; the extra byte will always be zero, so it isn't
|
|||
|
necessary to make a copy of strings just to null terminate them.
|
|||
|
|
|||
|
<p>
|
|||
|
In the following example, we retrieve the special target "TARGETS",
|
|||
|
which is a list of all targets into which the selection can be
|
|||
|
converted.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* gettargets.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
|
|||
|
void selection_received (GtkWidget *widget,
|
|||
|
GtkSelectionData *selection_data,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
/* Signal handler invoked when user clicks on the "Get Targets" button */
|
|||
|
void
|
|||
|
get_targets (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
static GdkAtom targets_atom = GDK_NONE;
|
|||
|
|
|||
|
/* Get the atom corresonding to the string "TARGETS" */
|
|||
|
if (targets_atom == GDK_NONE)
|
|||
|
targets_atom = gdk_atom_intern ("TARGETS", FALSE);
|
|||
|
|
|||
|
/* And request the "TARGETS" target for the primary selection */
|
|||
|
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
|
|||
|
GDK_CURRENT_TIME);
|
|||
|
}
|
|||
|
|
|||
|
/* Signal handler called when the selections owner returns the data */
|
|||
|
void
|
|||
|
selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
|
|||
|
gpointer data)
|
|||
|
{
|
|||
|
GdkAtom *atoms;
|
|||
|
GList *item_list;
|
|||
|
int i;
|
|||
|
|
|||
|
/* **** IMPORTANT **** Check to see if retrieval succeeded */
|
|||
|
if (selection_data->length < 0)
|
|||
|
{
|
|||
|
g_print ("Selection retrieval failed\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
/* Make sure we got the data in the expected form */
|
|||
|
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
|
|||
|
{
|
|||
|
g_print ("Selection \"TARGETS\" was not returned as atoms!\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/* Print out the atoms we received */
|
|||
|
atoms = (GdkAtom *)selection_data->data;
|
|||
|
|
|||
|
item_list = NULL;
|
|||
|
for (i=0; i<selection_data->length/sizeof(GdkAtom); i++)
|
|||
|
{
|
|||
|
char *name;
|
|||
|
name = gdk_atom_name (atoms[i]);
|
|||
|
if (name != NULL)
|
|||
|
g_print ("%s\n",name);
|
|||
|
else
|
|||
|
g_print ("(bad atom)\n");
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *button;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* Create the toplevel window */
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
/* Create a button the user can click to get targets */
|
|||
|
|
|||
|
button = gtk_button_new_with_label ("Get Targets");
|
|||
|
gtk_container_add (GTK_CONTAINER (window), button);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT(button), "clicked",
|
|||
|
GTK_SIGNAL_FUNC (get_targets), NULL);
|
|||
|
gtk_signal_connect (GTK_OBJECT(button), "selection_received",
|
|||
|
GTK_SIGNAL_FUNC (selection_received), NULL);
|
|||
|
|
|||
|
gtk_widget_show (button);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Supplying the selection
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
Supplying the selection is a bit more complicated. You must register
|
|||
|
handlers that will be called when your selection is requested. For
|
|||
|
each selection/target pair you will handle, you make a call to:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_selection_add_handler (GtkWidget *widget,
|
|||
|
GdkAtom selection,
|
|||
|
GdkAtom target,
|
|||
|
GtkSelectionFunction function,
|
|||
|
GtkRemoveFunction remove_func,
|
|||
|
gpointer data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
|
|||
|
this handler will manage. <tt/remove_func/ if not
|
|||
|
NULL, will be called when the signal handler is removed. This is
|
|||
|
useful, for instance, for interpreted languages which need to
|
|||
|
keep track of a reference count for <tt/data/.
|
|||
|
|
|||
|
<p>
|
|||
|
The callback function has the signature:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
|
|||
|
GtkSelectionData *selection_data,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The GtkSelectionData is the same as above, but this time, we're
|
|||
|
responsible for filling in the fields <tt/type/, <tt/format/,
|
|||
|
<tt/data/, and <tt/length/. (The <tt/format/ field is actually
|
|||
|
important here - the X server uses it to figure out whether the data
|
|||
|
needs to be byte-swapped or not. Usually it will be 8 - <em/i.e./ a
|
|||
|
character - or 32 - <em/i.e./ a. integer.) This is done by calling the
|
|||
|
function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_selection_data_set (GtkSelectionData *selection_data,
|
|||
|
GdkAtom type,
|
|||
|
gint format,
|
|||
|
guchar *data,
|
|||
|
gint length);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This function takes care of properly making a copy of the data so that
|
|||
|
you don't have to worry about keeping it around. (You should not fill
|
|||
|
in the fields of the GtkSelectionData structure by hand.)
|
|||
|
|
|||
|
<p>
|
|||
|
When prompted by the user, you claim ownership of the selection by
|
|||
|
calling:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_selection_owner_set (GtkWidget *widget,
|
|||
|
GdkAtom selection,
|
|||
|
guint32 time);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
If another application claims ownership of the selection, you will
|
|||
|
receive a "selection_clear_event".
|
|||
|
|
|||
|
As an example of supplying the selection, the following program adds
|
|||
|
selection functionality to a toggle button. When the toggle button is
|
|||
|
depressed, the program claims the primary selection. The only target
|
|||
|
supported (aside from certain targets like "TARGETS" supplied by GTK
|
|||
|
itself), is the "STRING" target. When this target is requested, a
|
|||
|
string representation of the time is returned.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* setselection.c */
|
|||
|
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include <time.h>
|
|||
|
|
|||
|
/* Callback when the user toggles the selection */
|
|||
|
void
|
|||
|
selection_toggled (GtkWidget *widget, gint *have_selection)
|
|||
|
{
|
|||
|
if (GTK_TOGGLE_BUTTON(widget)->active)
|
|||
|
{
|
|||
|
*have_selection = gtk_selection_owner_set (widget,
|
|||
|
GDK_SELECTION_PRIMARY,
|
|||
|
GDK_CURRENT_TIME);
|
|||
|
/* if claiming the selection failed, we return the button to
|
|||
|
the out state */
|
|||
|
if (!*have_selection)
|
|||
|
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (*have_selection)
|
|||
|
{
|
|||
|
/* Before clearing the selection by setting the owner to NULL,
|
|||
|
we check if we are the actual owner */
|
|||
|
if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window)
|
|||
|
gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
|
|||
|
GDK_CURRENT_TIME);
|
|||
|
*have_selection = FALSE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* Called when another application claims the selection */
|
|||
|
gint
|
|||
|
selection_clear (GtkWidget *widget, GdkEventSelection *event,
|
|||
|
gint *have_selection)
|
|||
|
{
|
|||
|
*have_selection = FALSE;
|
|||
|
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE);
|
|||
|
|
|||
|
return TRUE;
|
|||
|
}
|
|||
|
|
|||
|
/* Supplies the current time as the selection. */
|
|||
|
void
|
|||
|
selection_handle (GtkWidget *widget,
|
|||
|
GtkSelectionData *selection_data,
|
|||
|
gpointer data)
|
|||
|
{
|
|||
|
gchar *timestr;
|
|||
|
time_t current_time;
|
|||
|
|
|||
|
current_time = time (NULL);
|
|||
|
timestr = asctime (localtime(&current_time));
|
|||
|
/* When we return a single string, it should not be null terminated.
|
|||
|
That will be done for us */
|
|||
|
|
|||
|
gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING,
|
|||
|
8, timestr, strlen(timestr));
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
|
|||
|
GtkWidget *selection_button;
|
|||
|
|
|||
|
static int have_selection = FALSE;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
/* Create the toplevel window */
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
/* Create a toggle button to act as the selection */
|
|||
|
|
|||
|
selection_button = gtk_toggle_button_new_with_label ("Claim Selection");
|
|||
|
gtk_container_add (GTK_CONTAINER (window), selection_button);
|
|||
|
gtk_widget_show (selection_button);
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT(selection_button), "toggled",
|
|||
|
GTK_SIGNAL_FUNC (selection_toggled), &have_selection);
|
|||
|
gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
|
|||
|
GTK_SIGNAL_FUNC (selection_clear), &have_selection);
|
|||
|
|
|||
|
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
|
|||
|
GDK_SELECTION_TYPE_STRING,
|
|||
|
selection_handle, NULL);
|
|||
|
|
|||
|
gtk_widget_show (selection_button);
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>glib<label id="sec_glib">
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
glib provides many useful functions and definitions available for use
|
|||
|
when creating GDK
|
|||
|
and GTK applications. I will list them all here with a brief explanation.
|
|||
|
Many are duplicates of standard libc functions so I won't go into
|
|||
|
detail on those. This is mostly to be used as a reference, so you know what is
|
|||
|
available for use.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Definitions
|
|||
|
<p>
|
|||
|
Definitions for the extremes of many of the standard types are:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
G_MINFLOAT
|
|||
|
G_MAXFLOAT
|
|||
|
G_MINDOUBLE
|
|||
|
G_MAXDOUBLE
|
|||
|
G_MINSHORT
|
|||
|
G_MAXSHORT
|
|||
|
G_MININT
|
|||
|
G_MAXINT
|
|||
|
G_MINLONG
|
|||
|
G_MAXLONG
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Also, the following typedefs. The ones left unspecified are dynamically set
|
|||
|
depending on the architecture. Remember to avoid counting on the size of a
|
|||
|
pointer if you want to be portable! Eg, a pointer on an Alpha is 8 bytes, but 4
|
|||
|
on Intel.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
char gchar;
|
|||
|
short gshort;
|
|||
|
long glong;
|
|||
|
int gint;
|
|||
|
char gboolean;
|
|||
|
|
|||
|
unsigned char guchar;
|
|||
|
unsigned short gushort;
|
|||
|
unsigned long gulong;
|
|||
|
unsigned int guint;
|
|||
|
|
|||
|
float gfloat;
|
|||
|
double gdouble;
|
|||
|
long double gldouble;
|
|||
|
|
|||
|
void* gpointer;
|
|||
|
|
|||
|
gint8
|
|||
|
guint8
|
|||
|
gint16
|
|||
|
guint16
|
|||
|
gint32
|
|||
|
guint32
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Doubly Linked Lists
|
|||
|
<p>
|
|||
|
The following functions are used to create, manage, and destroy doubly
|
|||
|
linked lists. I assume you know what linked lists are, as it is beyond the scope
|
|||
|
of this document to explain them. Of course, it's not required that you
|
|||
|
know these for general use of GTK, but they are nice to know.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GList* g_list_alloc (void);
|
|||
|
|
|||
|
void g_list_free (GList *list);
|
|||
|
|
|||
|
void g_list_free_1 (GList *list);
|
|||
|
|
|||
|
GList* g_list_append (GList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GList* g_list_prepend (GList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GList* g_list_insert (GList *list,
|
|||
|
gpointer data,
|
|||
|
gint position);
|
|||
|
|
|||
|
GList* g_list_remove (GList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GList* g_list_remove_link (GList *list,
|
|||
|
GList *link);
|
|||
|
|
|||
|
GList* g_list_reverse (GList *list);
|
|||
|
|
|||
|
GList* g_list_nth (GList *list,
|
|||
|
gint n);
|
|||
|
|
|||
|
GList* g_list_find (GList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GList* g_list_last (GList *list);
|
|||
|
|
|||
|
GList* g_list_first (GList *list);
|
|||
|
|
|||
|
gint g_list_length (GList *list);
|
|||
|
|
|||
|
void g_list_foreach (GList *list,
|
|||
|
GFunc func,
|
|||
|
gpointer user_data);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Singly Linked Lists
|
|||
|
<p>
|
|||
|
Many of the above functions for singly linked lists are identical to the
|
|||
|
above. Here is a complete list:
|
|||
|
<tscreen><verb>
|
|||
|
GSList* g_slist_alloc (void);
|
|||
|
|
|||
|
void g_slist_free (GSList *list);
|
|||
|
|
|||
|
void g_slist_free_1 (GSList *list);
|
|||
|
|
|||
|
GSList* g_slist_append (GSList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GSList* g_slist_prepend (GSList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GSList* g_slist_insert (GSList *list,
|
|||
|
gpointer data,
|
|||
|
gint position);
|
|||
|
|
|||
|
GSList* g_slist_remove (GSList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GSList* g_slist_remove_link (GSList *list,
|
|||
|
GSList *link);
|
|||
|
|
|||
|
GSList* g_slist_reverse (GSList *list);
|
|||
|
|
|||
|
GSList* g_slist_nth (GSList *list,
|
|||
|
gint n);
|
|||
|
|
|||
|
GSList* g_slist_find (GSList *list,
|
|||
|
gpointer data);
|
|||
|
|
|||
|
GSList* g_slist_last (GSList *list);
|
|||
|
|
|||
|
gint g_slist_length (GSList *list);
|
|||
|
|
|||
|
void g_slist_foreach (GSList *list,
|
|||
|
GFunc func,
|
|||
|
gpointer user_data);
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Memory Management
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
gpointer g_malloc (gulong size);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This is a replacement for malloc(). You do not need to check the return
|
|||
|
vaule as it is done for you in this function.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gpointer g_malloc0 (gulong size);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Same as above, but zeroes the memory before returning a pointer to it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gpointer g_realloc (gpointer mem,
|
|||
|
gulong size);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Relocates "size" bytes of memory starting at "mem". Obviously, the memory should have been
|
|||
|
previously allocated.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_free (gpointer mem);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Frees memory. Easy one.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_mem_profile (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Dumps a profile of used memory, but requries that you add #define
|
|||
|
MEM_PROFILE to the top of glib/gmem.c and re-make and make install.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_mem_check (gpointer mem);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Checks that a memory location is valid. Requires you add #define
|
|||
|
MEM_CHECK to the top of gmem.c and re-make and make install.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Timers
|
|||
|
<p>
|
|||
|
Timer functions..
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GTimer* g_timer_new (void);
|
|||
|
|
|||
|
void g_timer_destroy (GTimer *timer);
|
|||
|
|
|||
|
void g_timer_start (GTimer *timer);
|
|||
|
|
|||
|
void g_timer_stop (GTimer *timer);
|
|||
|
|
|||
|
void g_timer_reset (GTimer *timer);
|
|||
|
|
|||
|
gdouble g_timer_elapsed (GTimer *timer,
|
|||
|
gulong *microseconds);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>String Handling
|
|||
|
<p>
|
|||
|
A whole mess of string handling functions. They all look very interesting, and
|
|||
|
probably better for many purposes than the standard C string functions, but
|
|||
|
require documentation.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GString* g_string_new (gchar *init);
|
|||
|
void g_string_free (GString *string,
|
|||
|
gint free_segment);
|
|||
|
|
|||
|
GString* g_string_assign (GString *lval,
|
|||
|
gchar *rval);
|
|||
|
|
|||
|
GString* g_string_truncate (GString *string,
|
|||
|
gint len);
|
|||
|
|
|||
|
GString* g_string_append (GString *string,
|
|||
|
gchar *val);
|
|||
|
|
|||
|
GString* g_string_append_c (GString *string,
|
|||
|
gchar c);
|
|||
|
|
|||
|
GString* g_string_prepend (GString *string,
|
|||
|
gchar *val);
|
|||
|
|
|||
|
GString* g_string_prepend_c (GString *string,
|
|||
|
gchar c);
|
|||
|
|
|||
|
void g_string_sprintf (GString *string,
|
|||
|
gchar *fmt,
|
|||
|
...);
|
|||
|
|
|||
|
void g_string_sprintfa (GString *string,
|
|||
|
gchar *fmt,
|
|||
|
...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Utility and Error Functions
|
|||
|
<p>
|
|||
|
<tscreen><verb>
|
|||
|
gchar* g_strdup (const gchar *str);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Replacement strdup function. Copies the
|
|||
|
original strings contents to newly allocated memory, and returns a pointer to it.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gchar* g_strerror (gint errnum);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
I recommend using this for all error messages. It's much nicer, and more
|
|||
|
portable than perror() or others. The output is usually of the form:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
program name:function that failed:file or further description:strerror
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Here's an example of one such call used in our hello_world program:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
g_print("hello_world:open:%s:%s\n", filename, g_strerror(errno));
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_error (gchar *format, ...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Prints an error message. The format is just like printf, but it
|
|||
|
prepends "** ERROR **: " to your message, and exits the program.
|
|||
|
Use only for fatal errors.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_warning (gchar *format, ...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Same as above, but prepends "** WARNING **: ", and does not exit the
|
|||
|
program.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_message (gchar *format, ...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Prints "message: " prepended to the string you pass in.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void g_print (gchar *format, ...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Replacement for printf().
|
|||
|
|
|||
|
And our last function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gchar* g_strsignal (gint signum);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Prints out the name of the Unix system signal given the signal number.
|
|||
|
Useful in generic signal handling functions.
|
|||
|
|
|||
|
All of the above are more or less just stolen from glib.h. If anyone cares
|
|||
|
to document any function, just send me an email!
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>GTK's rc Files
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
GTK has it's own way of dealing with application defaults, by using rc
|
|||
|
files. These can be used to set the colors of just about any widget, and
|
|||
|
can also be used to tile pixmaps onto the background of some widgets.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Functions For rc Files
|
|||
|
<p>
|
|||
|
When your application starts, you should include a call to:
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_rc_parse (char *filename);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Passing in the filename of your rc file. This will cause GTK to parse this
|
|||
|
file, and use the style settings for the widget types defined there.
|
|||
|
<p>
|
|||
|
If you wish to have a special set of widgets that can take on a different
|
|||
|
style from others, or any other logical division of widgets, use a call to:
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_widget_set_name (GtkWidget *widget,
|
|||
|
gchar *name);
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Passing your newly created widget as the first argument, and the name
|
|||
|
you wish to give it as the second. This will allow you to change the
|
|||
|
attributes of this widget by name through the rc file.
|
|||
|
<p>
|
|||
|
If we use a call something like this:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
button = gtk_button_new_with_label ("Special Button");
|
|||
|
gtk_widget_set_name (button, "special button");
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Then this button is given the name "special button" and may be addressed by
|
|||
|
name in the rc file as "special button.GtkButton". [<--- Verify ME!]
|
|||
|
<p>
|
|||
|
The example rc file below, sets the properties of the main window, and lets
|
|||
|
all children of that main window inherit the style described by the "main
|
|||
|
button" style. The code used in the application is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
gtk_widget_set_name (window, "main window");
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
And then the style is defined in the rc file using:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
widget "main window.*GtkButton*" style "main_button"
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Which sets all the GtkButton widgets in the "main window" to the
|
|||
|
"main_buttons" style as defined in the rc file.
|
|||
|
<p>
|
|||
|
As you can see, this is a fairly powerful and flexible system. Use your
|
|||
|
imagination as to how best to take advantage of this.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>GTK's rc File Format
|
|||
|
<p>
|
|||
|
The format of the GTK file is illustrated in the example below. This is
|
|||
|
the testgtkrc file from the GTK distribution, but I've added a
|
|||
|
few comments and things. You may wish to include this explanation
|
|||
|
your application to allow the user to fine tune his application.
|
|||
|
<p>
|
|||
|
There are several directives to change the attributes of a widget.
|
|||
|
<itemize>
|
|||
|
<item>fg - Sets the foreground color of a widget.
|
|||
|
<item>bg - Sets the background color of a widget.
|
|||
|
<item>bg_pixmap - Sets the background of a widget to a tiled pixmap.
|
|||
|
<item>font - Sets the font to be used with the given widget.
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
In addition to this, there are several states a widget can be in, and you
|
|||
|
can set different colors, pixmaps and fonts for each state. These states are:
|
|||
|
<itemize>
|
|||
|
<item>NORMAL - The normal state of a widget, without the mouse over top of
|
|||
|
it, and not being pressed etc.
|
|||
|
<item>PRELIGHT - When the mouse is over top of the widget, colors defined
|
|||
|
using this state will be in effect.
|
|||
|
<item>ACTIVE - When the widget is pressed or clicked it will be active, and
|
|||
|
the attributes assigned by this tag will be in effect.
|
|||
|
<item>INSENSITIVE - When a widget is set insensitive, and cannot be
|
|||
|
activated, it will take these attributes.
|
|||
|
<item>SELECTED - When an object is selected, it takes these attributes.
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
When using the "fg" and "bg" keywords to set the colors of widgets, the
|
|||
|
format is:
|
|||
|
<tscreen><verb>
|
|||
|
fg[<STATE>] = { Red, Green, Blue }
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where STATE is one of the above states (PRELIGHT, ACTIVE etc), and the Red,
|
|||
|
Green and Blue are values in the range of 0 - 1.0, { 1.0, 1.0, 1.0 } being
|
|||
|
white.
|
|||
|
They must be in float form, or they will register as 0, so a straight
|
|||
|
"1" will not work, it must
|
|||
|
be "1.0". A straight "0" is fine because it doesn't matter if it's not
|
|||
|
recognized. Unrecognized values are set to 0.
|
|||
|
<p>
|
|||
|
bg_pixmap is very similar to the above, except the colors are replaced by a
|
|||
|
filename.
|
|||
|
|
|||
|
pixmap_path is a list of paths seperated by ":"'s. These paths will be
|
|||
|
searched for any pixmap you specify.
|
|||
|
|
|||
|
<p>
|
|||
|
The font directive is simply:
|
|||
|
<tscreen><verb>
|
|||
|
font = "<font name>"
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
Where the only hard part is figuring out the font string. Using xfontsel or
|
|||
|
similar utility should help.
|
|||
|
<p>
|
|||
|
The "widget_class" sets the style of a class of widgets. These classes are
|
|||
|
listed in the widget overview on the class hierarchy.
|
|||
|
<p>
|
|||
|
The "widget" directive sets a specificaly named set of widgets to a
|
|||
|
given style, overriding any style set for the given widget class.
|
|||
|
These widgets are registered inside the application using the
|
|||
|
gtk_widget_set_name() call. This allows you to specify the attributes of a
|
|||
|
widget on a per widget basis, rather than setting the attributes of an
|
|||
|
entire widget class. I urge you to document any of these special widgets so
|
|||
|
users may customize them.
|
|||
|
<p>
|
|||
|
When the keyword "<tt>parent</>" is used as an attribute, the widget will take on
|
|||
|
the attributes of it's parent in the application.
|
|||
|
<p>
|
|||
|
When defining a style, you may assign the attributes of a previously defined
|
|||
|
style to this new one.
|
|||
|
<tscreen><verb>
|
|||
|
style "main_button" = "button"
|
|||
|
{
|
|||
|
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
|
|||
|
bg[PRELIGHT] = { 0.75, 0, 0 }
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
<p>
|
|||
|
This example takes the "button" style, and creates a new "main_button" style
|
|||
|
simply by changing the font and prelight background color of the "button"
|
|||
|
style.
|
|||
|
<p>
|
|||
|
Of course, many of these attributes don't apply to all widgets. It's a
|
|||
|
simple matter of common sense really. Anything that could apply, should.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1>Example rc file
|
|||
|
<p>
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
|
|||
|
#
|
|||
|
pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
|
|||
|
#
|
|||
|
# style <name> [= <name>]
|
|||
|
# {
|
|||
|
# <option>
|
|||
|
# }
|
|||
|
#
|
|||
|
# widget <widget_set> style <style_name>
|
|||
|
# widget_class <widget_class_set> style <style_name>
|
|||
|
|
|||
|
|
|||
|
# Here is a list of all the possible states. Note that some do not apply to
|
|||
|
# certain widgets.
|
|||
|
#
|
|||
|
# NORMAL - The normal state of a widget, without the mouse over top of
|
|||
|
# it, and not being pressed etc.
|
|||
|
#
|
|||
|
# PRELIGHT - When the mouse is over top of the widget, colors defined
|
|||
|
# using this state will be in effect.
|
|||
|
#
|
|||
|
# ACTIVE - When the widget is pressed or clicked it will be active, and
|
|||
|
# the attributes assigned by this tag will be in effect.
|
|||
|
#
|
|||
|
# INSENSITIVE - When a widget is set insensitive, and cannot be
|
|||
|
# activated, it will take these attributes.
|
|||
|
#
|
|||
|
# SELECTED - When an object is selected, it takes these attributes.
|
|||
|
#
|
|||
|
# Given these states, we can set the attributes of the widgets in each of
|
|||
|
# these states using the following directives.
|
|||
|
#
|
|||
|
# fg - Sets the foreground color of a widget.
|
|||
|
# fg - Sets the background color of a widget.
|
|||
|
# bg_pixmap - Sets the background of a widget to a tiled pixmap.
|
|||
|
# font - Sets the font to be used with the given widget.
|
|||
|
#
|
|||
|
|
|||
|
# This sets a style called "button". The name is not really important, as
|
|||
|
# it is assigned to the actual widgets at the bottom of the file.
|
|||
|
|
|||
|
style "window"
|
|||
|
{
|
|||
|
#This sets the padding around the window to the pixmap specified.
|
|||
|
#bg_pixmap[<STATE>] = "<pixmap filename>"
|
|||
|
bg_pixmap[NORMAL] = "warning.xpm"
|
|||
|
}
|
|||
|
|
|||
|
style "scale"
|
|||
|
{
|
|||
|
#Sets the foreground color (font color) to red when in the "NORMAL"
|
|||
|
#state.
|
|||
|
|
|||
|
fg[NORMAL] = { 1.0, 0, 0 }
|
|||
|
|
|||
|
#Sets the background pixmap of this widget to that of it's parent.
|
|||
|
bg_pixmap[NORMAL] = "<parent>"
|
|||
|
}
|
|||
|
|
|||
|
style "button"
|
|||
|
{
|
|||
|
# This shows all the possible states for a button. The only one that
|
|||
|
# doesn't apply is the SELECTED state.
|
|||
|
|
|||
|
fg[PRELIGHT] = { 0, 1.0, 1.0 }
|
|||
|
bg[PRELIGHT] = { 0, 0, 1.0 }
|
|||
|
bg[ACTIVE] = { 1.0, 0, 0 }
|
|||
|
fg[ACTIVE] = { 0, 1.0, 0 }
|
|||
|
bg[NORMAL] = { 1.0, 1.0, 0 }
|
|||
|
fg[NORMAL] = { .99, 0, .99 }
|
|||
|
bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
|
|||
|
fg[INSENSITIVE] = { 1.0, 0, 1.0 }
|
|||
|
}
|
|||
|
|
|||
|
# In this example, we inherit the attributes of the "button" style and then
|
|||
|
# override the font and background color when prelit to create a new
|
|||
|
# "main_button" style.
|
|||
|
|
|||
|
style "main_button" = "button"
|
|||
|
{
|
|||
|
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
|
|||
|
bg[PRELIGHT] = { 0.75, 0, 0 }
|
|||
|
}
|
|||
|
|
|||
|
style "toggle_button" = "button"
|
|||
|
{
|
|||
|
fg[NORMAL] = { 1.0, 0, 0 }
|
|||
|
fg[ACTIVE] = { 1.0, 0, 0 }
|
|||
|
|
|||
|
# This sets the background pixmap of the toggle_button to that of it's
|
|||
|
# parent widget (as defined in the application).
|
|||
|
bg_pixmap[NORMAL] = "<parent>"
|
|||
|
}
|
|||
|
|
|||
|
style "text"
|
|||
|
{
|
|||
|
bg_pixmap[NORMAL] = "marble.xpm"
|
|||
|
fg[NORMAL] = { 1.0, 1.0, 1.0 }
|
|||
|
}
|
|||
|
|
|||
|
style "ruler"
|
|||
|
{
|
|||
|
font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
|
|||
|
}
|
|||
|
|
|||
|
# pixmap_path "~/.pixmaps"
|
|||
|
|
|||
|
# These set the widget types to use the styles defined above.
|
|||
|
# The widget types are listed in the class hierarchy, but could probably be
|
|||
|
# just listed in this document for the users reference.
|
|||
|
|
|||
|
widget_class "GtkWindow" style "window"
|
|||
|
widget_class "GtkDialog" style "window"
|
|||
|
widget_class "GtkFileSelection" style "window"
|
|||
|
widget_class "*Gtk*Scale" style "scale"
|
|||
|
widget_class "*GtkCheckButton*" style "toggle_button"
|
|||
|
widget_class "*GtkRadioButton*" style "toggle_button"
|
|||
|
widget_class "*GtkButton*" style "button"
|
|||
|
widget_class "*Ruler" style "ruler"
|
|||
|
widget_class "*GtkText" style "text"
|
|||
|
|
|||
|
# This sets all the buttons that are children of the "main window" to
|
|||
|
# the main_buton style. These must be documented to be taken advantage of.
|
|||
|
widget "main window.*GtkButton*" style "main_button"
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Writing Your Own Widgets
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Overview
|
|||
|
<p>
|
|||
|
Although the GTK distribution comes with many types of widgets that
|
|||
|
should cover most basic needs, there may come a time when you need to
|
|||
|
create your own new widget type. Since GTK uses widget inheretence
|
|||
|
extensively, and there is already a widget that
|
|||
|
is close to what you want, it is often possible to make a useful new widget type in
|
|||
|
just a few lines of code. But before starting work on a new widget, check
|
|||
|
around first to make sure that someone has not already written
|
|||
|
it. This will prevent duplication of effort and keep the number of
|
|||
|
GTK widgets out there to a minimum, which will help keep both the code
|
|||
|
and the interface of different applications consistent. As a flip side
|
|||
|
to this, once you finish your widget, announce it to the world so
|
|||
|
other people can benefit. The best place to do this is probably the
|
|||
|
<tt>gtk-list</tt>.
|
|||
|
|
|||
|
Complete sources for the example widgets are available at the place you
|
|||
|
got this tutorial, or from:
|
|||
|
|
|||
|
<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"
|
|||
|
name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial">
|
|||
|
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> The Anatomy Of A Widget
|
|||
|
|
|||
|
<p>
|
|||
|
In order to create a new widget, it is important to have an
|
|||
|
understanding of how GTK objects work. This section is just meant as a
|
|||
|
brief overview. See the reference documentation for the details.
|
|||
|
|
|||
|
<p>
|
|||
|
GTK widgets are implemented in an object oriented fashion. However,
|
|||
|
they are implemented in standard C. This greatly improves portability
|
|||
|
and stability over using current generation C++ compilers; however,
|
|||
|
it does mean that the widget writer has to pay attention to some of
|
|||
|
the implementation details. The information common to all instances of
|
|||
|
one class of widgets (e.g., to all Button widgets) is stored in the
|
|||
|
<em>class structure</em>. There is only one copy of this in
|
|||
|
which is stored information about the class's signals
|
|||
|
(which act like virtual functions in C). To support inheritance, the
|
|||
|
first field in the class structure must be a copy of the parent's
|
|||
|
class structure. The declaration of the class structure of GtkButtton
|
|||
|
looks like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GtkButtonClass
|
|||
|
{
|
|||
|
GtkContainerClass parent_class;
|
|||
|
|
|||
|
void (* pressed) (GtkButton *button);
|
|||
|
void (* released) (GtkButton *button);
|
|||
|
void (* clicked) (GtkButton *button);
|
|||
|
void (* enter) (GtkButton *button);
|
|||
|
void (* leave) (GtkButton *button);
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
When a button is treated as a container (for instance, when it is
|
|||
|
resized), its class structure can be casted to GtkContainerClass, and
|
|||
|
the relevant fields used to handle the signals.
|
|||
|
|
|||
|
<p>
|
|||
|
There is also a structure for each widget that is created on a
|
|||
|
per-instance basis. This structure has fields to store information that
|
|||
|
is different for each instance of the widget. We'll call this
|
|||
|
structure the <em>object structure</em>. For the Button class, it looks
|
|||
|
like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GtkButton
|
|||
|
{
|
|||
|
GtkContainer container;
|
|||
|
|
|||
|
GtkWidget *child;
|
|||
|
|
|||
|
guint in_button : 1;
|
|||
|
guint button_down : 1;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
Note that, similar to the class structure, the first field is the
|
|||
|
object structure of the parent class, so that this structure can be
|
|||
|
casted to the parent class's object structure as needed.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Creating a Composite widget
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Introduction
|
|||
|
|
|||
|
<p>
|
|||
|
One type of widget that you may be interested in creating is a
|
|||
|
widget that is merely an aggregate of other GTK widgets. This type of
|
|||
|
widget does nothing that couldn't be done without creating new
|
|||
|
widgets, but provides a convenient way of packaging user interface
|
|||
|
elements for reuse. The FileSelection and ColorSelection widgets in
|
|||
|
the standard distribution are examples of this type of widget.
|
|||
|
|
|||
|
<p>
|
|||
|
The example widget that we'll create in this section is the Tictactoe
|
|||
|
widget, a 3x3 array of toggle buttons which triggers a signal when all
|
|||
|
three buttons in a row, column, or on one of the diagonals are
|
|||
|
depressed.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Choosing a parent class
|
|||
|
|
|||
|
<p>
|
|||
|
The parent class for a composite widget is typically the container
|
|||
|
class that holds all of the elements of the composite widget. For
|
|||
|
example, the parent class of the FileSelection widget is the
|
|||
|
Dialog class. Since our buttons will be arranged in a table, it
|
|||
|
might seem natural to make our parent class the GtkTable
|
|||
|
class. Unfortunately, this turns out not to work. The creation of a
|
|||
|
widget is divided among two functions - a <tt/WIDGETNAME_new()/
|
|||
|
function that the user calls, and a <tt/WIDGETNAME_init()/ function
|
|||
|
which does the basic work of initializing the widget which is
|
|||
|
independent of the arguments passed to the <tt/_new()/
|
|||
|
function. Descendent widgets only call the <tt/_init/ function of
|
|||
|
their parent widget. But this division of labor doesn't work well for
|
|||
|
tables, which when created, need to know the number of rows and
|
|||
|
columns in the table. Unless we want to duplicate most of the
|
|||
|
functionality of <tt/gtk_table_new()/ in our Tictactoe widget, we had
|
|||
|
best avoid deriving it from GtkTable. For that reason, we derive it
|
|||
|
from GtkVBox instead, and stick our table inside the VBox.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The header file
|
|||
|
|
|||
|
<p>
|
|||
|
Each widget class has a header file which declares the object and
|
|||
|
class structures for that widget, along with public functions.
|
|||
|
A couple of features are worth pointing out. To prevent duplicate
|
|||
|
definitions, we wrap the entire header file in:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#ifndef __TICTACTOE_H__
|
|||
|
#define __TICTACTOE_H__
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
#endif /* __TICTACTOE_H__ */
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
And to keep C++ programs that include the header file happy, in:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#ifdef __cplusplus
|
|||
|
extern "C" {
|
|||
|
#endif /* __cplusplus */
|
|||
|
.
|
|||
|
.
|
|||
|
.
|
|||
|
#ifdef __cplusplus
|
|||
|
}
|
|||
|
#endif /* __cplusplus */
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Along with the functions and structures, we declare three standard
|
|||
|
macros in our header file, <tt/TICTACTOE(obj)/,
|
|||
|
<tt/TICTACTOE_CLASS(klass)/, and <tt/IS_TICTACTOE(obj)/, which cast a
|
|||
|
pointer into a pointer to the the object or class structure, and check
|
|||
|
if an object is a Tictactoe widget respectively.
|
|||
|
|
|||
|
<p>
|
|||
|
Here is the complete header file:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* tictactoe.h */
|
|||
|
|
|||
|
#ifndef __TICTACTOE_H__
|
|||
|
#define __TICTACTOE_H__
|
|||
|
|
|||
|
#include <gdk/gdk.h>
|
|||
|
#include <gtk/gtkvbox.h>
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
extern "C" {
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
|
|||
|
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
|
|||
|
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
|
|||
|
|
|||
|
|
|||
|
typedef struct _Tictactoe Tictactoe;
|
|||
|
typedef struct _TictactoeClass TictactoeClass;
|
|||
|
|
|||
|
struct _Tictactoe
|
|||
|
{
|
|||
|
GtkVBox vbox;
|
|||
|
|
|||
|
GtkWidget *buttons[3][3];
|
|||
|
};
|
|||
|
|
|||
|
struct _TictactoeClass
|
|||
|
{
|
|||
|
GtkVBoxClass parent_class;
|
|||
|
|
|||
|
void (* tictactoe) (Tictactoe *ttt);
|
|||
|
};
|
|||
|
|
|||
|
guint tictactoe_get_type (void);
|
|||
|
GtkWidget* tictactoe_new (void);
|
|||
|
void tictactoe_clear (Tictactoe *ttt);
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
}
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
#endif /* __TICTACTOE_H__ */
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The <tt/_get_type()/ function.
|
|||
|
|
|||
|
<p>
|
|||
|
We now continue on to the implementation of our widget. A core
|
|||
|
function for every widget is the function
|
|||
|
<tt/WIDGETNAME_get_type()/. This function, when first called, tells
|
|||
|
GTK about the widget class, and gets an ID that uniquely identifies
|
|||
|
the widget class. Upon subsequent calls, it just returns the ID.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
guint
|
|||
|
tictactoe_get_type ()
|
|||
|
{
|
|||
|
static guint ttt_type = 0;
|
|||
|
|
|||
|
if (!ttt_type)
|
|||
|
{
|
|||
|
GtkTypeInfo ttt_info =
|
|||
|
{
|
|||
|
"Tictactoe",
|
|||
|
sizeof (Tictactoe),
|
|||
|
sizeof (TictactoeClass),
|
|||
|
(GtkClassInitFunc) tictactoe_class_init,
|
|||
|
(GtkObjectInitFunc) tictactoe_init,
|
|||
|
(GtkArgFunc) NULL,
|
|||
|
};
|
|||
|
|
|||
|
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
|
|||
|
}
|
|||
|
|
|||
|
return ttt_type;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
The GtkTypeInfo structure has the following definition:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GtkTypeInfo
|
|||
|
{
|
|||
|
gchar *type_name;
|
|||
|
guint object_size;
|
|||
|
guint class_size;
|
|||
|
GtkClassInitFunc class_init_func;
|
|||
|
GtkObjectInitFunc object_init_func;
|
|||
|
GtkArgFunc arg_func;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
The fields of this structure are pretty self-explanatory. We'll ignore
|
|||
|
the <tt/arg_func/ field here: It has an important, but as yet largely
|
|||
|
unimplemented, role in allowing widget options to be conveniently set
|
|||
|
from interpreted languages. Once GTK has a correctly filled in copy of
|
|||
|
this structure, it knows how to create objects of a particular widget
|
|||
|
type.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The <tt/_class_init()/ function
|
|||
|
|
|||
|
<p>
|
|||
|
The <tt/WIDGETNAME_class_init()/ function initializes the fields of
|
|||
|
the widget's class structure, and sets up any signals for the
|
|||
|
class. For our Tictactoe widget it looks like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
|
|||
|
enum {
|
|||
|
TICTACTOE_SIGNAL,
|
|||
|
LAST_SIGNAL
|
|||
|
};
|
|||
|
|
|||
|
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
|
|||
|
|
|||
|
static void
|
|||
|
tictactoe_class_init (TictactoeClass *class)
|
|||
|
{
|
|||
|
GtkObjectClass *object_class;
|
|||
|
|
|||
|
object_class = (GtkObjectClass*) class;
|
|||
|
|
|||
|
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
|
|||
|
GTK_RUN_FIRST,
|
|||
|
object_class->type,
|
|||
|
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
|
|||
|
gtk_signal_default_marshaller, GTK_ARG_NONE, 0);
|
|||
|
|
|||
|
|
|||
|
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
|
|||
|
|
|||
|
class->tictactoe = NULL;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
Our widget has just one signal, the ``tictactoe'' signal that is
|
|||
|
invoked when a row, column, or diagonal is completely filled in. Not
|
|||
|
every composite widget needs signals, so if you are reading this for
|
|||
|
the first time, you may want to skip to the next section now, as
|
|||
|
things are going to get a bit complicated.
|
|||
|
|
|||
|
The function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gint gtk_signal_new (gchar *name,
|
|||
|
GtkSignalRunType run_type,
|
|||
|
gint object_type,
|
|||
|
gint function_offset,
|
|||
|
GtkSignalMarshaller marshaller,
|
|||
|
GtkArgType return_val,
|
|||
|
gint nparams,
|
|||
|
...);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Creates a new signal. The parameters are:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> <tt/name/: The name of the signal.
|
|||
|
<item> <tt/run_type/: Whether the default handler runs before or after
|
|||
|
user handlers. Usually this will be <tt/GTK_RUN_FIRST/, or <tt/GTK_RUN_LAST/,
|
|||
|
although there are other possibilities.
|
|||
|
<item> <tt/object_type/: The ID of the object that this signal applies
|
|||
|
to. (It will also apply to that objects descendents)
|
|||
|
<item> <tt/function_offset/: The offset within the class structure of
|
|||
|
a pointer to the default handler.
|
|||
|
<item> <tt/marshaller/: A function that is used to invoke the signal
|
|||
|
handler. For signal handlers that have no arguments other than the
|
|||
|
object that emitted the signal and user data, we can use the
|
|||
|
presupplied marshaller function <tt/gtk_signal_default_marshaller/.
|
|||
|
<item> <tt/return_val/: The type of the return val.
|
|||
|
<item> <tt/nparams/: The number of parameters of the signal handler
|
|||
|
(other than the two default ones mentioned above)
|
|||
|
<item> <tt/.../: The types of the parameters.
|
|||
|
</itemize>
|
|||
|
|
|||
|
When specifying types, the <tt/GtkArgType/ enumeration is used:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
typedef enum
|
|||
|
{
|
|||
|
GTK_ARG_INVALID,
|
|||
|
GTK_ARG_NONE,
|
|||
|
GTK_ARG_CHAR,
|
|||
|
GTK_ARG_SHORT,
|
|||
|
GTK_ARG_INT,
|
|||
|
GTK_ARG_LONG,
|
|||
|
GTK_ARG_POINTER,
|
|||
|
GTK_ARG_OBJECT,
|
|||
|
GTK_ARG_FUNCTION,
|
|||
|
GTK_ARG_SIGNAL
|
|||
|
} GtkArgType;
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
<tt/gtk_signal_new()/ returns a unique integer identifier for the
|
|||
|
signal, that we store in the <tt/tictactoe_signals/ array, which we
|
|||
|
index using an enumeration. (Conventionally, the enumeration elements
|
|||
|
are the signal name, uppercased, but here there would be a conflict
|
|||
|
with the <tt/TICTACTOE()/ macro, so we called it <tt/TICTACTOE_SIGNAL/
|
|||
|
instead.
|
|||
|
|
|||
|
After creating our signals, we need to tell GTK to associate our
|
|||
|
signals with the Tictactoe class. We do that by calling
|
|||
|
<tt/gtk_object_class_add_signals()/. We then set the pointer which
|
|||
|
points to the default handler for the ``tictactoe'' signal to NULL,
|
|||
|
indicating that there is no default action.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The <tt/_init()/ function.
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
Each widget class also needs a function to initialize the object
|
|||
|
structure. Usually, this function has the fairly limited role of
|
|||
|
setting the fields of the structure to default values. For composite
|
|||
|
widgets, however, this function also creates the component widgets.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
|
|||
|
static void
|
|||
|
tictactoe_init (Tictactoe *ttt)
|
|||
|
{
|
|||
|
GtkWidget *table;
|
|||
|
gint i,j;
|
|||
|
|
|||
|
table = gtk_table_new (3, 3, TRUE);
|
|||
|
gtk_container_add (GTK_CONTAINER(ttt), table);
|
|||
|
gtk_widget_show (table);
|
|||
|
|
|||
|
for (i=0;i<3; i++)
|
|||
|
for (j=0;j<3; j++)
|
|||
|
{
|
|||
|
ttt->buttons[i][j] = gtk_toggle_button_new ();
|
|||
|
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
|
|||
|
i, i+1, j, j+1);
|
|||
|
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
|
|||
|
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
|
|||
|
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
|
|||
|
gtk_widget_show (ttt->buttons[i][j]);
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> And the rest...
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
There is one more function that every widget (except for base widget
|
|||
|
types like GtkBin that cannot be instantiated) needs to have - the
|
|||
|
function that the user calls to create an object of that type. This is
|
|||
|
conventionally called <tt/WIDGETNAME_new()/In some
|
|||
|
widgets, thought not for the Tictactoe widgets, this function takes
|
|||
|
arguments, and does some setup based on the arguments. The other two
|
|||
|
functions are specific to the Tictactoe widget.
|
|||
|
|
|||
|
<p>
|
|||
|
<tt/tictactoe_clear()/ is a public function that resets all the
|
|||
|
buttons in the widget to the up position. Note the use of
|
|||
|
<tt/gtk_signal_handler_block_by_data()/ to keep our signal handler for
|
|||
|
button toggles from being triggered unnecessarily.
|
|||
|
|
|||
|
<p>
|
|||
|
<tt/tictactoe_toggle()/ is the signal handler that is invoked when the
|
|||
|
user clicks on a button. It checks to see if there are any winning
|
|||
|
combinations that involve the toggled button, and if so, emits
|
|||
|
the "tictactoe" signal.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget*
|
|||
|
tictactoe_new ()
|
|||
|
{
|
|||
|
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
tictactoe_clear (Tictactoe *ttt)
|
|||
|
{
|
|||
|
int i,j;
|
|||
|
|
|||
|
for (i=0;i<3;i++)
|
|||
|
for (j=0;j<3;j++)
|
|||
|
{
|
|||
|
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
|
|||
|
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
|
|||
|
FALSE);
|
|||
|
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
|
|||
|
{
|
|||
|
int i,k;
|
|||
|
|
|||
|
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
|
|||
|
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
|
|||
|
{ 0, 1, 2 }, { 0, 1, 2 } };
|
|||
|
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
|
|||
|
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
|
|||
|
{ 0, 1, 2 }, { 2, 1, 0 } };
|
|||
|
|
|||
|
int success, found;
|
|||
|
|
|||
|
for (k=0; k<8; k++)
|
|||
|
{
|
|||
|
success = TRUE;
|
|||
|
found = FALSE;
|
|||
|
|
|||
|
for (i=0;i<3;i++)
|
|||
|
{
|
|||
|
success = success &&
|
|||
|
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
|
|||
|
found = found ||
|
|||
|
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
|
|||
|
}
|
|||
|
|
|||
|
if (success && found)
|
|||
|
{
|
|||
|
gtk_signal_emit (GTK_OBJECT (ttt),
|
|||
|
tictactoe_signals[TICTACTOE_SIGNAL]);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
And finally, an example program using our Tictactoe widget:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#include <gtk/gtk.h>
|
|||
|
#include "tictactoe.h"
|
|||
|
|
|||
|
/* Invoked when a row, column or diagonal is completed */
|
|||
|
void
|
|||
|
win (GtkWidget *widget, gpointer data)
|
|||
|
{
|
|||
|
g_print ("Yay!\n");
|
|||
|
tictactoe_clear (TICTACTOE (widget));
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
main (int argc, char *argv[])
|
|||
|
{
|
|||
|
GtkWidget *window;
|
|||
|
GtkWidget *ttt;
|
|||
|
|
|||
|
gtk_init (&argc, &argv);
|
|||
|
|
|||
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|||
|
|
|||
|
gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|||
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|||
|
|
|||
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|||
|
|
|||
|
/* Create a new Tictactoe widget */
|
|||
|
ttt = tictactoe_new ();
|
|||
|
gtk_container_add (GTK_CONTAINER (window), ttt);
|
|||
|
gtk_widget_show (ttt);
|
|||
|
|
|||
|
/* And attach to its "tictactoe" signal */
|
|||
|
gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
|
|||
|
GTK_SIGNAL_FUNC (win), NULL);
|
|||
|
|
|||
|
gtk_widget_show (window);
|
|||
|
|
|||
|
gtk_main ();
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Creating a widget from scratch.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Introduction
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
In this section, we'll learn more about how widgets display themselves
|
|||
|
on the screen and interact with events. As an example of this, we'll
|
|||
|
create a analog dial widget with a pointer that the user can drag to
|
|||
|
set the value.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Displaying a widget on the screen
|
|||
|
|
|||
|
<p>
|
|||
|
There are several steps that are involved in displaying on the screen.
|
|||
|
After the widget is created with a call to <tt/WIDGETNAME_new()/,
|
|||
|
several more functions are needed:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> <tt/WIDGETNAME_realize()/ is responsible for creating an X
|
|||
|
window for the widget if it has one.
|
|||
|
<item> <tt/WIDGETNAME_map()/ is invoked after the user calls
|
|||
|
<tt/gtk_widget_show()/. It is responsible for making sure the widget
|
|||
|
is actually drawn on the screen (<em/mapped/). For a container class,
|
|||
|
it must also make calls to <tt/map()/> functions of any child widgets.
|
|||
|
<item> <tt/WIDGETNAME_draw()/ is invoked when <tt/gtk_widget_draw()/
|
|||
|
is called for the widget or one of its ancestors. It makes the actual
|
|||
|
calls to the drawing functions to draw the widget on the screen. For
|
|||
|
container widgets, this function must make calls to
|
|||
|
<tt/gtk_widget_draw()/ for its child widgets.
|
|||
|
<item> <tt/WIDGETNAME_expose()/ is a handler for expose events for the
|
|||
|
widget. It makes the necessary calls to the drawing functions to draw
|
|||
|
the exposed portion on the screen. For container widgets, this
|
|||
|
function must generate expose events for its child widgets which don't
|
|||
|
have their own windows. (If they have their own windows, then X will
|
|||
|
generate the necessary expose events)
|
|||
|
</itemize>
|
|||
|
|
|||
|
<p>
|
|||
|
You might notice that the last two functions are quite similar - each
|
|||
|
is responsible for drawing the widget on the screen. In fact many
|
|||
|
types of widgets don't really care about the difference between the
|
|||
|
two. The default <tt/draw()/ function in the widget class simply
|
|||
|
generates a synthetic expose event for the redrawn area. However, some
|
|||
|
types of widgets can save work by distinguishing between the two
|
|||
|
functions. For instance, if a widget has multiple X windows, then
|
|||
|
since expose events identify the exposed window, it can redraw only
|
|||
|
the affected window, which is not possible for calls to <tt/draw()/.
|
|||
|
|
|||
|
<p>
|
|||
|
Container widgets, even if they don't care about the difference for
|
|||
|
themselves, can't simply use the default <tt/draw()/ function because
|
|||
|
their child widgets might care about the difference. However,
|
|||
|
it would be wasteful to duplicate the drawing code between the two
|
|||
|
functions. The convention is that such widgets have a function called
|
|||
|
<tt/WIDGETNAME_paint()/ that does the actual work of drawing the
|
|||
|
widget, that is then called by the <tt/draw()/ and <tt/expose()/
|
|||
|
functions.
|
|||
|
|
|||
|
<p>
|
|||
|
In our example approach, since the dial widget is not a container
|
|||
|
widget, and only has a single window, we can take the simplest
|
|||
|
approach and use the default <tt/draw()/ function and only implement
|
|||
|
an <tt/expose()/ function.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The origins of the Dial Widget
|
|||
|
|
|||
|
<p>
|
|||
|
Just as all land animals are just variants on the first amphibian that
|
|||
|
crawled up out of the mud, Gtk widgets tend to start off as variants
|
|||
|
of some other, previously written widget. Thus, although this section
|
|||
|
is entilted ``Creating a Widget from Scratch'', the Dial widget really
|
|||
|
began with the source code for the Range widget. This was picked as a
|
|||
|
starting point because it would be nice if our Dial had the same
|
|||
|
interface as the Scale widgets which are just specialized descendents
|
|||
|
of the Range widget. So, though the source code is presented below in
|
|||
|
finished form, it should not be implied that it was written, <em>deus
|
|||
|
ex machina</em> in this fashion. Also, if you aren't yet familiar with
|
|||
|
how scale widgets work from the application writer's point of view, it
|
|||
|
would be a good idea to look them over before continuing.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> The Basics
|
|||
|
|
|||
|
<p>
|
|||
|
Quite a bit of our widget should look pretty familiar from the
|
|||
|
Tictactoe widget. First, we have a header file:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* GTK - The GIMP Toolkit
|
|||
|
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
|
|||
|
*
|
|||
|
* This library is free software; you can redistribute it and/or
|
|||
|
* modify it under the terms of the GNU Library General Public
|
|||
|
* License as published by the Free Software Foundation; either
|
|||
|
* version 2 of the License, or (at your option) any later version.
|
|||
|
*
|
|||
|
* This library is distributed in the hope that it will be useful,
|
|||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|||
|
* Library General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU Library General Public
|
|||
|
* License along with this library; if not, write to the Free
|
|||
|
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|||
|
*/
|
|||
|
|
|||
|
#ifndef __GTK_DIAL_H__
|
|||
|
#define __GTK_DIAL_H__
|
|||
|
|
|||
|
#include <gdk/gdk.h>
|
|||
|
#include <gtk/gtkadjustment.h>
|
|||
|
#include <gtk/gtkwidget.h>
|
|||
|
|
|||
|
|
|||
|
#ifdef __cplusplus
|
|||
|
extern "C" {
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
|
|||
|
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
|
|||
|
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
|
|||
|
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
|
|||
|
|
|||
|
|
|||
|
typedef struct _GtkDial GtkDial;
|
|||
|
typedef struct _GtkDialClass GtkDialClass;
|
|||
|
|
|||
|
struct _GtkDial
|
|||
|
{
|
|||
|
GtkWidget widget;
|
|||
|
|
|||
|
/* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
|
|||
|
guint policy : 2;
|
|||
|
|
|||
|
/* Button currently pressed or 0 if none */
|
|||
|
guint8 button;
|
|||
|
|
|||
|
/* Dimensions of dial components */
|
|||
|
gint radius;
|
|||
|
gint pointer_width;
|
|||
|
|
|||
|
/* ID of update timer, or 0 if none */
|
|||
|
guint32 timer;
|
|||
|
|
|||
|
/* Current angle */
|
|||
|
gfloat angle;
|
|||
|
|
|||
|
/* Old values from adjustment stored so we know when something changes */
|
|||
|
gfloat old_value;
|
|||
|
gfloat old_lower;
|
|||
|
gfloat old_upper;
|
|||
|
|
|||
|
/* The adjustment object that stores the data for this dial */
|
|||
|
GtkAdjustment *adjustment;
|
|||
|
};
|
|||
|
|
|||
|
struct _GtkDialClass
|
|||
|
{
|
|||
|
GtkWidgetClass parent_class;
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
|
|||
|
guint gtk_dial_get_type (void);
|
|||
|
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
|
|||
|
void gtk_dial_set_update_policy (GtkDial *dial,
|
|||
|
GtkUpdateType policy);
|
|||
|
|
|||
|
void gtk_dial_set_adjustment (GtkDial *dial,
|
|||
|
GtkAdjustment *adjustment);
|
|||
|
#ifdef __cplusplus
|
|||
|
}
|
|||
|
#endif /* __cplusplus */
|
|||
|
|
|||
|
|
|||
|
#endif /* __GTK_DIAL_H__ */
|
|||
|
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Since there is quite a bit more going on in this widget, than the last
|
|||
|
one, we have more fields in the data structure, but otherwise things
|
|||
|
are pretty similar.
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
Next, after including header files, and declaring a few constants,
|
|||
|
we have some functions to provide information about the widget
|
|||
|
and initialize it:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
#include <math.h>
|
|||
|
#include <stdio.h>
|
|||
|
#include <gtk/gtkmain.h>
|
|||
|
#include <gtk/gtksignal.h>
|
|||
|
|
|||
|
#include "gtkdial.h"
|
|||
|
|
|||
|
#define SCROLL_DELAY_LENGTH 300
|
|||
|
#define DIAL_DEFAULT_SIZE 100
|
|||
|
|
|||
|
/* Forward declararations */
|
|||
|
|
|||
|
[ omitted to save space ]
|
|||
|
|
|||
|
/* Local data */
|
|||
|
|
|||
|
static GtkWidgetClass *parent_class = NULL;
|
|||
|
|
|||
|
guint
|
|||
|
gtk_dial_get_type ()
|
|||
|
{
|
|||
|
static guint dial_type = 0;
|
|||
|
|
|||
|
if (!dial_type)
|
|||
|
{
|
|||
|
GtkTypeInfo dial_info =
|
|||
|
{
|
|||
|
"GtkDial",
|
|||
|
sizeof (GtkDial),
|
|||
|
sizeof (GtkDialClass),
|
|||
|
(GtkClassInitFunc) gtk_dial_class_init,
|
|||
|
(GtkObjectInitFunc) gtk_dial_init,
|
|||
|
(GtkArgFunc) NULL,
|
|||
|
};
|
|||
|
|
|||
|
dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
|
|||
|
}
|
|||
|
|
|||
|
return dial_type;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_class_init (GtkDialClass *class)
|
|||
|
{
|
|||
|
GtkObjectClass *object_class;
|
|||
|
GtkWidgetClass *widget_class;
|
|||
|
|
|||
|
object_class = (GtkObjectClass*) class;
|
|||
|
widget_class = (GtkWidgetClass*) class;
|
|||
|
|
|||
|
parent_class = gtk_type_class (gtk_widget_get_type ());
|
|||
|
|
|||
|
object_class->destroy = gtk_dial_destroy;
|
|||
|
|
|||
|
widget_class->realize = gtk_dial_realize;
|
|||
|
widget_class->expose_event = gtk_dial_expose;
|
|||
|
widget_class->size_request = gtk_dial_size_request;
|
|||
|
widget_class->size_allocate = gtk_dial_size_allocate;
|
|||
|
widget_class->button_press_event = gtk_dial_button_press;
|
|||
|
widget_class->button_release_event = gtk_dial_button_release;
|
|||
|
widget_class->motion_notify_event = gtk_dial_motion_notify;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_init (GtkDial *dial)
|
|||
|
{
|
|||
|
dial->button = 0;
|
|||
|
dial->policy = GTK_UPDATE_CONTINUOUS;
|
|||
|
dial->timer = 0;
|
|||
|
dial->radius = 0;
|
|||
|
dial->pointer_width = 0;
|
|||
|
dial->angle = 0.0;
|
|||
|
dial->old_value = 0.0;
|
|||
|
dial->old_lower = 0.0;
|
|||
|
dial->old_upper = 0.0;
|
|||
|
dial->adjustment = NULL;
|
|||
|
}
|
|||
|
|
|||
|
GtkWidget*
|
|||
|
gtk_dial_new (GtkAdjustment *adjustment)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
dial = gtk_type_new (gtk_dial_get_type ());
|
|||
|
|
|||
|
if (!adjustment)
|
|||
|
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
|||
|
|
|||
|
gtk_dial_set_adjustment (dial, adjustment);
|
|||
|
|
|||
|
return GTK_WIDGET (dial);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_destroy (GtkObject *object)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
g_return_if_fail (object != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (object));
|
|||
|
|
|||
|
dial = GTK_DIAL (object);
|
|||
|
|
|||
|
if (dial->adjustment)
|
|||
|
gtk_object_unref (GTK_OBJECT (dial->adjustment));
|
|||
|
|
|||
|
if (GTK_OBJECT_CLASS (parent_class)->destroy)
|
|||
|
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Note that this <tt/init()/ function does less than for the Tictactoe
|
|||
|
widget, since this is not a composite widget, and the <tt/new()/
|
|||
|
function does more, since it now has an argument. Also, note that when
|
|||
|
we store a pointer to the Adjustment object, we increment its
|
|||
|
reference count, (and correspondingly decrement when we no longer use
|
|||
|
it) so that GTK can keep track of when it can be safely destroyed.
|
|||
|
|
|||
|
<p>
|
|||
|
Also, there are a few function to manipulate the widget's options:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkAdjustment*
|
|||
|
gtk_dial_get_adjustment (GtkDial *dial)
|
|||
|
{
|
|||
|
g_return_val_if_fail (dial != NULL, NULL);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
|
|||
|
|
|||
|
return dial->adjustment;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
gtk_dial_set_update_policy (GtkDial *dial,
|
|||
|
GtkUpdateType policy)
|
|||
|
{
|
|||
|
g_return_if_fail (dial != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (dial));
|
|||
|
|
|||
|
dial->policy = policy;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
gtk_dial_set_adjustment (GtkDial *dial,
|
|||
|
GtkAdjustment *adjustment)
|
|||
|
{
|
|||
|
g_return_if_fail (dial != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (dial));
|
|||
|
|
|||
|
if (dial->adjustment)
|
|||
|
{
|
|||
|
gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
|
|||
|
gtk_object_unref (GTK_OBJECT (dial->adjustment));
|
|||
|
}
|
|||
|
|
|||
|
dial->adjustment = adjustment;
|
|||
|
gtk_object_ref (GTK_OBJECT (dial->adjustment));
|
|||
|
|
|||
|
gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
|
|||
|
(GtkSignalFunc) gtk_dial_adjustment_changed,
|
|||
|
(gpointer) dial);
|
|||
|
gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
|
|||
|
(GtkSignalFunc) gtk_dial_adjustment_value_changed,
|
|||
|
(gpointer) dial);
|
|||
|
|
|||
|
dial->old_value = adjustment->value;
|
|||
|
dial->old_lower = adjustment->lower;
|
|||
|
dial->old_upper = adjustment->upper;
|
|||
|
|
|||
|
gtk_dial_update (dial);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<sect2> <tt/gtk_dial_realize()/
|
|||
|
|
|||
|
<p>
|
|||
|
Now we come to some new types of functions. First, we have a function
|
|||
|
that does the work of creating the X window. Notice that a mask is
|
|||
|
passed to the function <tt/gdk_window_new()/ which specifies which fields of
|
|||
|
the GdkWindowAttr structure actually have data in them (the remaining
|
|||
|
fields wll be given default values). Also worth noting is the way the
|
|||
|
event mask of the widget is created. We call
|
|||
|
<tt/gtk_widget_get_events()/ to retrieve the event mask that the user
|
|||
|
has specified for this widget (with <tt/gtk_widget_set_events()/, and
|
|||
|
add the events that we are interested in ourselves.
|
|||
|
|
|||
|
<p>
|
|||
|
After creating the window, we set its style and background, and put a
|
|||
|
pointer to the widget in the user data field of the GdkWindow. This
|
|||
|
last step allows GTK to dispatch events for this window to the correct
|
|||
|
widget.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static void
|
|||
|
gtk_dial_realize (GtkWidget *widget)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
GdkWindowAttr attributes;
|
|||
|
gint attributes_mask;
|
|||
|
|
|||
|
g_return_if_fail (widget != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (widget));
|
|||
|
|
|||
|
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
attributes.x = widget->allocation.x;
|
|||
|
attributes.y = widget->allocation.y;
|
|||
|
attributes.width = widget->allocation.width;
|
|||
|
attributes.height = widget->allocation.height;
|
|||
|
attributes.wclass = GDK_INPUT_OUTPUT;
|
|||
|
attributes.window_type = GDK_WINDOW_CHILD;
|
|||
|
attributes.event_mask = gtk_widget_get_events (widget) |
|
|||
|
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
|
|||
|
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
|
|||
|
GDK_POINTER_MOTION_HINT_MASK;
|
|||
|
attributes.visual = gtk_widget_get_visual (widget);
|
|||
|
attributes.colormap = gtk_widget_get_colormap (widget);
|
|||
|
|
|||
|
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
|
|||
|
widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
|
|||
|
|
|||
|
widget->style = gtk_style_attach (widget->style, widget->window);
|
|||
|
|
|||
|
gdk_window_set_user_data (widget->window, widget);
|
|||
|
|
|||
|
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<sect2> Size negotiation
|
|||
|
|
|||
|
<p>
|
|||
|
Before the first time that the window containing a widget is
|
|||
|
displayed, and whenever the layout of the window changes, GTK asks
|
|||
|
each child widget for its desired size. This request is handled by the
|
|||
|
function, <tt/gtk_dial_size_request()/. Since our widget isn't a
|
|||
|
container widget, and has no real constraints on its size, we just
|
|||
|
return a reasonable default value.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static void
|
|||
|
gtk_dial_size_request (GtkWidget *widget,
|
|||
|
GtkRequisition *requisition)
|
|||
|
{
|
|||
|
requisition->width = DIAL_DEFAULT_SIZE;
|
|||
|
requisition->height = DIAL_DEFAULT_SIZE;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
After all the widgets have requested an ideal size, the layout of the
|
|||
|
window is computed and each child widget is notified of its actual
|
|||
|
size. Usually, this will at least as large as the requested size, but
|
|||
|
if for instance, the user has resized the window, it may occasionally
|
|||
|
be smaller than the requested size. The size notification is handled
|
|||
|
by the function <tt/gtk_dial_size_allocate()/. Notice that as well as
|
|||
|
computing the sizes of some component pieces for future use, this
|
|||
|
routine also does the grunt work of moving the widgets X window into
|
|||
|
the new position and size.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static void
|
|||
|
gtk_dial_size_allocate (GtkWidget *widget,
|
|||
|
GtkAllocation *allocation)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
g_return_if_fail (widget != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (widget));
|
|||
|
g_return_if_fail (allocation != NULL);
|
|||
|
|
|||
|
widget->allocation = *allocation;
|
|||
|
if (GTK_WIDGET_REALIZED (widget))
|
|||
|
{
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
gdk_window_move_resize (widget->window,
|
|||
|
allocation->x, allocation->y,
|
|||
|
allocation->width, allocation->height);
|
|||
|
|
|||
|
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
|
|||
|
dial->pointer_width = dial->radius / 5;
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> <tt/gtk_dial_expose()/
|
|||
|
|
|||
|
<p>
|
|||
|
As mentioned above, all the drawing of this widget is done in the
|
|||
|
handler for expose events. There's not much to remark on here except
|
|||
|
the use of the function <tt/gtk_draw_polygon/ to draw the pointer with
|
|||
|
three dimensional shading according to the colors stored in the
|
|||
|
widget's style.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static gint
|
|||
|
gtk_dial_expose (GtkWidget *widget,
|
|||
|
GdkEventExpose *event)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
GdkPoint points[3];
|
|||
|
gdouble s,c;
|
|||
|
gdouble theta;
|
|||
|
gint xc, yc;
|
|||
|
gint tick_length;
|
|||
|
gint i;
|
|||
|
|
|||
|
g_return_val_if_fail (widget != NULL, FALSE);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
|
|||
|
g_return_val_if_fail (event != NULL, FALSE);
|
|||
|
|
|||
|
if (event->count > 0)
|
|||
|
return FALSE;
|
|||
|
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
gdk_window_clear_area (widget->window,
|
|||
|
0, 0,
|
|||
|
widget->allocation.width,
|
|||
|
widget->allocation.height);
|
|||
|
|
|||
|
xc = widget->allocation.width/2;
|
|||
|
yc = widget->allocation.height/2;
|
|||
|
|
|||
|
/* Draw ticks */
|
|||
|
|
|||
|
for (i=0; i<25; i++)
|
|||
|
{
|
|||
|
theta = (i*M_PI/18. - M_PI/6.);
|
|||
|
s = sin(theta);
|
|||
|
c = cos(theta);
|
|||
|
|
|||
|
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
|
|||
|
|
|||
|
gdk_draw_line (widget->window,
|
|||
|
widget->style->fg_gc[widget->state],
|
|||
|
xc + c*(dial->radius - tick_length),
|
|||
|
yc - s*(dial->radius - tick_length),
|
|||
|
xc + c*dial->radius,
|
|||
|
yc - s*dial->radius);
|
|||
|
}
|
|||
|
|
|||
|
/* Draw pointer */
|
|||
|
|
|||
|
s = sin(dial->angle);
|
|||
|
c = cos(dial->angle);
|
|||
|
|
|||
|
|
|||
|
points[0].x = xc + s*dial->pointer_width/2;
|
|||
|
points[0].y = yc + c*dial->pointer_width/2;
|
|||
|
points[1].x = xc + c*dial->radius;
|
|||
|
points[1].y = yc - s*dial->radius;
|
|||
|
points[2].x = xc - s*dial->pointer_width/2;
|
|||
|
points[2].y = yc - c*dial->pointer_width/2;
|
|||
|
|
|||
|
gtk_draw_polygon (widget->style,
|
|||
|
widget->window,
|
|||
|
GTK_STATE_NORMAL,
|
|||
|
GTK_SHADOW_OUT,
|
|||
|
points, 3,
|
|||
|
TRUE);
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Event handling
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
The rest of the widget's code handles various types of events, and
|
|||
|
isn't too different from what would be found in many GTK
|
|||
|
applications. Two types of events can occur - either the user can
|
|||
|
click on the widget with the mouse and drag to move the pointer, or
|
|||
|
the value of the Adjustment object can change due to some external
|
|||
|
circumstance.
|
|||
|
|
|||
|
<p>
|
|||
|
When the user clicks on the widget, we check to see if the click was
|
|||
|
appropriately near the pointer, and if so, store then button that the
|
|||
|
user clicked with in the <tt/button/ field of the widget
|
|||
|
structure, and grab all mouse events with a call to
|
|||
|
<tt/gtk_grab_add()/. Subsequent motion of the mouse causes the
|
|||
|
value of the control to be recomputed (by the function
|
|||
|
<tt/gtk_dial_update_mouse/). Depending on the policy that has been
|
|||
|
set, "value_changed" events are either generated instantly
|
|||
|
(<tt/GTK_UPDATE_CONTINUOUS/), after a delay in a timer added with
|
|||
|
<tt/gtk_timeout_add()/ (<tt/GTK_UPDATE_DELAYED/), or only when the
|
|||
|
button is released (<tt/GTK_UPDATE_DISCONTINUOUS/).
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static gint
|
|||
|
gtk_dial_button_press (GtkWidget *widget,
|
|||
|
GdkEventButton *event)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
gint dx, dy;
|
|||
|
double s, c;
|
|||
|
double d_parallel;
|
|||
|
double d_perpendicular;
|
|||
|
|
|||
|
g_return_val_if_fail (widget != NULL, FALSE);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
|
|||
|
g_return_val_if_fail (event != NULL, FALSE);
|
|||
|
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
/* Determine if button press was within pointer region - we
|
|||
|
do this by computing the parallel and perpendicular distance of
|
|||
|
the point where the mouse was pressed from the line passing through
|
|||
|
the pointer */
|
|||
|
|
|||
|
dx = event->x - widget->allocation.width / 2;
|
|||
|
dy = widget->allocation.height / 2 - event->y;
|
|||
|
|
|||
|
s = sin(dial->angle);
|
|||
|
c = cos(dial->angle);
|
|||
|
|
|||
|
d_parallel = s*dy + c*dx;
|
|||
|
d_perpendicular = fabs(s*dx - c*dy);
|
|||
|
|
|||
|
if (!dial->button &&
|
|||
|
(d_perpendicular < dial->pointer_width/2) &&
|
|||
|
(d_parallel > - dial->pointer_width))
|
|||
|
{
|
|||
|
gtk_grab_add (widget);
|
|||
|
|
|||
|
dial->button = event->button;
|
|||
|
|
|||
|
gtk_dial_update_mouse (dial, event->x, event->y);
|
|||
|
}
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
static gint
|
|||
|
gtk_dial_button_release (GtkWidget *widget,
|
|||
|
GdkEventButton *event)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
g_return_val_if_fail (widget != NULL, FALSE);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
|
|||
|
g_return_val_if_fail (event != NULL, FALSE);
|
|||
|
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
if (dial->button == event->button)
|
|||
|
{
|
|||
|
gtk_grab_remove (widget);
|
|||
|
|
|||
|
dial->button = 0;
|
|||
|
|
|||
|
if (dial->policy == GTK_UPDATE_DELAYED)
|
|||
|
gtk_timeout_remove (dial->timer);
|
|||
|
|
|||
|
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
|
|||
|
(dial->old_value != dial->adjustment->value))
|
|||
|
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
|
|||
|
}
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
static gint
|
|||
|
gtk_dial_motion_notify (GtkWidget *widget,
|
|||
|
GdkEventMotion *event)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
GdkModifierType mods;
|
|||
|
gint x, y, mask;
|
|||
|
|
|||
|
g_return_val_if_fail (widget != NULL, FALSE);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
|
|||
|
g_return_val_if_fail (event != NULL, FALSE);
|
|||
|
|
|||
|
dial = GTK_DIAL (widget);
|
|||
|
|
|||
|
if (dial->button != 0)
|
|||
|
{
|
|||
|
x = event->x;
|
|||
|
y = event->y;
|
|||
|
|
|||
|
if (event->is_hint || (event->window != widget->window))
|
|||
|
gdk_window_get_pointer (widget->window, &x, &y, &mods);
|
|||
|
|
|||
|
switch (dial->button)
|
|||
|
{
|
|||
|
case 1:
|
|||
|
mask = GDK_BUTTON1_MASK;
|
|||
|
break;
|
|||
|
case 2:
|
|||
|
mask = GDK_BUTTON2_MASK;
|
|||
|
break;
|
|||
|
case 3:
|
|||
|
mask = GDK_BUTTON3_MASK;
|
|||
|
break;
|
|||
|
default:
|
|||
|
mask = 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (mods & mask)
|
|||
|
gtk_dial_update_mouse (dial, x,y);
|
|||
|
}
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
static gint
|
|||
|
gtk_dial_timer (GtkDial *dial)
|
|||
|
{
|
|||
|
g_return_val_if_fail (dial != NULL, FALSE);
|
|||
|
g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
|
|||
|
|
|||
|
if (dial->policy == GTK_UPDATE_DELAYED)
|
|||
|
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
|
|||
|
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
|
|||
|
{
|
|||
|
gint xc, yc;
|
|||
|
gfloat old_value;
|
|||
|
|
|||
|
g_return_if_fail (dial != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (dial));
|
|||
|
|
|||
|
xc = GTK_WIDGET(dial)->allocation.width / 2;
|
|||
|
yc = GTK_WIDGET(dial)->allocation.height / 2;
|
|||
|
|
|||
|
old_value = dial->adjustment->value;
|
|||
|
dial->angle = atan2(yc-y, x-xc);
|
|||
|
|
|||
|
if (dial->angle < -M_PI/2.)
|
|||
|
dial->angle += 2*M_PI;
|
|||
|
|
|||
|
if (dial->angle < -M_PI/6)
|
|||
|
dial->angle = -M_PI/6;
|
|||
|
|
|||
|
if (dial->angle > 7.*M_PI/6.)
|
|||
|
dial->angle = 7.*M_PI/6.;
|
|||
|
|
|||
|
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
|
|||
|
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
|
|||
|
|
|||
|
if (dial->adjustment->value != old_value)
|
|||
|
{
|
|||
|
if (dial->policy == GTK_UPDATE_CONTINUOUS)
|
|||
|
{
|
|||
|
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
gtk_widget_draw (GTK_WIDGET(dial), NULL);
|
|||
|
|
|||
|
if (dial->policy == GTK_UPDATE_DELAYED)
|
|||
|
{
|
|||
|
if (dial->timer)
|
|||
|
gtk_timeout_remove (dial->timer);
|
|||
|
|
|||
|
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
|
|||
|
(GtkFunction) gtk_dial_timer,
|
|||
|
(gpointer) dial);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
Changes to the Adjustment by external means are communicated to our
|
|||
|
widget by the ``changed'' and ``value_changed'' signals. The handlers
|
|||
|
for these functions call <tt/gtk_dial_update()/ to validate the
|
|||
|
arguments, compute the new pointer angle, and redraw the widget (by
|
|||
|
calling <tt/gtk_widget_draw()/).
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static void
|
|||
|
gtk_dial_update (GtkDial *dial)
|
|||
|
{
|
|||
|
gfloat new_value;
|
|||
|
|
|||
|
g_return_if_fail (dial != NULL);
|
|||
|
g_return_if_fail (GTK_IS_DIAL (dial));
|
|||
|
|
|||
|
new_value = dial->adjustment->value;
|
|||
|
|
|||
|
if (new_value < dial->adjustment->lower)
|
|||
|
new_value = dial->adjustment->lower;
|
|||
|
|
|||
|
if (new_value > dial->adjustment->upper)
|
|||
|
new_value = dial->adjustment->upper;
|
|||
|
|
|||
|
if (new_value != dial->adjustment->value)
|
|||
|
{
|
|||
|
dial->adjustment->value = new_value;
|
|||
|
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
|
|||
|
}
|
|||
|
|
|||
|
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
|
|||
|
(dial->adjustment->upper - dial->adjustment->lower);
|
|||
|
|
|||
|
gtk_widget_draw (GTK_WIDGET(dial), NULL);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
|
|||
|
gpointer data)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
g_return_if_fail (adjustment != NULL);
|
|||
|
g_return_if_fail (data != NULL);
|
|||
|
|
|||
|
dial = GTK_DIAL (data);
|
|||
|
|
|||
|
if ((dial->old_value != adjustment->value) ||
|
|||
|
(dial->old_lower != adjustment->lower) ||
|
|||
|
(dial->old_upper != adjustment->upper))
|
|||
|
{
|
|||
|
gtk_dial_update (dial);
|
|||
|
|
|||
|
dial->old_value = adjustment->value;
|
|||
|
dial->old_lower = adjustment->lower;
|
|||
|
dial->old_upper = adjustment->upper;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
|
|||
|
gpointer data)
|
|||
|
{
|
|||
|
GtkDial *dial;
|
|||
|
|
|||
|
g_return_if_fail (adjustment != NULL);
|
|||
|
g_return_if_fail (data != NULL);
|
|||
|
|
|||
|
dial = GTK_DIAL (data);
|
|||
|
|
|||
|
if (dial->old_value != adjustment->value)
|
|||
|
{
|
|||
|
gtk_dial_update (dial);
|
|||
|
|
|||
|
dial->old_value = adjustment->value;
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect2> Possible Enhancements
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
The Dial widget as we've described it so far runs about 670 lines of
|
|||
|
code. Although that might sound like a fair bit, we've really
|
|||
|
accomplished quite a bit with that much code, especially since much of
|
|||
|
that length is headers and boilerplate. However, there are quite a few
|
|||
|
more enhancements that could be made to this widget:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> If you try this widget out, you'll find that there is some
|
|||
|
flashing as the pointer is dragged around. This is because the entire
|
|||
|
widget is erased every time the pointer is moved before being
|
|||
|
redrawn. Often, the best way to handle this problem is to draw to an
|
|||
|
offscreen pixmap, then copy the final results onto the screen in one
|
|||
|
step. (The ProgressBar widget draws itself in this fashion.)
|
|||
|
|
|||
|
<item> The user should be able to use the up and down arrow keys to
|
|||
|
increase and decrease the value.
|
|||
|
|
|||
|
<item> It would be nice if the widget had buttons to increase and
|
|||
|
decrease the value in small or large steps. Although it would be
|
|||
|
possible to use embedded Button widgets for this, we would also like
|
|||
|
the buttons to auto-repeat when held down, as the arrows on a
|
|||
|
scrollbar do. Most of the code to implement this type of behavior can
|
|||
|
be found in the GtkRange widget.
|
|||
|
|
|||
|
<item> The Dial widget could be made into a container widget with a
|
|||
|
single child widget positioned at the bottom between the buttons
|
|||
|
mentioned above. The user could then add their choice of a label or
|
|||
|
entry widget to display the current value of the dial.
|
|||
|
|
|||
|
</itemize>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Learning More
|
|||
|
|
|||
|
<p>
|
|||
|
Only a small part of the many details involved in creating widgets
|
|||
|
could be described above. If you want to write your own widgets, the
|
|||
|
best source of examples is the GTK source itself. Ask yourself some
|
|||
|
questions about the widget you want to write: is it a Container
|
|||
|
widget? does it have its own window? is it a modification of an
|
|||
|
existing widget? Then find a similar widget, and start making changes.
|
|||
|
Good luck!
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Scribble, A Simple Example Drawing Program
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Overview
|
|||
|
|
|||
|
<p>
|
|||
|
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. After
|
|||
|
creating the simple drawing program, we will extend it by adding
|
|||
|
support for XInput devices, such as drawing tablets. GTK provides
|
|||
|
support routines which makes getting extended information, such as
|
|||
|
pressure and tilt, from such devices quite easy.
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Event Handling
|
|||
|
|
|||
|
<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 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 <em>events</em>. The handlers for these signals have an
|
|||
|
extra parameter which is a pointer to a structure containing
|
|||
|
information about the event. For instance, motion events handlers are
|
|||
|
passed a pointer to a GdkEventMotion structure which looks (in part)
|
|||
|
like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GdkEventMotion
|
|||
|
{
|
|||
|
GdkEventType type;
|
|||
|
GdkWindow *window;
|
|||
|
guint32 time;
|
|||
|
gdouble x;
|
|||
|
gdouble y;
|
|||
|
...
|
|||
|
guint state;
|
|||
|
...
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tt/type/ will be set to the event type, in this case
|
|||
|
<tt/GDK_MOTION_NOTIFY/, window is the window in which the event
|
|||
|
occured. <tt/x/ and <tt/y/ give the coordinates of the event,
|
|||
|
and <tt/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:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<p>
|
|||
|
As for other signals, to determine what happens when an event occurs
|
|||
|
we call <tt>gtk_signal_connect()</tt>. But we also need let GTK
|
|||
|
know which events we want to be notified about. To do this, we call
|
|||
|
the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_widget_set_events (GtkWidget *widget,
|
|||
|
gint events);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The second field specifies 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:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GDK_EXPOSURE_MASK
|
|||
|
GDK_POINTER_MOTION_MASK
|
|||
|
GDK_POINTER_MOTION_HINT_MASK
|
|||
|
GDK_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
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
There are a few subtle points that have to be observed when calling
|
|||
|
<tt/gtk_widget_set_events()/. 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:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkAlignment
|
|||
|
GtkArrow
|
|||
|
GtkBin
|
|||
|
GtkBox
|
|||
|
GtkImage
|
|||
|
GtkItem
|
|||
|
GtkLabel
|
|||
|
GtkPaned
|
|||
|
GtkPixmap
|
|||
|
GtkScrolledWindow
|
|||
|
GtkSeparator
|
|||
|
GtkTable
|
|||
|
GtkViewport
|
|||
|
GtkAspectFrame
|
|||
|
GtkFrame
|
|||
|
GtkVPaned
|
|||
|
GtkHPaned
|
|||
|
GtkVBox
|
|||
|
GtkHBox
|
|||
|
GtkVSeparator
|
|||
|
GtkHSeparator
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
To capture events for these widgets, you need to use an EventBox
|
|||
|
widget. See the section on
|
|||
|
<ref id="sec_The_EventBox_Widget" name="The EventBox Widget"> for
|
|||
|
details.
|
|||
|
|
|||
|
<p>
|
|||
|
For our drawing program, we want to know when the mouse button is
|
|||
|
pressed and when the mouse is moved, so we specify
|
|||
|
<tt/GDK_POINTER_MOTION_MASK/ and <tt/GDK_BUTTON_PRESS_MASK/. We also
|
|||
|
want to know when we need to redraw our window, so we specify
|
|||
|
<tt/GDK_EXPOSURE_MASK/. Although we want to be notified via a
|
|||
|
Configure event when our window size changes, we don't have to specify
|
|||
|
the corresponding <tt/GDK_STRUCTURE_MASK/ flag, because it is
|
|||
|
automatically specified for all windows.
|
|||
|
|
|||
|
<p>
|
|||
|
It turns out, however, that there is a problem with just specifying
|
|||
|
<tt/GDK_POINTER_MOTION_MASK/. 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 <tt/GDK_POINTER_MOTION_HINT_MASK/.
|
|||
|
|
|||
|
<p>
|
|||
|
When we specify <tt/GDK_POINTER_MOTION_HINT_MASK/, 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 explicitely ask for
|
|||
|
the position of the pointer using the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkWindow* gdk_window_get_pointer (GdkWindow *window,
|
|||
|
gint *x,
|
|||
|
gint *y,
|
|||
|
GdkModifierType *mask);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
(There is another function, <tt>gtk_widget_get_pointer()</tt> which
|
|||
|
has a simpler interface, but turns out not to be very useful, since
|
|||
|
it only retrieves the position of the mouse, not whether the buttons
|
|||
|
are pressed.)
|
|||
|
|
|||
|
<p>
|
|||
|
The code to set the events for our window then looks like:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
We'll save the "expose_event" and "configure_event" handlers for
|
|||
|
later. The "motion_notify_event" and "button_press_event" handlers
|
|||
|
pretty simple:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> The DrawingArea Widget, And Drawing
|
|||
|
|
|||
|
<p>
|
|||
|
We know 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 created using the call:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GtkWidget* gtk_drawing_area_new (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
A default size for the widget can be specified by calling:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_drawing_area_size (GtkDrawingArea *darea,
|
|||
|
gint width,
|
|||
|
gint height);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
This default size can be overriden, as is true for all widgets,
|
|||
|
by calling <tt>gtk_widget_set_usize()</tt>, and that, in turn, can
|
|||
|
be overridden if the user manually resizes the the window containing
|
|||
|
the 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 our
|
|||
|
window is obscured then uncovered, we get an exposure event and must
|
|||
|
redraw what was previously hidden.
|
|||
|
|
|||
|
<p>
|
|||
|
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 <em>backing pixmap</em>.
|
|||
|
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.
|
|||
|
|
|||
|
<p>
|
|||
|
To create an offscreen pixmap, we call the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
|
|||
|
gint width,
|
|||
|
gint height,
|
|||
|
gint depth);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The <tt>window</tt> parameter specifies a GDK window that this pixmap
|
|||
|
takes some of its properties from. <tt>width</tt> and <tt>height</tt>
|
|||
|
specify the size of the pixmap. <tt>depth</tt> specifies the <em>color
|
|||
|
depth</em>, that is the number of bits per pixel, for the new window.
|
|||
|
If the depth is specified as <tt>-1</tt>, it will match the depth
|
|||
|
of <tt>window</tt>.
|
|||
|
|
|||
|
<p>
|
|||
|
We create the pixmap in our "configure_event" handler. This event
|
|||
|
is generated whenever the window changes size, including when it
|
|||
|
is originally created.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* Backing pixmap for drawing area */
|
|||
|
static GdkPixmap *pixmap = NULL;
|
|||
|
|
|||
|
/* Create a new backing pixmap of the appropriate size */
|
|||
|
static gint
|
|||
|
configure_event (GtkWidget *widget, GdkEventConfigure *event)
|
|||
|
{
|
|||
|
if (pixmap)
|
|||
|
{
|
|||
|
gdk_pixmap_destroy(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;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The call to <tt>gdk_draw_rectangle()</tt> clears the pixmap
|
|||
|
initially to white. We'll say more about that in a moment.
|
|||
|
|
|||
|
<p>
|
|||
|
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 event->area field of the exposure event):
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* Refill 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;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
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 calls in GTK's GDK library for drawing on
|
|||
|
<em>drawables</em>. 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 calls above,
|
|||
|
<tt>gdk_draw_rectangle()</tt> and <tt>gdk_draw_pixmap()</tt>. The
|
|||
|
complete list is:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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 ()
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
See the reference documentation or the header file
|
|||
|
<tt><gdk/gdk.h></tt> for further details on these functions.
|
|||
|
These functions all share the same first two arguments. The first
|
|||
|
argument 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 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. (Which can be modified in a gtkrc file, see
|
|||
|
the section GTK's rc file.) This, among other things, stores a number
|
|||
|
of graphics contexts. Some examples of accessing these graphics
|
|||
|
contexts are:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
widget->style->white_gc
|
|||
|
widget->style->black_gc
|
|||
|
widget->style->fg_gc[GTK_STATE_NORMAL]
|
|||
|
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
The fields <tt>fg_gc</tt>, <tt>bg_gc</tt>, <tt>dark_gc</tt>, and
|
|||
|
<tt>light_gc</tt> are indexed by a parameter of type
|
|||
|
<tt>GtkStateType</tt> which can take on the values:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GTK_STATE_NORMAL,
|
|||
|
GTK_STATE_ACTIVE,
|
|||
|
GTK_STATE_PRELIGHT,
|
|||
|
GTK_STATE_SELECTED,
|
|||
|
GTK_STATE_INSENSITIVE
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
For instance, the for <tt/GTK_STATE_SELECTED/ the default foreground
|
|||
|
color is white and the default background color, dark blue.
|
|||
|
|
|||
|
<p>
|
|||
|
Our function <tt>draw_brush()</tt>, which does the actual drawing
|
|||
|
on the screen, is then:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* Draw a rectangle on the screen */
|
|||
|
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);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
After we draw the rectangle representing the brush onto the pixmap,
|
|||
|
we call the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gtk_widget_draw (GtkWidget *widget,
|
|||
|
GdkRectangle *area);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
which notifies X that the area given by the <tt>area</tt> parameter
|
|||
|
needs to be updated. X will eventually generate an expose event
|
|||
|
(possibly combining the areas passed in several calls to
|
|||
|
<tt>gtk_widget_draw()</tt>) which will cause our expose event handler
|
|||
|
to copy the relevant portions to the screen.
|
|||
|
|
|||
|
<p>
|
|||
|
We have now covered the entire drawing program except for a few
|
|||
|
mundane details like creating the main window. The complete
|
|||
|
source code is available from the location from which you got
|
|||
|
this tutorial, or from:
|
|||
|
|
|||
|
<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"
|
|||
|
name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial">
|
|||
|
|
|||
|
|
|||
|
<!-- ----------------------------------------------------------------- -->
|
|||
|
<sect1> Adding XInput support
|
|||
|
|
|||
|
<p>
|
|||
|
|
|||
|
It is now possible to buy quite inexpensive input devices such
|
|||
|
as drawing tablets, which allow drawing with a much greater
|
|||
|
ease of artistic expression than does a mouse. The simplest way
|
|||
|
to use such devices is simply as a replacement for the mouse,
|
|||
|
but that misses out many of the advantages of these devices,
|
|||
|
such as:
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item> Pressure sensitivity
|
|||
|
<item> Tilt reporting
|
|||
|
<item> Sub-pixel positioning
|
|||
|
<item> Multiple inputs (for example, a stylus with a point and eraser)
|
|||
|
</itemize>
|
|||
|
|
|||
|
For information about the XInput extension, see the <htmlurl
|
|||
|
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
|
|||
|
name="XInput-HOWTO">.
|
|||
|
|
|||
|
<p>
|
|||
|
If we examine the full definition of, for example, the GdkEventMotion
|
|||
|
structure, we see that it has fields to support extended device
|
|||
|
information.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tt/pressure/ gives the pressure as a floating point number between
|
|||
|
0 and 1. <tt/xtilt/ and <tt/ytilt/ can take on values between
|
|||
|
-1 and 1, corresponding to the degree of tilt in each direction.
|
|||
|
<tt/source/ and <tt/deviceid/ specify the device for which the
|
|||
|
event occurred in two different ways. <tt/source/ gives some simple
|
|||
|
information about the type of device. It can take the enumeration
|
|||
|
values.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GDK_SOURCE_MOUSE
|
|||
|
GDK_SOURCE_PEN
|
|||
|
GDK_SOURCE_ERASER
|
|||
|
GDK_SOURCE_CURSOR
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<tt/deviceid/ specifies a unique numeric ID for the device. This can
|
|||
|
be used to find out further information about the device using the
|
|||
|
<tt/gdk_input_list_devices()/ call (see below). The special value
|
|||
|
<tt/GDK_CORE_POINTER/ is used for the core pointer device. (Usually
|
|||
|
the mouse.)
|
|||
|
|
|||
|
<sect2> Enabling extended device information
|
|||
|
|
|||
|
<p>
|
|||
|
To let GTK know about our interest in the extended device information,
|
|||
|
we merely have to add a single line to our program:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
By giving the value <tt/GDK_EXTENSION_EVENTS_CURSOR/ we say that
|
|||
|
we are interested in extension events, but only if we don't have
|
|||
|
to draw our own cursor. See the section <ref
|
|||
|
id="sec_Further_Sophistications" name="Further Sophistications"> below
|
|||
|
for more information about drawing the cursor. We could also
|
|||
|
give the values <tt/GDK_EXTENSION_EVENTS_ALL/ if we were willing
|
|||
|
to draw our own cursor, or <tt/GDK_EXTENSION_EVENTS_NONE/ to revert
|
|||
|
back to the default condition.
|
|||
|
|
|||
|
<p>
|
|||
|
This is not completely the end of the story however. By default,
|
|||
|
no extension devices are enabled. We need a mechanism to allow
|
|||
|
users to enable and configure their extension devices. GTK provides
|
|||
|
the InputDialog widget to automate this process. The following
|
|||
|
procedure manages an InputDialog widget. It creates the dialog if
|
|||
|
it isn't present, and raises it to the top otherwise.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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);
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
(You might want to take note of the way we handle this dialog. By
|
|||
|
connecting to the "destroy" signal, we make sure that we don't keep a
|
|||
|
pointer to dialog around after it is destroyed - that could lead to a
|
|||
|
segfault.)
|
|||
|
|
|||
|
<p>
|
|||
|
The InputDialog has two buttons "Close" and "Save", which by default
|
|||
|
have no actions assigned to them. In the above function we make
|
|||
|
"Close" hide the dialog, hide the "Save" button, since we don't
|
|||
|
implement saving of XInput options in this program.
|
|||
|
|
|||
|
<sect2> Using extended device information
|
|||
|
|
|||
|
<p>
|
|||
|
Once we've enabled the device, we can just use the extended
|
|||
|
device information in the extra fields of the event structures.
|
|||
|
In fact, it is always safe to use this information since these
|
|||
|
fields will have reasonable default values even when extended
|
|||
|
events are not enabled.
|
|||
|
|
|||
|
<p>
|
|||
|
Once change we do have to make is to call
|
|||
|
<tt/gdk_input_window_get_pointer()/ instead of
|
|||
|
<tt/gdk_window_get_pointer/. This is necessary because
|
|||
|
<tt/gdk_window_get_pointer/ doesn't return the extended device
|
|||
|
information.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
void gdk_input_window_get_pointer (GdkWindow *window,
|
|||
|
guint32 deviceid,
|
|||
|
gdouble *x,
|
|||
|
gdouble *y,
|
|||
|
gdouble *pressure,
|
|||
|
gdouble *xtilt,
|
|||
|
gdouble *ytilt,
|
|||
|
GdkModifierType *mask);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
When calling this function, we need to specify the device ID as
|
|||
|
well as the window. Usually, we'll get the device ID from the
|
|||
|
<tt/deviceid/ field of an event structure. Again, this function
|
|||
|
will return reasonable values when extension events are not
|
|||
|
enabled. (In this case, <tt/event->deviceid/ will have the value
|
|||
|
<tt/GDK_CORE_POINTER/).
|
|||
|
|
|||
|
So the basic structure of our button-press and motion event handlers,
|
|||
|
doesn't change much - we just need to add code to deal with the
|
|||
|
extended information.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
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;
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
We also need to do something with the new information. Our new
|
|||
|
<tt/draw_brush()/ function draws with a different color for
|
|||
|
each <tt/event->source/ and changes the brush size depending
|
|||
|
on the pressure.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
/* Draw a rectangle on the screen, size depending on pressure,
|
|||
|
and color on the type of device */
|
|||
|
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);
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
<sect2> Finding out more about a device
|
|||
|
|
|||
|
<p>
|
|||
|
As an example of how to find out more about a device, our program
|
|||
|
will print the name of the device that generates each button
|
|||
|
press. To find out the name of a device, we call the function:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
GList *gdk_input_list_devices (void);
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
which returns a GList (a linked list type from the glib library)
|
|||
|
of GdkDeviceInfo structures. The GdkDeviceInfo strucure is defined
|
|||
|
as:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
struct _GdkDeviceInfo
|
|||
|
{
|
|||
|
guint32 deviceid;
|
|||
|
gchar *name;
|
|||
|
GdkInputSource source;
|
|||
|
GdkInputMode mode;
|
|||
|
gint has_cursor;
|
|||
|
gint num_axes;
|
|||
|
GdkAxisUse *axes;
|
|||
|
gint num_keys;
|
|||
|
GdkDeviceKey *keys;
|
|||
|
};
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
Most of these fields are configuration information that you
|
|||
|
can ignore unless you are implemented XInput configuration
|
|||
|
saving. The we are interested in here is <tt/name/ which is
|
|||
|
simply the name that X assigns to the device. The other field
|
|||
|
that isn't configuration information is <tt/has_cursor/. If
|
|||
|
<tt/has_cursor/ is false, then we we need to draw our own
|
|||
|
cursor. But since we've specified <tt/GDK_EXTENSION_EVENTS_CURSOR/,
|
|||
|
we don't have to worry about this.
|
|||
|
|
|||
|
<p>
|
|||
|
Our <tt/print_button_press()/ function simply iterates through
|
|||
|
the returned list until it finds a match, then prints out
|
|||
|
the name of the device.
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
static void
|
|||
|
print_button_press (guint32 deviceid)
|
|||
|
{
|
|||
|
GList *tmp_list;
|
|||
|
|
|||
|
/* gdk_input_list_devices returns an internal list, so we shouldn't
|
|||
|
free it afterwards */
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
That completes the changes to ``XInputize'' our program. As with
|
|||
|
the first version, the complete source is available at the location
|
|||
|
from which you got this tutorial, or from:
|
|||
|
|
|||
|
<htmlurl url="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial"
|
|||
|
name="http://www.msc.cornell.edu/~otaylor/gtk-gimp/tutorial">
|
|||
|
|
|||
|
|
|||
|
<sect2> Further sophistications <label id="sec_Further_Sophistications">
|
|||
|
|
|||
|
<p>
|
|||
|
Although our program now supports XInput quite well, it lacks some
|
|||
|
features we would want in a full-featured application. First, the user
|
|||
|
probably doesn't want to have to configure their device each time they
|
|||
|
run the program, so we should allow them to save the device
|
|||
|
configuration. This is done by iterating through the return of
|
|||
|
<tt/gdk_input_list_devices()/ and writing out the configuration to a
|
|||
|
file.
|
|||
|
|
|||
|
<p>
|
|||
|
To restore the state next time the program is run, GDK provides
|
|||
|
functions to change device configuration:
|
|||
|
|
|||
|
<tscreen><verb>
|
|||
|
gdk_input_set_extension_events()
|
|||
|
gdk_input_set_source()
|
|||
|
gdk_input_set_mode()
|
|||
|
gdk_input_set_axes()
|
|||
|
gdk_input_set_key()
|
|||
|
</verb></tscreen>
|
|||
|
|
|||
|
(The list returned from <tt/gdk_input_list_devices()/ should not be
|
|||
|
modified directly.) An example of doing this can be found in the
|
|||
|
drawing program gsumi. (Available from <htmlurl
|
|||
|
url="http://www.msc.cornell.edu/~otaylor/gsumi/"
|
|||
|
name="http://www.msc.cornell.edu/~otaylor/gsumi/">) Eventually, it
|
|||
|
would be nice to have a standard way of doing this for all
|
|||
|
applications. This probably belongs at a slightly higher level than
|
|||
|
GTK, perhaps in the GNOME library.
|
|||
|
|
|||
|
<p>
|
|||
|
Another major ommission that we have mentioned above is the lack of
|
|||
|
cursor drawing. Platforms other than XFree86 currently do not allow
|
|||
|
simultaneously using a device as both the core pointer and directly by
|
|||
|
an application. See the <url
|
|||
|
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
|
|||
|
name="XInput-HOWTO"> for more information about this. This means that
|
|||
|
applications that want to support the widest audience need to draw
|
|||
|
their own cursor.
|
|||
|
|
|||
|
<p>
|
|||
|
An application that draws it's own cursor needs to do two things:
|
|||
|
determine if the current device needs a cursor drawn or not, and
|
|||
|
determine if the current device is in proximity. (If the current
|
|||
|
device is a drawing tablet, it's a nice touch to make the cursor
|
|||
|
disappear when the stylus is lifted from the tablet. When the
|
|||
|
device is touching the stylus, that is called "in proximity.")
|
|||
|
The first is done by searching the device list, as we did
|
|||
|
to find out the device name. The second is achieved by selecting
|
|||
|
"proximity_out" events. An example of drawing one's own cursor is
|
|||
|
found in the 'testinput' program found in the GTK distribution.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Tips For Writing GTK Applications
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
This section is simply a gathering of wisdom, general style guidelines and hints to
|
|||
|
creating good GTK applications. It is totally useless right now cause it's
|
|||
|
only a topic sentence :)
|
|||
|
|
|||
|
Use GNU autoconf and automake! They are your friends :) I am planning to
|
|||
|
make a quick intro on them here.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Contributing
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
This document, like so much other great software out there, was created for
|
|||
|
free by volunteers. If you are at all knowledgeable about any aspect of GTK
|
|||
|
that does not already have documentation, please consider contributing to
|
|||
|
this document.
|
|||
|
<p>
|
|||
|
If you do decide to contribute, please mail your text to Tony Gale,
|
|||
|
<tt><htmlurl url="mailto:gale@gimp.org"
|
|||
|
name="gale@gimp.org"></tt>. Also, be aware that the entirety of this
|
|||
|
document is free, and any addition by yourself must also be free. That is,
|
|||
|
people may use any portion of your examples in their programs, and copies
|
|||
|
of this document may be distributed at will etc.
|
|||
|
<p>
|
|||
|
Thank you.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect>Credits
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<p>
|
|||
|
I would like to thank the following for their contributions to this text.
|
|||
|
|
|||
|
<itemize>
|
|||
|
<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
|
|||
|
name="chamele0n@geocities.com"></tt> for the menus tutorial.
|
|||
|
|
|||
|
<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
|
|||
|
name="raph@acm.org"></tt>
|
|||
|
for hello world ala GTK, widget packing, and general all around wisdom.
|
|||
|
He's also generously donated a home for this tutorial.
|
|||
|
|
|||
|
<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
|
|||
|
name="petm@xcf.berkeley.edu"></tt> for the simplest GTK program..
|
|||
|
and the ability to make it :)
|
|||
|
|
|||
|
<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
|
|||
|
name="werner.koch@guug.de"></tt> for converting the original plain text to
|
|||
|
SGML, and the widget class hierarchy.
|
|||
|
|
|||
|
<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
|
|||
|
name="crichton@expert.cc.purdue.edu"></tt> for the menu factory code, and
|
|||
|
the table packing tutorial.
|
|||
|
|
|||
|
<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
|
|||
|
name="owt1@cornell.edu"></tt> for the EventBox widget section (and
|
|||
|
the patch to the distro). He's also responsible for the selections code and
|
|||
|
tutorial, as well as the sections on writing your own GTK widgets, and the
|
|||
|
example application. Thanks a lot Owen for all you help!
|
|||
|
|
|||
|
<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
|
|||
|
name="mvboom42@calvin.edu"></tt> for his wonderful work on the Notebook,
|
|||
|
Progress Bar, Dialogs, and File selection widgets. Thanks a lot Mark!
|
|||
|
You've been a great help.
|
|||
|
|
|||
|
<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
|
|||
|
name="timj@psynet.net"></tt> for his great job on the Lists Widget.
|
|||
|
Thanks Tim :)
|
|||
|
|
|||
|
<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
|
|||
|
name="rajat@ix.netcom.com"</tt> for the excellent job on the Pixmap tutorial.
|
|||
|
|
|||
|
<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
|
|||
|
name="johnsonm@redhat.com"></tt> for info and code for popup menus.
|
|||
|
|
|||
|
</itemize>
|
|||
|
<p>
|
|||
|
And to all of you who commented and helped refine this document.
|
|||
|
<p>
|
|||
|
Thanks.
|
|||
|
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
<sect> Tutorial Copyright and Permissions Notice
|
|||
|
<!-- ***************************************************************** -->
|
|||
|
|
|||
|
<p>
|
|||
|
The GTK Tutorial is Copyright (C) 1997 Ian Main.
|
|||
|
|
|||
|
Copyright (C) 1998 Tony Gale.
|
|||
|
<p>
|
|||
|
Permission is granted to make and distribute verbatim copies of this
|
|||
|
manual provided the copyright notice and this permission notice are
|
|||
|
preserved on all copies.
|
|||
|
<P>Permission is granted to copy and distribute modified versions of
|
|||
|
this document under the conditions for verbatim copying, provided that
|
|||
|
this copyright notice is included exactly as in the original,
|
|||
|
and that the entire resulting derived work is distributed under
|
|||
|
the terms of a permission notice identical to this one.
|
|||
|
<P>Permission is granted to copy and distribute translations of this
|
|||
|
document into another language, under the above conditions for modified
|
|||
|
versions.
|
|||
|
<P>If you are intending to incorporate this document into a published
|
|||
|
work, please contact the maintainer, and we will make an effort
|
|||
|
to ensure that you have the most up to date information available.
|
|||
|
<P>There is no guarentee that this document lives up to its intended
|
|||
|
purpose. This is simply provided as a free resource. As such,
|
|||
|
the authors and maintainers of the information provided within can
|
|||
|
not make any guarentee that the information is even accurate.
|
|||
|
</article>
|