Managing Selections
- Overview
- Retrieving the selection
- Supplying the selection
- Selection Example
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 Inti 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 <gdk/gdkselection.h>
corresponding to these atoms. For instance the constant
GDK_SELECTION_PRIMARY 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.
Retrieving the
selection
Retrieving the selection is an asynchronous process. To start the
process, you call the Gtk::Widget method:
bool
selection_convert(Gdk::Atom selection, Gdk::Atom target, unsigned int time = GDK_CURRENT_TIME);
|
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 virtual signal handler for this
signal receives a reference to a Gtk::SelectionData class which
provides
the following accessors to access the fields in the underlying
GtkSelectionData structure:
Gdk::Atom selection() const;
Gdk::Atom target() const;
Gdk::Atom type() const;
int format() const;
unsigned char* data() const;
int length() const;
|
The selection() and target() methods return the
values you gave in your Gtk::Widget::selection_convert() call. The
type() method returns 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. The format() method returns
the length of the units (for instance characters) in bits. Usually, you
don't care about this when receiving data. The data() method returns a
pointer to the returned data, and the length() method returns 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.
For convenience, there are accessors that return the selection, target
and type as a String.
String get_selection() const;
String get_target() const;
String get_type() const;
|
You can retrieve the selection_data as a
vector of supported targets. This can be used to interpret the results
of getting the standard "TARGETS" target that is always supplied for
any
selection.
bool
get_targets(std::vector<Gdk::Atom>& targets) const;
bool
get_targets(std::vector<String>& targets) const; |
The first method retrieves the targets as a vector of Gdk::Atom. The
second method retrieves the target names as a vector of String.
The following method returns true if the length of the
selection data is greater than 0.
You can connect directly to the selection_received signal by
doing the following:
widget->sig_selection_received().connect(slot(this,
&MyClass::selection_received_handler)); |
The selection_received signal handler has the following
prototype:
void
MyClass::selection_received_handler(GtkSelectionData *selection_data, unsigned int time);
|
It receives a pointer to a GtkSelectionData structure which has fields
that correspond to those described above for Gtk::SelectionData.
Supplying the selection
Being the owner of a selection is a bit more
complicated and only makes sense when there is data for transmission.
Data can exist in a variety of formats so first you have to define the
targets that correspond to these formats. You are free to come up with
target names yourself but this is only useful within the same
application. Therefore, a number of standard targets have been agreed
upon. A list of these can be found in "selection.txt" in the "docs"
subdirectory.
To define the supported target(s) for a certain selection and widget
use one of the following methods:
void
selection_add_target(Gdk::Atom selection, Gdk::Atom target, unsigned int info);
void selection_add_target(Gdk::Atom
selection, const Gtk::TargetEntry&
entry);
void selection_add_targets(Gdk::Atom
selection, const
std::vector<Gtk::TargetEntry>& targets); |
The selection argument is the atom
corresponding to the selection for which the target(s) will be
registered, the target argument is the atom corresponding to
the target name you want to register and the info argument
is
a unique integer id that will be passed back through signals later on.
The first two methods register a single target. The second method
takes a Gtk::TargetEntry rather than separate target and info
arguments. The last method lets you register multiple targets at once
by
passing a vector of Gtk::TargetEntry.
You can create a new Gtk::TargetEntry with one of the following
constructors:
TargetEntry();
TargetEntry(const char *target_name, unsigned int unique_id, unsigned
int drag_flags = 0); |
The
target_name argument is a string
corresponding to the target atom and
unique_id is the unique
integer id. The
drag_flags argument is only used when
implementing
Drag and Drop.
You can set the target_name and unique_id arguments
by calling the Gtk::TargetEntry method:
void
set(const char *target_name, unsigned int unique_id, unsigned
int drag_flags = 0);
|
Now that the targets have been registered, when
prompted by the user you need to take ownership of a certain selection.
This is done with the following Gtk::Widget method:
bool
selection_owner_set(Gdk::Atom selection, unsigned
int time = GDK_CURRENT_TIME); |
The selection argument is the atom
corresponding the selection for which the owner should be changed. If
the return value is true the calling widget owns the
selection. If you need to check whether a widget owns a certain
selection you can call the Gtk::Widget accessor:
bool
selection_owner_get(Gdk::Atom selection); |
The return value is true if the calling widget owns the
specified selection.
To be able to respond to a
Gtk::Widget::selection_convert() request you must either connect to the
selection_get
signal or derive a new class for the selection widget and override the
virtual Gtk::Widget::on_selection_get() signal handler.
To connect to the selection_get signal you would do the
following:
widget->sig_selection_get().connect(slot(this, &MyClass::selection_get_handler)); |
where the selection_get handler has the following prototype:
void
MyClass::selection_get_handler(GtkSelectionData *selection_data, unsigned int info, unsigned
int time);
|
To override the virtual Gtk::Widget::on_selection_get() handler add the
following method to your selection widget class.
virtual
void on_selection_get(Gtk::SelectionData& selection_data, unsigned int info, unsigned
int time); |
Here, the selection_data is a wrapper
class for the GtkSelectionData structure and provides convenient
accessors and methods to get and set the structure's fields.
When the selection_get signal returns, the widget requesting
the selection will receive the selection_data. You must not
fill in the selection_data values by hand. Instead you call
the
following Gtk::SelectionData method:
void
set(Gdk::Atom type, int format, const void *data, int
length);
|
The type is an atom that describes the
data provided and the format is the number of bits required
to store one element of the target type. Usually it will be 8 for a
character or 32 for an integer. The most commonly used target types
are:
ATOM, COMPOUND_TEXT, INTEGER and STRING. This method takes care of
properly making a copy of the data so that you don't have to worry
about
keeping it around.
If another application claims ownership of the selection, you will
receive a selection_clear_event signal. To explicitly remove
ownership of a selection call the following Gtk::Widget method:
static
bool selection_owner_unset(Gdk::Atom selection, unsigned int time = GDK_CURRENT_TIME);
|
Selection Example
In the following example, we retrieve the special
target "TARGETS", which is a list of all targets into which the
selection can be converted. When the "Retrieve Selection" button is
pressed, the data of the target is requested from the PRIMARY
selection.
When the Gtk::SelectionData class has been received by the
on_selection_received() handler the values of all the class data
members
are printed to the standard output. Note, if the PRIMARY selection is
not owned by any client the type() will be null, the format and data()
0, and the length() -1.
Run the program from a shell. When the dialog is first displayed the
entry widget displays the text "TARGETS". The text is highlighted,
which
means the entry widget is the current owner of the PRIMARY selection.
When the "Retrieve Selection" button is pressed the targets returned
are
printed to the standard output. These will be the three mandatory ones:
TIMESTAMP, TARGETS and MULTIPLE, plus those registered by the Entry
widget: UTF8_STRING, STRING, TEXT and COMPOUND_TEXT.
The "Supply Selection" button registers two targets - STRING and
LENGTH. When toggled in, the "Supply Selection" button takes ownership
of the primary selection. Entering STRING or LENGTH into the entry
widget and pressing the "Retrieve Selection" button prints the values
returned by the Gtk::SelectionData class, including the target's data,
to the standard output. For STRING, this data is the arbitrary string
"This is the data of the STRING target." and for LENGTH the data is 38
-
the number of characters in the STRING data.
The header file for Selection Example is
selection.h:
#include<inti/main.h>
#include <inti/core.h>
#include <inti/gtk/entry.h>
#include <inti/gtk/selection.h>
#include <inti/gtk/togglebutton.h>
#include <inti/gtk/invisible.h>
using namespace Inti;
// RetrieveSelectionButton
class RetrieveSelectionButton : public Gtk::Button
{
protected:
void
on_get_target(Gtk::Entry *entry);
virtual void
on_selection_received(const
Gtk::SelectionData& selection_data, unsigned
int time);
public:
RetrieveSelectionButton(Gtk::Entry *entry);
virtual
~RetrieveSelectionButton();
};
// SupplySelectionButton
class SupplySelectionButton : public Gtk::ToggleButton
{
static
std::vector<Gtk::TargetEntry> target_entries;
Gtk::Invisible *selection_widget;
String target_string;
protected:
virtual void on_toggled();
void
selection_get(GtkSelectionData *selection_data, unsigned
int info, unsigned int time);
bool
selection_clear_event(GdkEventSelection *event);
public:
SupplySelectionButton();
virtual
~SupplySelectionButton();
};
// SelectionTest
class SelectionTest : public Gtk::Dialog
{
public:
SelectionTest();
virtual
~SelectionTest();
};
|
and the source file is selection.cc:
#include"selection.h"
#include <inti/gtk/buttonbox.h>
#include <inti/gtk/label.h>
#include <inti/bind.h>
#include <iostream>
std::vector<Gtk::TargetEntry>
SupplySelectionButton::target_entries;
enum
{
STRING,
LENGTH
};
// RetrieveSelectionButton
RetrieveSelectionButton::RetrieveSelectionButton(Gtk::Entry *entry)
{
set_label("Retrieve Selection");
sig_clicked().connect(bind(slot(this,
&RetrieveSelectionButton::on_get_target), entry));
}
RetrieveSelectionButton::~RetrieveSelectionButton()
{
}
void
RetrieveSelectionButton::on_get_target(Gtk::Entry *entry)
{
// Get the atom
corresponding to the target string entered into the entry widget.
Gdk::Atom target_atom =
gdk_atom_intern(entry->get_text().c_str(), FALSE);
// And request the
target for the primary selection.
selection_convert(GDK_SELECTION_PRIMARY, target_atom);
}
void
RetrieveSelectionButton::on_selection_received(const
Gtk::SelectionData& selection_data, unsigned
int time)
{
using namespace std;
// Print out the values
of the selection_data members.
cout << endl << "The values
returned by the Gtk::SelectionData members are..." << endl;
cout << " * selection() = " <<
selection_data.get_selection() << endl;
cout << " * target() = " <<
selection_data.get_target() << endl;
cout << " * type() = " <<
selection_data.get_type() << endl;
cout << " * format() = " <<
selection_data.format() << endl;
cout.setf(ios_base::hex, ios_base::basefield);
cout << " * data() = 0x" << reinterpret_cast<unsigned
int>(selection_data.data()) << endl;
cout.setf(ios_base::dec, ios_base::basefield);
cout << " * length() = " <<
selection_data.length() << endl << endl;
// IMPORTANT - Check to
see if any data was returned.
if
(!selection_data.is_valid())
{
cout << "Couldn't get the
target's data." << endl;
return;
}
if
(selection_data.get_target() == "TARGETS")
{
cout << "The targets
supported by the \"" << selection_data.get_selection();
cout << "\" selection
are..." << endl;
//
Retrieve the target names as a vector of String
std::vector<String>
atom_names;
if
(!selection_data.get_targets(atom_names))
return;
// Print
out the names
int
count = atom_names.size();
for
(int i = 0; i < count; i++)
{
cout <<
" * " << atom_names[i] << endl;
}
}
else if
(selection_data.get_target() == "STRING")
{
cout << "The 'STRING' data
= " << selection_data.data() << endl;
}
else if
(selection_data.get_target() == "LENGTH")
{
cout << "The 'LENGTH' data
= " << static_cast<int>(*selection_data.data()) << endl;
}
}
// SupplySelectionButton
SupplySelectionButton::SupplySelectionButton()
{
set_label("Supply Selection");
// Add STRING and LENGTH
targets to the primary selection.
target_entries.push_back(Gtk::TargetEntry("STRING", STRING));
target_entries.push_back(Gtk::TargetEntry("LENGTH",
LENGTH));
// Create the widget
that will be used as the selection owner;
selection_widget = new Gtk::Invisible;
selection_widget->realize();
selection_widget->sig_selection_get().connect(slot(this,
&SupplySelectionButton::selection_get));
selection_widget->sig_selection_clear_event().connect(slot(this,
&SupplySelectionButton::selection_clear_event));
selection_widget->selection_add_targets(GDK_SELECTION_PRIMARY,
target_entries);
// Define the content the
STRING target's data.
target_string = "This is the data of the STRING
target.";
}
SupplySelectionButton::~SupplySelectionButton()
{
}
void
SupplySelectionButton::on_toggled()
{
using namespace std;
if (get_active())
{
if
(selection_widget->selection_owner_set(GDK_SELECTION_PRIMARY))
cout
<< "SUCCESSFULLY GOT OWNERSHIP." << endl;
else
{
// If unable to claim ownership of selection return
button to out state.
cout << "FAILED TO GET OWNERSHIP." <<
endl;
set_active(false);
}
}
else
{
//
Before clearing the selection by calling selection_owner_unset(),
// we check if we are the
actual owner.
if
(selection_widget->selection_owner_get(GDK_SELECTION_PRIMARY))
{
selection_owner_unset(GDK_SELECTION_PRIMARY);
cout <<
"SUCCESSFULLY CLEARED OWNERSHIP." << endl;
}
}
}
void
SupplySelectionButton::selection_get(GtkSelectionData *data, unsigned int info, unsigned
int time)
{
using namespace std;
Gtk::SelectionData *selection_data = static_cast<Gtk::SelectionData*>(data);
cout << "Got request for the '" <<
selection_data->get_target() << "' target." << endl;
// Set the target data
for our registered targets.
switch
(info)
{
case STRING:
selection_data->set
(
gdk_atom_intern("STRING", TRUE), // type
8,
// format
target_string.c_str(),
// data
target_string.size()
// data length
);
break;
case LENGTH:
{
int length = target_string.size();
selection_data->set(gdk_atom_intern("INTEGER", TRUE), 32,
&length, sizeof(length));
}
break;
}
}
bool
SupplySelectionButton::selection_clear_event(GdkEventSelection *event)
{
if (get_active())
{
set_active(false);
std::cout << "FORCEFULLY
CLEARED OWNERSHIP." << std::endl;
}
return false;
}
// SelectionTest
SelectionTest::SelectionTest()
{
set_title("Selection Test");
set_border_width(5);
set_has_separator(false);
set_resizable(false);
// Add label
Gtk::VBox *vbox = new
Gtk::VBox;
Gtk::HBox *hbox = new
Gtk::HBox;
Gtk::Label *label = new
Gtk::Label("Enter target name");
label->set_justify(Gtk::JUSTIFY_LEFT);
hbox->pack_start(*label, false,false);
vbox->pack_start(*hbox);
// Add entry widget
Gtk::Entry *entry = new Gtk::Entry;
entry->set_text("TARGETS");
vbox->pack_start(*entry);
client_area()->pack_start(*vbox, true, true, 5);
client_area()->show_all();
// Create a button the
user can click to get selection targets
RetrieveSelectionButton
*retrieve_button = new
RetrieveSelectionButton(entry);
action_area()->pack_end(*retrieve_button, false, false);
retrieve_button->show();
// Add a button the user
can toggle to claim ownership of the selection
SupplySelectionButton *supply_button = new SupplySelectionButton;
action_area()->pack_end(*supply_button, false, false);
supply_button->show();
// Add a button to end
the program.
Gtk::Button *button = new Gtk::Button("Quit");
action_area()->pack_end(*button, false, false);
button->sig_clicked().connect(slot(this, &SelectionTest::dispose));
button->show();
}
SelectionTest::~SelectionTest()
{
}
int main (int
argc, char *argv[])
{
using namespace Main;
init(&argc, &argv);
SelectionTest window;
window.sig_destroy().connect(slot(&Inti::Main::quit));
window.show();
run();
return 0;
}
|
For convenience, the widget requesting the
selection "RetrieveSelectionButton" and the widget supplying the
selection "SupplySelectionButton" are in the same application, but they
could have been in different applications as in the GTK+ selection
example.