The Main Event Loop
Table of Contents
- The GLIb Main Loop
- Main Contexts
- Event Sources
- Timeout Sources
- The Timeout Signal
- Idle Sources
- The Idle Signal
- Monitoring IO
- The IO Signal
- The GTK Main Loop
- Key Snooper
Functions
- The Quit Signal
- Example Main
Functions
The GLib Main Loop
The GLib main event loop is a low-level generic abstraction that
manages all the available sources of events for GLib and GTK+
applications. These events can come from any number of different types
of sources such as file descriptors (plain files, pipes or sockets) and
timeouts. New sources can be added using G::Source::attach().
GLib allows multiple independent sets of sources to be handled in
different
threads, each source is associated with a G::MainContext. A main
context
can only be running in a single thread, but sources can be added to it
and removed from it, from other threads.
Each event source is assigned a priority. The default priority,
G::PRIORITY_DEFAULT, has a value of 0. Values less than 0 denote higher
priorities.
Values greater than 0 denote lower priorities. Events from high
priority sources are always processed before events from lower priority
sources. Idle functions can also be added, and assigned a priority.
These will be run whenever no events with a higher priority are ready
to be processed.
A G::MainLoop represents a single invocation of GLib main event loop
and can be
created with one of the following constructors:
MainLoop();
MainLoop(G::MainContext& context);
The first constructor creates a main loop using the default
G::MainContext. The second constructor creates a main loop using the
specified G::MainContext.
After adding the initial event sources, run() is called to run the
event
loop:
void run();
Once run() is invoked the main loop will iterate until quit() is called:
void quit();
While the main loop is running, it continuously checks for new events
from each of the event sources and dispatches them. If one of the
events from one of the sources leads to a call to quit() to exit the
main loop, run() returns.
It is possible to create new instances of G::MainLoop recursively. This
is often used in GTK+ applications when showing modal dialog boxes.
Note that event sources are associated with a particular
G::MainContext,
and will be checked and dispatched for all main loops associated with
that context.
One of the unusual features of the GLib main loop functionality is that
new types of event source can be created and used in addition to the
built-in types of event source. A new source type is created by
deriving
it from G::CustomSource, but for most purposes the built-in event
sources
should suffice: G::ChildWatchSource, G::TimeoutSource, G::IdleSource
and G::IOSource.
Main Contexts
A G::MainContext is an object that represents a set of event sources to
be processed in a main loop. Different main contexts must be used for
different threads but sources can be added to a main context from any
thread.
A main context is created with the following constructor:
MainContext();
Applications that need only one context can use the default main
context instead, by calling the following function:
Pointer<MainContext> get_default();
A single iteration of a main context can be run by calling:
bool iteration(bool may_block);
If 'may_block' is set to true and no event sources are ready to be
processed, the context will wait for a source to become ready,
dispatching the highest priority events sources that are ready.
iteration() will return true if any events were dispatched.
If you need more control over exactly how the details of the main event
loop work, for instance, when integrating G::MainLoop with an
external main loop, you can call the component methods of iteration()
directly. These methods are prepare(), query(), check() and dispatch(),
and they must be called in that sequence. The sequence can be stopped
at any
time, and you can return to prepare(), but you must never skip a step
or call one of the methods twice (except for query()). Whenever
prepare() is invoked, any pending sources are cleared, and checking
starts again from scratch.
Event Sources
A G::Source object corresponds to a single source of input to be
processed by the main loop. GLib provides several built-in source
types, represented by the following XFC classes: ChildWatchSource,
TimeoutSource, IdleSource and IOSource. In most cases these built-in
source types should be adequate. When they aren't, or when you need
something more powerful, you can create a custom event source by
subclassing G::CustomSource.
When a source is created it isn't associated with any main context but
must be explicitly attached to a context by calling the G::Source
attach method:
unsigned int attach(MainContext *context
= 0);
If 'context' is null the source is attached to the default main
context; otherwise it's attached to the specified main context. The
return value is an integer that identifies the source within the
context.
If a source is attached to a specific main context,
G::Source::is_attached() returns true:
bool is_attached(G::MainContext&
context) const;
Event sources processed in the main loop are dispatched according to
their priority, which can be set and retrieved with the following two
methods respectively:
void set_priority(int priority);
int get_priority() const;
The 'priority' can be one of the following values:
- PRIORITY_HIGH - use this for high priority event sources;
It is not used within GLib or GTK+ (value -100).
- PRIORITY_DEFAULT - use this for default priority event
sources; In GLib this priority is used when adding timeout functions
(value 0).
- PRIORITY_HIGH_IDLE - use this for high priority idle
functions (value 100);
- PRIORITY_DEFAULT_IDLE - use this for default priority idle
functions; In GLib this priority is used when adding idle functions
(value 200).
- PRIORITY_LOW - use this for very low priority background
tasks; It is not used within GLib or GTK+ (value 300).
Negative values have a higher priority than positive ones, with the
most negative value having the highest the priority and the most
positive value having the lowest priority. While the main loop is being
run, a source will be dispatched if it is
ready to be dispatched and no sources with a higher priority are ready
to be dispatched.
Note, GTK+ uses PRIORITY_HIGH_IDLE + 10 for window resizes and
PRIORITY_HIGH_IDLE + 20 for window redraws, to ensure that any pending
resizes are processed before any pending redraws, so that widgets are
not redrawn twice unnecessarily; you will interfere with GTK+ if you
use a priority above PRIORITY_HIGH_IDLE + 10 (that is,
GTK_PRIORITY_RESIZE).
Event sources can be called recursively, that is, while being
dispatched a source wont be blocked until the dispatch returns, but
instead will be processed normally. You can set and retrieve the
'can_recurse' property with the following two methods respectively:
void set_can_recurse(bool can_recurse);
bool get_can_recurse() const;
As well as calling ref() and unref() on a source, sources can be
explicitly destroyed with the following method:
void destroy();
Calling destroy() removes a source from its main context, if any, and
marks it as destroyed. After calling this method the source cannot be
subsequently added to another context.
Timeout Sources
Adding a timeout source to a G::MainContext is a convenient way
to
you register a slot to be called at regular intervals, with a
given
priority. Once connected, the slot is called repeatedly until its
function
returns false, at
which point the timeout is automatically destroyed and will not be
called again. The first
call to the slot will be at the end of the first interval.
A timeout source can be created with one of the following constructors:
TimeoutSource(unsigned int interval);
TimeoutSource(const SourceSlot& slot, unsigned int interval);
The both constructors create a timeout source that can be associated
with any main context. The SlotType is a callback slot of type
sigc::slot<bool>. The 'interval' is the time between calls to the
callback slot, in milliseconds. G::Source::attach() must be called to
attach the source to a context.
If you do not pass a slot to the timeout source constructor, you can
connect a callback slot later by calling:
sigc::connection connect(const
SourceSlot& slot);
The Timeout Signal
For convenience, XFC provides a timeout signal that lets you connect a
callback slot to the default main context, without having to create a
timeout source. The timeout signal is declared in the G::
namespace like this:
class TimeoutSignal : public
sigc::trackable
{
public:
typedef sigc::slot<bool> SlotType;
sigc::connection connect(const SlotType& slot,
unsigned int interval, int priority = PRIORITY_DEFAULT_IDLE);
};
extern TimeoutSignal timeout_signal;
The interval argument passed to connect() is the time in milliseconds
that is to elapse
between each call. Your slot will be called every 'interval'
milliseconds until its function returns false, at which point the
timeout
connection is destroyed and will not be called again. The connect
method returns a sigc::connection class. Unlike widget
signals, you must keep a copy of this connection and specifically call
sigc::connection::disconnect() when the timeout is no longer required.
Function slots that connect to the timeout_signal have the following
prototype:
bool MyClass::signal_handler();
The code to set up a timeout connection should look something like
this. In your class interface include a sigc::connection and a timeout
signal
handler.
class MyClass : public Gtk::Window
{
sigc::connection timeout_connection;
protected:
bool on_timeout();
public:
MyClass();
~MyClass();
};
then in your class implementation connect your signal handler to the
timeout signal in the constructor and disconnect it in the
destructor :
MyClass::MyClass()
{
...
timeout_connection =
G::timeout_signal.connect(sigc::mem_fun(this,
&MyClass::on_timeout), 150);
}
MyClass::~MyClass()
{
timeout_connection.disconnect();
}
bool
MyClass::on_timeout()
{
// put your code here.
// return true to continue calling this
method; return false to end the timeout.
return true;
}
Note that timeout functions may be delayed,
due to the processing of other event sources, so they should not be
relied on
for precise timing. After each call to your timeout slot, the time of
the next
timeout is recalculated based on the current time and the given
interval (it does
not try to 'catch up' time lost in delays).
Idle Sources
What if you have a method which you want called when nothing else is
happening? That's what an IdleSource does. It's a source that is called
whenever there are no higher priority events pending.
An idle source can be created with one of the following constructors:
IdleSource();
IdleSource(const SourceSlot& slot);
Both constructors create an idle source that can be associated with any
main context. The SlotType is a callback slot of type
sigc::slot<bool>. G::Source::attach() must be called to attach
the new source to a context.
If you do not pass a slot to the idle source constructor, you can
connect a callback slot later by calling:
sigc::connection connect(const
SourceSlot& slot);
Once connected, the slot will continue to be called until
it returns false, at which point it is automatically removed from the
list of event sources and will not be called again.
The Idle Signal
For convenience, XFC provides an
idle signal that lets you connect a
callback slot to the default main context, without having to create an
idle source. The idle signal is declared in the G::
namespace like this:
class IdleSignal : public sigc::trackable
{
public:
typedef sigc::slot<bool> SlotType;
sigc::connection connect(const SlotType& slot,
int priority = PRIORITY_DEFAULT_IDLE);
};
extern IdleSignal idle_signal;
The SlotType is a callback slot of type
sigc::slot<bool>. The connect
method returns a sigc::connection class. Unlike widget
signals, you must keep a copy of this connection and specifically call
sigc::connection::disconnect() when idle processing is no longer
required. If a connected slot returns false it is automatically removed
from the list of event sources and will not be called again.
Function slots that connect to the idle_signal have the following
prototype:
bool MyClass::signal_handler();
If your using a different context, for example, inside a thread, you
should use an IdleSource object instead.
Monitoring IO
IOSource is an event source that's dispatched when a specific condition
is met for the given file descriptor channel. For example, if the
condition
is G::IO_IN, the source will be dispatched when there's data available
for reading.
An io source can be created with one of the following constructors:
IOSource(G::IOChannel& channel,
G::IOConditionField condition);
IOSource(G::IOChannel& channel, G::IOConditionField condition,
const IOSlot& slot);
Both constructors create an io source that can be associated with any
main context. The IOSlot is a callback slot of type sigc::slot<bool,
IOConditionField>. The 'condition' can be one or more of the
following values from the
G::IOCondition enum:
- IO_IN - there is data to read.
- IO_OUT - data can be written (without blocking).
- IO_PRI - there is urgent data to read.
- IO_ERR - an error condition.
- IO_HUP - hung up (the connection has been broken, usually
for pipes and sockets).
- IO_NVAL - invalid request. The file descriptor is not open.
G::Source::attach() must be called to attach the new source to a
context. If you do not pass a slot to the IOSource constructor, you can
connect a callback slot later by calling:
sigc::connection connect(const
IOSlot& slot);
Once connected, the slot will be called whenever the
requested condition on the G::IOChannel is satisfied. If the slot
returns false it is automatically removed from the list
of event sources and will not be called again.
The IO Signal
For convenience, XFC provides an io signal that lets you connect a
callback slot to the default main context, without having to create a
IOSource. The io signal is declared in the G::
namespace like this:
class IOSignal : public sigc::trackable
{
public:
typedef sigc::slot<bool, IOConditionField>
SlotType;
sigc::connection connect(G::IOChannel& channel,
G::IOConditionField condition, const SlotType& slot, int priority);
};
extern IOSignal io_signal;
The SlotType is a callback slot of type sigc::slot<bool,
IOConditionField>. The 'condition' is the condition to watch for and
the 'priority' is the priority of the io source. The connect
method returns a sigc::connection class. Unlike widget
signals, you must keep a copy of this connection and specifically call
sigc::connection::disconnect() when channel monitoring is no longer
required. If a connected slot returns false it is automatically removed
from the list of event sources and will not be called again.
Function slots that connect to the io_signal have the following
prototype:
bool MyClass::signal_handler(G:: IOConditionField );
If your using a different context, for example, inside a thread, you
should use an IOSource object instead.
The GTK Main Loop
The GTK+ main event loop is primarily implemented by GLib, which has a
generic
main loop abstraction. GTK+ attaches the GLib main loop to GDK's X
server connection, and
presents a convenient interface to the user. In XFC, the main loop and
its related functions are declared in the 'Main::' namespace.
Before using GTK+, you need to initialize it; initialization connects
to the window system display, and parses some standard command line
arguments from argc and argv.
void init(int *argc, char ***argv);
bool init_check(int *argc, char ***argv);
The init() functions initialize everything needed to operate the GTK+
toolkit. You should call init() before using any other XFC methods in
your GUI applications. If errors occur, init() will exit your
application. To avoid this, you can use init_check() which allows you
to recover from a failed GTK+
initialization. init_check() does the same work as init() except that
it does not terminate the application if the GUI can't be initialized.
Instead it returns false on failure. This way your application can fall
back to some other means of communication with the user - for example a
command line interface.
You can register a callback slot to be called when the main loop is
started:
void init_add(const
sigc::slot<bool>& callback);
Once invoked, the callback slot is removed from the list of slots to
call, and is not called again.
Before you use a thread related method, you must call this next method
to initialize the thread system:
void threads_init(GThreadFunctions
*vtable = 0);
The 'vtable' argument is a function table of type GThreadFunctions,
that provides the entry points for the thread system to use. Most of
the time the default functions are appropriate so you can just set
vtable to null. Main::threads_init() is a convenience function that
calls both G::Thread::init() and gdk_threads_init(). G::Thread::init()
initializes the GLib thread system and
gdk_threads_init() initializes GDK so that it can be used with multiple
threads. After calling threads_init() you can call Gdk::Mutex::lock()
and Gdk::Mutex::unlock() to lock and unlock critical sections of code.
To start an application you must call run(), which runs the GTK+
main loop until quit() is called:
void run();
void quit();
You can nest calls to run(). In that case quit() will make the
innermost invocation of the main loop return. All instances of the main
loop are functionally identical; they are all
watching the same connection to the X server and working from the same
event queue. Main loop instances are used to block, halting a
function's flow of control until some conditions are met. All GTK+
programs use this technique to keep main() from exiting while the
application is running. The Gtk::Dialog::run() method uses a recursive
main loop, so it doesn't return until the
user clicks a dialog button.
Sometimes you want to process a few events, without handing the flow of
control to main(). You can perform a single iteration of the main
loop by calling:
bool iterate(bool blocking = true);
Set 'blocking' to true if you want to block if no events are pending.
True is returned if quit() has been called for the innermost main loop.
A call to iterate() might process a single
event, for example; it depends on what tasks are pending. You can check
whether any events need to be processed by calling the events_pending()
predicate.
bool events_pending();
Together, iterate() and pending() allow you
to temporarily return control to GTK+, so the GUI can "catch up." For
example, during a long computation, you will want to display a progress
bar; you must allow the GTK+ main loop to run periodically, so GTK+ can
redraw the progress bar. Use this code:
// computation going on
while (events_pending())
iterate();
// computation continued
When the user is doing nothing,
GTK+ sits in the main loop and waits
for input. If the user performs some action - say, a mouse click - then
the main loop "wakes up" and delivers an event to GTK+. GTK+ forwards
the event to one or more widgets. When widgets receive an event, they
frequently emit one or more
signals. Signals notify your program that 'something interesting
happened' by invoking the callback slots you've connected to the
signal. When your slot functions are invoked, you would typically take
some action -
for example, when an Open button is clicked you might display a
FileChooserDialog. After a callback finishes, GTK+ will return to
the main loop and await more user input.
Key Snooper Functions
Key snooper functions are called on all key events before delivering
them normally, so they can be used to implement custom key event
handling. The key_snooper_signal is declared in the Main:: namespace
like this:
class KeySnooperSignal : public
sigc::trackable
{
std::vector<Connection*> connection_list;
public:
typedef sigc::slot<bool, Gtk::Widget&, const
Gdk::EventKey&> SlotType;
~KeySnooperSignal();
sigc::connection connect(const SlotType& slot);
};
extern KeySnooperSignal key_snooper_signal;
The SlotType is a callback slot of type
sigc::slot<bool, Gtk::Widget&, const
Gdk::EventKey&>. The connection method returns a
sigc::connection class. Unlike widget
signals, you must keep a copy of this connection and specifically call
sigc::connection::disconnect() when you no longer wish to snoop key
events.
Functions connected to the key_snooper_signal have
the following prototype:
bool
MyClass::signal_handler(Gtk::Widget& widget, const
Gdk::EventKey& event);
The 'widget' argument is the widget to which the event will be
delivered.
The 'event' argument is the key event. It is the snooper's
responsibility
to pass the key event on to the widget, but care must be taken not to
pass it twice. Return true to stop further snooping. Return false
to continue.
The Quit Signal
You can connect a function to the 'quit_signal' to be called when an
instance of the main loop is left. The quit_signal is declared in the
Main:: namespace
like this:
class QuitSignal : public sigc::trackable
{
public:
typedef sigc::slot<bool> SlotType;
sigc::connection connect(const SlotType& slot,
unsigned int main_level = 0);
};
extern QuitSignal quit_signal;
The SlotType is a callback slot of type sigc::slot<bool> and
'main_level' is the level at which the termination slot shall be called. The connection method
returns a sigc::connection class. You can keep a copy of this
connection and specifically call sigc::connection::disconnect().
Example Main Functions
The main function is the entry point for a C/C++ application. In XFC
the main function is responsible for initializing the GTK+ toolkit and
running the main loop. For most uses your main function wont need
to do much more than this.
For single threaded applications your main function should look like
this:
int main (int argc, char *argv[])
{
using namespace Main;
// init GTK+
init(&argc, &argv);
// create a window
Window window;
window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
window.show()
// enter the main loop
run();
return 0;
}
and for multi-threaded applications it should look something like this:
int main (int argc, char *argv[])
{
using namespace Main;
// init thread support
threads_init();
// init GTK+
init(&argc, &argv);
// create a window
Window window;
window.sig_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
window.show()
// enter the main loop
Gdk::Mutex::lock();
run();
Gdk::Mutex::unlock();
return 0;
}
Copyright
© 2004-2005 The XFC
Development Team |
Top
|
XFC
4.4
|
|