New selection code




I've put my new selection handling code at:

 http://www.msc.cornell.edu/~otaylor/gtk-gimp/patch-gtk-selection.1.gz

(At a mere 4000 lines, it seemed a bit long to include here)

This patch is meant to patch cleanly against gtk-970828 + Alex
Jaborska's big patch, but it shouldn't be to hard to get it to
work with other combinations. It does require automake, since it adds
gtkselection.c/.h

There are most likely some bugs in it, but it's performed stably for
me over the last week. If you aren't writing code dealing with
selections, then this patch won't make much of a difference for
you. (Pasting into a few applications will work better) If you are,
then you'll have to make some changes to your code, but things should
be easier to implement, and more featureful.

As well as selection handling changes (the new interface is described
fairly well in the tutorial segment I've included below), I've made a
few other additions and changes to GDK.

Change:

An actual_length output parameter has been added to gdk_property_get
so the caller can figure out how much data was returned.

Additions:

 GdkEventMask  gdk_window_get_events      (GdkWindow       *window);
 void          gdk_window_set_events      (GdkWindow       *window,
 	 				   GdkEventMask     event_mask);

These do the obvious thing - they get and change the event mask on
an existing window.

 GdkWindow *   gdk_window_foreign_new (guint32 id);

This creates a "foreign" window - that is, a GdkWindow corresponding
to a window in some other application.

  gchar*  gdk_atom_name       (GdkAtom atom);

This returns the name corresponding to a given atom, or NULL if atom
is invalid. (in malloc'ed memory, caller is responsible for
freeing).

There's a new test in testgtk.c for selection retrieval, plus an
entire new testselection.c that allows more comprehensive
experimentation. (You'll need to fill in the entry with the name of a
valid target before trying to get a selection - try TARGETS to see
what an application supports)

Regards,
                                        Owen

=== the tutorial section === (updated)

<sect>Managing Selections

<sect1> Overview

<p>

One type of interprocess communication supported by GTK is
<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 application on a
display, (he <em>owner</em>_ 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 <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 some 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>

This <em>converts</em> the selection into the form specified by
<tt/target/. 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 to the request, 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;
  gint    format;
  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/format/ gives the length of the units (for
instance characters) in bits. Usually, you don't care about this when
receiving data.  <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 invoked 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,
				GtkSelectionFunction function,
				GtkRemoveFunction    remove_func,
				gpointer             data);
</verb></tscreen>

<tt/widget/, <tt/selection/, and <tt/target/ identify the requests
this handler will manage.  <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 void (*GtkSelectionFunction) (GtkWidget *widget, 
                                      GtkSelectionData *selection_data,
				      gpointer data);

</verb></tscreen>

The GtkSelectionData is the same as above, but this time, we're
responsible for filling in the fields <tt/type/, <tt/format/,
<tt/data/, and <tt/length/. (The <tt/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 - <em/i.e./ a
character - or 32 - <em/i.e./ a. integer.) This is done by calling the
function:

<tscreen><verb>
void gtk_selection_data_set (GtkSelectionData *selection_data,
			     GdkAtom           type,
			     gint              format,
			     guchar           *data,
			     gint              length);
</verb></tscreen>

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 by hand.)

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

<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. */
void
selection_handle (GtkWidget *widget, 
		  GtkSelectionData *selection_data,
		  gpointer data)
{
  gchar *timestr;
  time_t current_time;

  current_time = time (NULL);
  timestr = asctime (localtime(&current_time)); 
  /* 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, timestr, strlen(timestr));
}

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

  GtkWidget *selection_button;

  static int have_selection = FALSE;
  
  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_border_width (GTK_CONTAINER (window), 10);

  gtk_signal_connect (GTK_OBJECT (window), "destroy",
		      GTK_SIGNAL_FUNC (gtk_exit), NULL);

  /* 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_handler (selection_button, GDK_SELECTION_PRIMARY,
			     GDK_SELECTION_TYPE_STRING,
			     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]