New selections code (long, sorry)




I've just about finished my long-promised rewrite of the selection
handling code. I'm not quite ready to release the patches, but since
I'd like some comments on the interface, I'm providing a draft of 
a tutorial section on selections instead. I think it gives a pretty
clear idea of the interface.

The things that this rewrite adds are:

  * Support for selections other than the primary and secondary
     (needed for Motif DND)
  * The ability to supply other targets than the STRING one.
     (needed for ICCCM compliance (TARGETS target), for international
      applications (COMPOUND_TEXT), and for general completeness)
  * Support for a lot of intricacies of the ICCCM, in
     particular MULTIPLE requests and the INCR protocol

Right now, my code not only supports the INCR protocol for receiving
selections, but also uses it to send large selections. (For
non-experts, the INCR protocol is a rather complicated mechanism for
sending large selections by breaking them into pieces.) This is what
the ICCCM calls for, but I'm not completely sure its the best thing to
do. At least older versions of Tk do it this way, and in fact, can't
receive large selections atomically. However, emacs supports receiving
via the INCR protocol, but sends even large selections atomically,
presumably to support applications which have a simple implementation
of selections, like the older GTK implementation. I don't actually
know of any significant applications that can't receive via INCR, so I
think the way I'm doing things now is the right one, but it may take
some further testing to make sure.

The other thing that I have some doubts about is the use of a
callback-handler mechanism separate from signals to supply the
selection. I did this so I could have handlers for different types of
targets without creating an army of signals, and so that I could
ensure that only one handler was called for each request. Neither of
these is essential (just use one handler with a switch, and hope the
user is smart enough not to create multiple handlers), though it does
seem cleaner this way, but every callback interface does add another
complication when writing language bindings.

The callback handlers are expected to be able to return the selection
in pieces. This saves memory and avoids some problems with figuring
out who frees allocated memory, but does make things a bit more
difficult to write (not too bad, see the example below), and there is
a risk of providing inconsistent results if the selection changes
while the INCR protocol is in progress.

Any comments on the above issues (or on anything else) would be
appreciated. I'll probably release the patches in the next two or
three days once I get a chance to bang on them a bit more.

Regards,
                                        Owen


----

<sect>Managing Selections

<sect1> Overview

<p>

One type of interprocess communication supported by GTK is via
<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 selection on a display,
called the <em>owner</em> can have a particular selection at one time,
so when a selection is claimed by one application, the previous owner
must indicate 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 many 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>

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, 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;
  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>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>
#include <gtk/gtk.h>

void selection_received (GtkWidget *widget, 
			 GtkSelectionData *selection_data, 
			 gpointer data);

/* Signal handler 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 (&amp;argc, &amp;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,
				GdkAtom              type,
				gint                 format,
				GtkSelectionFunction function,
				GtkRemoveFunction    remove_func,
				gpointer             data);
</verb></tscreen>

<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
this handler will manage.  The <tt>type</tt> argument, as above,
indentifies the type of data that the handler returns. The
<tt>format</tt> argument gives the number of bits in the fundemental
units of the data returned, usually 8 or 32. <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 gint (*GtkSelectionFunction) (GtkWidget *widget, gint offset, 
				      guchar *buffer, gint length,
				      gint *bytes_after,
				      gpointer data);

</verb></tscreen>

When called, it writes a maximum of <tt/length/ bytes into the buffer,
starting at an offset of <tt/offset/ in the
selection. <tt/bytes_after/ should be set to indicate the number of
bytes after the portion returned that didn't fit into the buffer.
This allows the selection handling code to get the data in pieces, and
also to find out the total length of the selection (by calling the
callback with <tt/length/ 0).

<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
this 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>
#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. It's probably pretty safe to
   assume the asctime() always returns the same length string. If not,
   there's a tiny chance that our response will be truncated, since
   multiple calls to the handler may be made to supply one request. */
gint
selection_handle (GtkWidget *widget, gint offset, guchar *buffer,
		  gint length, gint *bytes_after, gpointer data)
{
  gchar *timestr;
  gint return_len;
  time_t current_time;

  current_time = time (NULL);
  timestr = asctime (localtime(&amp;current_time)); 
  /* When we return a single string, it should not be null terminated.
     That will be done for us */
  return_len = strlen(timestr) - offset;	

  *bytes_after = 0;

  if (return_len > length)
    {
      *bytes_after = return_len - length;
      return_len = length;
    }

  memcpy (buffer + offset, timestr, return_len);

  return return_len;
}

int
main (int argc, char *argv[])
{
  GtkWidget *window;

  GtkWidget *selection_button;

  static int have_selection = FALSE;
  
  gtk_init (&amp;argc, &amp;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 */

  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), &amp;have_selection);
  gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
		      GTK_SIGNAL_FUNC (selection_clear), &amp;have_selection);

  gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
			     GDK_SELECTION_TYPE_STRING,
			     GDK_SELECTION_TYPE_STRING, 8,
			     selection_handle, NULL, NULL);

  gtk_widget_show (selection_button);
  gtk_widget_show (window);
  
  gtk_main ();
  
  return 0;
}
</verb></tscreen>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]