[Previous] [Contents] [Next]

19. Managing Selections

19.1 Overview

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.h 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.


19.2 Retrieving the Selection

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 ) : gboolean;

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;
                          _type : TGdkAtom;
                          format : gint;
                          data : pguchar;
                          length : gint;
                       end;

selection and target are the values you gave in your gtk_selection_convert() call. _type 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 glib2, gtk2, gdk2;

{ Signal handler invoked when user clicks on the "Get Targets" button }

var
   targets_atom : TGdkAtom;

procedure get_targets (widget : PGtkWidget; data : gpointer); cdecl;
begin
   { Get the atom corresponding to the string "TARGETS" }
   if targets_atom = GDK_NONE then
      targets_atom := gdk_atom_intern('TARGETS', false);
   
   { And request the "TARGETS" target for the primary selection }
   gtk_selection_convert(GTK_WIDGET(data), GDK_SELECTION_PRIMARY, targets_atom,
                         GDK_CURRENT_TIME);
end;

{ Signal handler called when the selections owner returns the data }

procedure selection_received (widget : PGtkWidget;
			      selection_data : PGtkSelectionData;
                              data : gpointer); cdecl;
var
   atoms	   : PGdkAtom;
   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 := pGdkAtom(selection_data^.data);

      num_elements := trunc((selection_data^.length) / sizeof(TGdkAtom));
      for i := 0 to num_elements - 1 do begin
         name := gdk_atom_name(atoms[i]);
         if name <> nil then
	    writeln(name)
	 else
	    writeln('(bad atom)');
      end;
   end;
end;

var
   window, button : PGtkWidget;

begin
   gtk_init(@argc, @argv);
   targets_atom := GDK_NONE;
   
   { Create the toplevel window }
   window := gtk_window_new(GTK_WINDOW_TOPLEVEL);
   gtk_window_set_title(GTK_WINDOW(window), 'Event Box');
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@gtk_main_quit), 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);
   
   g_signal_connect(button, 'clicked',
                    G_CALLBACK(@get_targets), window);
   g_signal_connect(window, 'selection_received',
                    G_CALLBACK(@selection_received), nil);

   gtk_widget_show(button);
   gtk_widget_show(window);
   
   gtk_main();
end.

19.3 Supplying the Selection

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;
                                  _type : 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.

program SetSelection;

uses gtk2, gdk2, glib2, sysutils;

var
   selection_button, selection_widget : PGtkWidget;

{ Callback when the user toggles the selection }

procedure selection_toggled (widget : PGtkWidget;
                             var have_selection : gboolean); cdecl;
begin
   if active(GTK_TOGGLE_BUTTON(widget)^) <> 0 then begin
      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 not have_selection then
	 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), false);
   end
   else
      if have_selection 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,
                                    GDK_CURRENT_TIME);
	 have_selection := false;
      end;
end;

{ Called when another application claims the selection }

function selection_clear (widget : PGtkWidget;
                          event : PGdkEventSelection;
                          var have_selection : gboolean) : gboolean; cdecl;
begin
   have_selection := false;
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), false);
   selection_clear := true;
end;

{ Supplies the current time as the selection. }

procedure selection_handle (widget         : PGtkWidget;
                            selection_data : PGtkSelectionData;
                            info           : guint;
                            time_stamp     : guint;
                            data           : gpointer); cdecl;
var
   timestr : pgchar;
   t	   : tDateTime; { from sysutils }
begin
   { time_t is a C structure, the rest of this callback would need to be translated
   appropriately if GdkAtom is ever implemented in Pascal /////// }
   { time_t current_time; }
   { current_time = time(NULL); }
   { timestr = asctime (localtime(¤t_time)); }
   t := now;
   timestr := pgchar(TimeToStr(t));
   { 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, pguchar(timestr), strlen(timestr));
end;

var
   window : PGtkWidget;
   have_selection : boolean = false;

begin
   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_set_border_width(GTK_CONTAINER (window), 10);
   
   g_signal_connect(window, 'destroy',
                    G_CALLBACK(@gtk_main_quit), nil);

   { Create a toggle button to act as the selection }
   selection_widget := gtk_invisible_new();
   selection_button := gtk_toggle_button_new_with_label('Claim Selection');
   gtk_container_add(GTK_CONTAINER(window), selection_button);
   gtk_widget_show(selection_button);

   g_signal_connect(selection_button, 'toggled',
                    G_CALLBACK(@selection_toggled), @have_selection);
   g_signal_connect(selection_widget, 'selection_clear_event',
                    G_CALLBACK(@selection_clear), @have_selection);

   gtk_selection_add_target(selection_widget,
                            GDK_SELECTION_PRIMARY,
                            GDK_SELECTION_TYPE_STRING,
                            1);
   g_signal_connect(selection_widget, 'selection_get',
                    G_CALLBACK(@selection_handle), @have_selection);

   gtk_widget_show(selection_button);
   gtk_widget_show(window);
   
   gtk_main();
end.

[Previous] [Contents] [Next]