You may be wondering how to make GTK-- do useful work while it's
idling along (well, sleeping actually) in Gtk::Main()
. Happily,
you have several options. Using the following functions you can create
a timeout function that will be called every few milliseconds.
Connection Gtk::Main::timeout.connect(const SlotType &sd, guint32 interval);
The first argument is a slot you wish to have called when the timeout
occurs. The second argument is the number of milliseconds between
calls to your function. You get back a Connection
object that can be
used to destroy the connection. Use
MyConnection.disconnect();
to destroy the connection. Another way of destroying the Connection
is your callback function. It has to be of the type
Slot0<gint>
. As you see from the definition your
callback function has to return a value of the type gint
. A
definition of a sample function might look like this:
gint MyCallback() { cout << "Hello World!"; return true; }
You can stop the timeout function by returning zero or false
from
your callback function. Therefore, if you want your
function to be called repeatedly, it should return a non-zero value,
or true
.
Here's an example of this technique:
Source location: examples/timeout/timeout.cc
#include <iostream>
#include <gtk--/button.h>
#include <gtk--/box.h>
#include <gtk--/main.h>
#include <gtk--/window.h>
#include <map>
class TimerExample : public Gtk::Window
{
// the usual stuff - nothing exciting
Gtk::HBox m_box;
Gtk::Button m_add_timer, m_del_timer, m_quit;
gint m_t_nr;
// the start value for our timer
static const gint COUNT_VALUE;
// the timeout value for the timers in [ms]
static const gint TIMEOUT_VALUE;
// we need this to store our connections
map<gint,Gtk::Connection> m_timers;
// this is for storing our timer values
// each timer countsa back from COUNT_VALUE to 0 and
// if removed when it reaches 0
map<gint,gint> m_counters;
public:
TimerExample();
// the callback functions for add & remove button
void add_timer_pressed();
void del_timer_pressed();
// the callback for the timer
// note that is not of the type gint callback(void)
// since we use bind() to add a data value of type gint to it
gint timer_callback(gint timer_nr);
// this will end the application when its window is closed
gint delete_event_impl(GdkEventAny*);
};
const gint TimerExample::COUNT_VALUE = 5;
const gint TimerExample::TIMEOUT_VALUE = 1500;
TimerExample::TimerExample() :
m_add_timer("add a new timer"),
m_del_timer("remove timer"),
m_quit("Quit"),
m_box(true,10),
m_t_nr(0)
{
// connect the callbacks
m_quit.pressed.connect(Gtk::Main::quit.slot());
m_add_timer.pressed.connect(slot(this,&TimerExample::add_timer_pressed));
m_del_timer.pressed.connect(slot(this,&TimerExample::del_timer_pressed));
// put buttons into container
m_box.pack_start(m_add_timer);
m_box.pack_start(m_del_timer);
m_box.pack_start(m_quit);
// set border and display all
set_border_width(10);
add(m_box);
show_all();
}
void TimerExample::add_timer_pressed()
{
// creation of a new object prevents long lines and
// shows us a little how slots work
// we have 0 parameters and gint as return value after calling bind
SigC::Slot0<gint> my_slot = bind(slot(this,&TimerExample::timer_callback),m_t_nr);
// now connect the slot to Gtk::Main::timeout
Gtk::Connection conn = Gtk::Main::timeout.connect(my_slot,TIMEOUT_VALUE);
// memorize connection
m_timers[m_t_nr] = conn;
// initialize timer count
m_counters[m_t_nr] = COUNT_VALUE + 1;
// print some information on the console
cout << "added timeout " << m_t_nr++ << endl;
}
void TimerExample::del_timer_pressed()
{
// are there any timers ?
if(m_timers.empty()) {
// nope
cout << "sorry, there are no timers left" << endl;
} else {
// get the nr of the first timer
gint timer_nr = m_timers.begin()->first;
// give a little information to the user
cout << "removing timer " << timer_nr << endl;
// delete the entry in the counter values
m_counters.erase(timer_nr);
// destroy the connection !!!!!
// this is important since the connection is NOT destroyed when
// the according Connection-Object is deleted
// The purpose of the connection object is to give you the
// possibility to destroy a connection without having to destroy
// either the sender or the receiver
// Try it and comment out the following line ....
m_timers[timer_nr].disconnect();
// destroy the connection
m_timers.erase(timer_nr);
}
}
gint TimerExample::timer_callback(gint timer_nr)
{
// print the timernr
cout << "This is timer " << timer_nr;
// decrement & check counter value
if(--m_counters[timer_nr] == 0) {
cout << " boom" << endl;
// delete the counter entry
m_counters.erase(timer_nr);
// delete the connection entry
m_timers.erase(timer_nr);
// note that we do not need to call disconnect on the connection
// since we Gtk::Main does this for us when we return 0
return 0;
}
// print the timer value
cout << " - " << m_counters[timer_nr] << "/" << COUNT_VALUE << endl;
return 1;
}
// the intresting stuff ends here
gint TimerExample::delete_event_impl(GdkEventAny*)
{
Gtk::Main::quit(); return 0;
}
int main (int argc, char *argv[])
{
Gtk::Main app(argc, argv);
TimerExample example;
app.run();
return 0;
}
A nifty feature of GDK (one of the libraries that underlying GTK--) is the ability to have it check for data on a file descriptor for you. This is especially useful for networking applications. The following function is used to do this:
Connection Gtk::Main::input.connect(const SlotType& sd, gint source,
GdkInputCondition condition);
The first argument is a slot you wish to have called when then the
specified event (see argument 3) occurs on the file descriptor you
specify using argument two. Argument three may be one or a combination
(using |
) of:
The return value is a Connection that may be used to stop monitoring
this file descriptor using the disconnect
following function.
The callback function should be declared as follows:
void input_callback(gint source, GdkInputCondition condition);
where source
and condition
are as specified above. As usual
the slot is created with slot()
and can be a member function
of an object.
A little (and somewhat dirty) example follows as usual. To use
the example just execute it from a terminal; it doesn't create a
window. It will create a pipe named testpipe
in the current
directory. Then start another shell and execute cat
>testpipe
. The example will print each line you enter until you
type quit
.
Source location: examples/input/input.cc
#include <gtk--/main.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fstream>
#include <iostream>
#include <memory>
using std::istream;
using std::auto_ptr;
using SigC::slot;
auto_ptr<istream> input;
// this will be our callback for read operations
// there is not much to say. just read a string,
// print it and quit the application if the string was quit
void MyCallback(int, GdkInputCondition) {
Gtk::string dummy;
do {
(*input) >> dummy;
cout << dummy << endl;
if(dummy == "quit") Gtk::Main::quit();
} while(input->fail());
}
int main (int argc, char *argv[])
{
// the usual Gtk::Main object
Gtk::Main app(argc, argv);
// create a fifo for testing purposes
if (mkfifo("testfifo",0666) != 0) {
cerr << "error creating fifo" << endl;
return -1;
}
// open the fifo
input=new ifstream("testfifo");
// int fd = open("testfifo", 0);
// if (fd == -1) {
// cerr << "error opening fifo" << endl;
// return -1;
// }
// assign the fifo's filedescriptor to our ifstream object
//This sucks; it will only ever work with libstdc++-v3, as
// both istream::__filebuf_type and the basic_filebuf contructor
// that takes an fd are libstdc++-v3 specific.
//input=new istream(new ifstream::__filebuf_type(fd,"testfifo"));
// connect the callback function
app.input.connect(slot(MyCallback), fd, GDK_INPUT_READ);
// and last but not least - run the application main loop
app.run();
// now remove the temporary fifo
if(unlink("testfifo"))
cerr << "error removing fifo" << endl;
return 0;
}
What if you have a function you want called when nothing else is happening? Hook it up using the following:
Connection Gtk::Main::idle.connect(Slot0<gint> idlefunc, gint priority);
This causes GTK-- to call the specified function whenever nothing else
is happening. You can add a priority (lower numbers are higher
priorities). If you don't supply a priority value, then
GTK_PRIORITY_DEFAULT will be used. There are two ways to remove the
callback: calling disconnect
on the Connection object, or
returning false
(or 0) in the callback function, which should be
declared as follows:
int idleFunc();
Since this is very similar to the functions above this explanation should be sufficient to understand what's going on. However, here's a little example:
Source location: examples/idle/idle.cc
#include <iostream>
#include <gtk--/adjustment.h>
#include <gtk--/button.h>
#include <gtk--/box.h>
#include <gtk--/label.h>
#include <gtk--/progressbar.h>
#include <gtk--/main.h>
#include <gtk--/window.h>
#include <map>
class IdleExample : public Gtk::Window
{
// the usual stuff - nothing exciting
Gtk::Button m_quit;
Gtk::Adjustment m_percentage_c;
Gtk::ProgressBar m_progressbar_c;
Gtk::Adjustment m_percentage_d;
Gtk::ProgressBar m_progressbar_d;
public:
IdleExample();
// a timer-function
gint timer_callback();
// a idle-function
gint idle_callback();
gint delete_event_impl(GdkEventAny*);
};
IdleExample::IdleExample() :
m_quit("Quit"),
m_percentage_c(0,0,100,0.5),
m_progressbar_c(m_percentage_c),
m_percentage_d(0,0,5000,0.5),
m_progressbar_d(m_percentage_d)
{
// connect the callbacks
m_quit.pressed.connect(Gtk::Main::quit.slot());
// put buttons into container
Gtk::VBox *vbox = manage( new Gtk::VBox(false,5));
// adding a few widgets
vbox->pack_start(* manage(new Gtk::Label("Formatting windows drive C:")));
vbox->pack_start(* manage(new Gtk::Label("100 MB")));
vbox->pack_start(m_progressbar_c);
m_progressbar_c.set_show_text(true);
vbox->pack_start(* manage(new Gtk::Label("")));
vbox->pack_start(* manage(new Gtk::Label("Formatting windows drive D:")));
vbox->pack_start(* manage(new Gtk::Label("5000 MB")));
vbox->pack_start(m_progressbar_d);
m_progressbar_d.set_show_text(true);
Gtk::HBox *hbox = manage( new Gtk::HBox(false,10));
hbox->pack_start(m_quit, true, false);
vbox->pack_start(*hbox);
// set border and display all
set_border_width(5);
add(*vbox);
show_all();
// formattinf drive c in timeout callback ;-)
Gtk::Main::timeout.connect(slot(this,&IdleExample::timer_callback), 50);
// formatting drive d in idle callback ;-)
Gtk::Main::idle.connect(slot(this,&IdleExample::idle_callback));
}
// increase the progressbar's value and remove callback when done
gint IdleExample::timer_callback()
{
float value = m_percentage_c.get_value();
m_percentage_c.set_value(value + 0.5);
return value < 99.99;
}
// increase the progressbar's value and remove callback when done
// note the diffrence in speed and also the impact of system load
// try to increase system load and watch the drive d value
gint IdleExample::idle_callback()
{
float value = m_percentage_d.get_value();
m_percentage_d.set_value(value + 0.5);
return value < 4999.99;
}
gint IdleExample::delete_event_impl(GdkEventAny*)
{
Gtk::Main::quit(); return 0;
}
int main (int argc, char *argv[])
{
Gtk::Main app(argc, argv);
IdleExample example;
app.run();
return 0;
}
This example points out the difference of idle and timeout functions a little. If you need functions that are called periodically, and speed is not very important, then you want timeout functions. If you want functions that are called as often as possible (like calculating a fractal in background), then use idle functions.
Try executing the example and increasing the system load. The upper progress bar will increase steadily; the lower one will slow down.