[Previous] [Contents] [Next]
19. Managing Selections
One type of interprocess communication supported by X and GTK is selections. 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 (the owner) 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 targets. There can be any number
of selections, but most X applications only handle one, the primary selection.
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.
A fundamental concept needed to understand selection handling is that of the atom. 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 the gtk header file corresponding to these atoms. For instance the constant
GDK_PRIMARY_SELECTION corresponds to the string 'PRIMARY'. In other cases, you should use
the functions gdk_atom_intern(), to get the atom corresponding to a string,and
gdk_atom_name(), to get the name of an atom. Both selections and targets are identified by atoms.
[Previous] [Contents] [Next]
Retrieving the selection is an asynchronous process. To start the process, you call:
FUNCTION gtk_selection_convert( widget :
pGtkWidget ; selection : tGdkAtom ;
target : tGdkAtom ;
time : guint32 ) : gint;
This converts the selection into the form specified by target. If at 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 GDK_CURRENT_TIME.
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 GtkSelectionData structure, which is defined as:
TYPE
pGtkSelectionData = ^tGtkSelectionData;
tGtkSelectionData = RECORD
selection : tGdkAtom;
target : tGdkAtom;
thetype : tGdkAtom;
format : gint;
data : pguchar;
length : gint;
END;
selection and target are the values you gave in your gtk_selection_convert() call.
thetype 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. format gives
the length of the units (for instance characters) in bits. Usually, you don't care about this when receiving data.
data is a pointer to the returned data, and length gives the length of the returned data, in bytes. If
length 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 length; the extra byte will always be zero, so it isn't necessary to make a
copy of strings just to null terminate them.
In the following example, we retrieve the special target TARGETS, which is a list of all targets into which the
selection can be converted.
PROGRAM gettargets;
USES gtk, gdk, glib;
{ ------------------------------Global Variables------------------------- }
VAR
window, button : pGtkWidget;
targets_atom : tGdkAtom;
{ ---------------------------------selection_received------------------------ }
PROCEDURE selection_received( widget : pGtkWidget ;
selection_data : pGtkSelectionData ;
data : gpointer ); cdecl;
{ -- Signal handler called when the selections owner returns the data -- }
VAR
atoms : ptGdkAtom;
i, num_elements : Integer;
name : pchar;
BEGIN
{ **** IMPORTANT **** Check to see if retrieval succeeded }
IF (selection_data^.length < 0) THEN
writeln('Selection retrieval failed')
{ Make sure we got the data in the expected form }
ELSE IF (selection_data^.type <> GDK_SELECTION_TYPE_ATOM)
THEN
writeln('Selection --TARGETS-- was not returned as atoms!')
ELSE
BEGIN
{ Print out the atoms we received }
atoms := ptGdkAtom(selection_data^.data);
num_elements := trunc((selection_data^.length)/sizeof(tGdkAtom)) - 1;
FOR i := 0 TO num_elements
DO
BEGIN
name := gdk_atom_name(atoms[i]);
IF name <> NIL
THEN
writeln(name)
ELSE
writeln('(bad atom)');
END; { -- for loop -- }
END; { -- ELSE -- }
END;
{ -------------------------------------selection received-------------------------- }
{ -------------------------------------get_targets---------------------------- }
PROCEDURE get_targets( widget : pGtkWidget ;
data : gpointer ); cdecl;
{ Signal handler invoked when user clicks on the --Get Targets-- button }
BEGIN
{ Get the atom corresponding to the string --TARGETS-- }
IF targets_atom = 0 THEN
targets_atom := gdk_atom_intern('TARGETS', 0);
{ 0 = FALSE }
{ And request the --TARGETS-- target for the primary selection }
gtk_selection_convert(widget, GDK_SELECTION_PRIMARY, targets_atom, 0);
END;
{ --------------------------------get_targets----------------------------- }
{ ------------------------------Main Program------------------------------ }
BEGIN
gtk_init(@argc, @argv); { Initialise GTK }
targets_atom := 0;
window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
{ Create a new window }
gtk_window_set_title(GTK_WINDOW(window), 'Event Box');
gtk_signal_connect(GTK_OBJECT(window), 'destroy',
GTK_SIGNAL_FUNC(@gtk_exit), NIL);
{ 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), NIL);
gtk_signal_connect(GTK_OBJECT(button), 'selection_received',
GTK_SIGNAL_FUNC(@selection_received), NIL);
gtk_widget_show(button);
gtk_widget_show(window);
gtk_main();
END.
{ ----------------------------------Main Program------------------------------ }
[Previous] [Contents] [Next]
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:
PROCEDURE gtk_selection_add_target( widget :
pGtkWidget ; selection : tGdkAtom ;
target : tGdkAtom ;
info : guint );
widget, selection, and target identify the requests this handler will manage. When a request for a
selection is received, the selection_get signal will be called. info can be used as an enumerator to
identify the specific target within the callback function.
The callback function has the signature:
PROCEDURE selection_get( widget : pGtkWidget ;
selection_data : pGtkSelectionData ;
info guint ;
time : guint );
The GtkSelectionData is the same as above, but this time, we're responsible for filling in the fields type,
format, data, and length. (The 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 - i.e. a character
- or 32 - i.e. an integer.) This is done by calling the function:
PROCEDURE gtk_selection_data_set( selection_data :
pGtkSelectionData ;
thetype : tGdkAtom ;
format : gint ; data : pguchar ;
length : gint );
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 (record) by hand.)
When prompted by the user, you claim ownership of the selection by calling:
FUNCTION gtk_selection_owner_set( widget :
pGtkWidget ; selection : tGdkAtom ;
time : guint32 ) :
gint;
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.
You'll see from the screen-shot below that the only visible output is a depressed (but not sad) button. However,
if you were to open up a text editor and press Ctrl+V, the current time will be pasted into your document,
e.g. 11:50. Alternatively, if you have access to KDE's clipboard tool Klipper, right-click on its icon and you'll
see the time added to the selection list each minute. Handy!
PROGRAM setselection;
USES gtk, gdk, glib, sysutils;
{ -----------------------------selection_toggled----------------------------- }
PROCEDURE selection_toggled( widget : pGtkWidget ;
VAR have_selection : gint );
cdecl;
{ Callback when the user toggles the selection }
BEGIN
IF active(GTK_TOGGLE_BUTTON(widget)^) <> 0 THEN
BEGIN
have_selection := gtk_selection_owner_set(widget, GDK_SELECTION_PRIMARY, 0);
{ the 0 here = CURRENT_TIME }
{ if claiming the selection failed, we return the button to the out state }
IF have_selection = 0 THEN
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
FALSE);
END
ELSE
IF have_selection <> 0 THEN
BEGIN
{ Before clearing the selection by setting the owner to NIL,
we check if we are the actual owner }
IF
gdk_selection_owner_get(GDK_SELECTION_PRIMARY) =
widget^.window THEN
gtk_selection_owner_set(NIL,
GDK_SELECTION_PRIMARY, 0);
have_selection := 0; { i.e. FALSE }
END; { -- IF NOT have_selection -- }
END;
{ -----------------------------selection_toggled---------------------------- }
{ --------------------------------selection_clear------------------------- }
FUNCTION selection_clear( widget : pGtkWidget ;
event : pGdkEvent ;
VAR have_selection :
gint ) : gint; cdecl;
{ Called when another application claims the selection }
BEGIN
have_selection := 0; { i.e. FALSE }
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
FALSE);
selection_clear := 1; { i.e. TRUE }
END;
{ -----------------------------------selection_clear---------------------------- }
{ ------------------------------selection_handle----------------------------- }
PROCEDURE selection_handle( widget : pGtkWidget ;
selection_data : pGtkSelectionData ;
data : gpointer );
cdecl;
{ Supplies the current time as the selection. }
VAR
timestr : pgchar;
t : tDateTime; { from sysutils }
BEGIN
t := now;
timestr := pgchar(TimeToStr(t));
gtk_selection_data_set(selection_data, GDK_SELECTION_TYPE_STRING, 8,
timestr, strlen(timestr));
END;
{ -----------------------------selection_handle---------------------------- }
{ -------------------------------Global Variables---------------------------- }
VAR
window, selection_button : pGtkWidget;
have_selection : Integer;
{ ---------------------------------Main Program------------------------------ }
BEGIN
have_selection := 0; { i.e. FALSE }
gtk_init(@argc, @argv); { Initialise GTK }
window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
{ Create a new window }
gtk_window_set_title(GTK_WINDOW(window), 'Event Box');
gtk_signal_connect(GTK_OBJECT(window), 'destroy',
GTK_SIGNAL_FUNC(@gtk_exit),
NIL);
{ 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_target(selection_button, GDK_SELECTION_PRIMARY,
GDK_SELECTION_TYPE_STRING, 1);
gtk_signal_connect(GTK_OBJECT(selection_button),
'selection_get',
GTK_SIGNAL_FUNC(@selection_handle), @have_selection);
gtk_widget_show(selection_button);
gtk_widget_show(window);
gtk_main();
END.
{ --------------------------------------Main Program---------------------------------- }
[Previous] [Contents] [Next]