Xfce Foundation Classes
 « Main Page | Index

Data Selections

Table of Contents

  1. Overview
  2. Retrieving the selection
  3. Supplying the selection
  4. 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 XFC 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 signal handler for this signal receives a reference to a Gtk::SelectionData object which provides the following accessor functions:
 
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() functions return the values you gave in your Gtk::Widget::selection_convert() call. The type() function 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() function returns the length of the units (8 for characters, 32 for integers) in bits. Usually, you don't care about this when receiving data. The data() function returns a pointer to the returned data, and length() 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 get_targets() function retrieves the targets as a vector of Gdk::Atom. The second retrieves the target names as a vector of String.
 
The following function returns true if the length of the selection data is greater than 0.

bool is_valid() const;

You can connect directly to the "selection_received" signal with the following code:

widget->signal_selection_received().connect(sigc::mem_fun(this, &MyClass::selection_received_handler));

where the "selection_received" signal handler has the following prototype:

void MyClass::selection_received_handler(const Gtk::SelectionData& selection_data, unsigned int time);

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 targets and information on their use can be found in the ICCCM. The targets MULTIPLE, TARGETS and TIMESTAMP are defined by
X11 architecture as mandatory and are automatically provided by GTK+.
 
To define the supported target(s) for a certain selection and widget use one of the following Gtk::Widget 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 TargetEntry object with one of the following constructors:

TargetEntry();
 
TargetEntry(const String& target_name, unsigned int unique_id, Gtk::TargetFlagsField drag_flags = 0);

The first constructor creates an empty TargetEntry. The second constructor creates a target entry with the specified name and unique id. 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. It restricts the validity of the corresponding target to the same application or the same widget.
 
If you create an empty TargetEntry, you can set its target_name and unique_id by calling the Gtk::TargetEntry method:

void set(const String& target_name, unsigned int unique_id, Gtk::TargetFlagsField drag_flags = 0);

The 'target_name' is the string corresponding to the target atom and unique_id is the unique integer id. The 'drag_flags' argument is used to restrict the validity of the corresponding target and can either be Gtk::TARGET_SAME_APP or Gtk::TARGET_SAME_WIDGET.

Now 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, const Gdk::Display *display = 0);

The 'selection' argument is the atom corresponding the selection for which the owner should be changed, 'time' is a timestamp with which to claim the selection, or GDK_CURRENT_TIME. The 'display' is the Gdk::Display where the selection is set, or null for the default display. 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 'selection' is an interned atom representing the selection to check. 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 Gtk::WidgetSignals::on_selection_get() virtual signal handler.
 
To connect to the "selection_get" signal, use the following code:

widget->signal_selection_get().connect(sigc::mem_fun(this, &MyClass::selection_get_handler));

The selection_get handler has the following prototype:

void MyClass::selection_get_handler(Gtk::SelectionData& selection_data, unsigned int info, unsigned int time);

The 'selection_data' is the SelectionData object to be filled in, 'info' is the info integer that was set when the target was registered, and 'time' is the time of the conversion request as set by the requestor with Gtk::Widget::selection_convert().

When the "selection_get" signal returns, the widget requesting the selection will receive the selection_data. You must call the following Gtk::SelectionData methods to set the selection_data fields:

void set(Gdk::Atom type, int format, const void *data, int length);

bool set_text(const String& str);


These methods should only be called from a selection handler callback. The 'type' argument 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. The length argument is the data length. The data should be null-terminated.

For convenience, the set_text() method sets the contents of the selection from a UTF-8 encoded string. The string is converted to the form determined by the target and true is returned if the selection was successfully set.
 
If another application claims ownership of the selection, you will receive a "selection_clear_event" signal. To explicitly release ownership of a selection call the following Gtk::Widget method:

bool selection_owner_unset(Gdk::Atom selection, unsigned int time, const Gdk::Display *display = 0);

The 'selection' argument is an interned atom representing the selection to release, 'time' is a timestamp, or GDK_CURRENT_TIME and 'display' is the Gdk::Display where the selection is set, or null for the default display. True is returned if the operation succeeded.

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.

The header file for the selection example is <selection.hh>:

#include <xfc/main.hh>
#include <xfc/gtk/entry.hh>
#include <xfc/gtk/selection.hh>
#include <xfc/gtk/togglebutton.hh>
#include <xfc/gtk/togglebuttonsignals.hh>
#include <xfc/gtk/invisible.hh>
#include <xfc/gtk/widgetsignals.hh>
#include <xfc/gtk/dialog.hh>

using namespace Xfc;

// RetrieveSelectionButton

class RetrieveSelectionButton : public Gtk::Button, protected Gtk::WidgetSignals
{
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, protected Gtk::ToggleButtonSignals
{
    static std::vector<Gtk::TargetEntry> target_entries;
    
    Gtk::Invisible *selection_widget;
    String target_string;

protected:
    virtual void on_toggled();
    void selection_get(Gtk::SelectionData& selection_data, unsigned int info, unsigned int time);
    bool selection_clear_event(const Gdk::EventSelection& event);

public:
    SupplySelectionButton();
    virtual ~SupplySelectionButton();
};

// SelectionTest

class SelectionTest : public Gtk::Dialog
{
public:
    SelectionTest();
    virtual ~SelectionTest();
};


and the source file is <selection.cc>:
 
#include "selection.hh"
#include <xfc/gtk/box.hh>
#include <xfc/gtk/buttonbox.hh>
#include <xfc/gtk/label.hh>
#include <iostream>

std::vector<Gtk::TargetEntry> SupplySelectionButton::target_entries;

enum
{
    STRING,
    LENGTH
};

// RetrieveSelectionButton

RetrieveSelectionButton::RetrieveSelectionButton(Gtk::Entry *entry)
: Gtk::WidgetSignals(this)
{
    set_label("Retrieve Selection");
    signal_clicked().connect(sigc::bind(sigc::mem_fun(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()
: Gtk::ToggleButtonSignals(this)
{
    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->signal_selection_get().connect(sigc::mem_fun(this, &SupplySelectionButton::selection_get));
    selection_widget->signal_selection_clear_event().connect(sigc::mem_fun(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, GDK_CURRENT_TIME))
            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, GDK_CURRENT_TIME);
            cout << "SUCCESSFULLY CLEARED OWNERSHIP." << endl;
        }
    }
}

void
SupplySelectionButton::selection_get(Gtk::SelectionData& selection_data, unsigned int info, unsigned int time)
{

    using namespace std;

    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(const Gdk::EventSelection& 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->signal_clicked().connect(sigc::mem_fun(this, &SelectionTest::dispose));
    button->show();
}

SelectionTest::~SelectionTest()
{
}

int main (int argc, char *argv[])
{
    using namespace Main;

    init(&argc, &argv);

    SelectionTest window;
    window.signal_destroy().connect(sigc::ptr_fun(&Xfc::Main::quit));
    window.show();

    run();
    return 0;
}

Compiling Selection

If you compiled and installed XFC yourself, you will find the source code for Selection in the <examples/selection> source directory along with a Makefile. If XFC came pre-installed, or you installed it from an RPM package, you will find the source code in the </usr/share/doc/xfcui-X.X/examples/selection> subdirectory. In this case you will have to create the Makefile yourself (replace X.X with the version number of the libXFCui library you have installed).

To create a Makefile for Selection, add the following lines to a new text file and save it using the name "Makefile":

CC = g++

CFLAGS = -Wall -O2

spinbutton: spinbutton.cc spinbutton.hh
    $(CC) spinbutton.cc -o spinbutton $(CFLAGS) `pkg-config xfcui-X.X --cflags --libs`

clean:
    rm -f *.o spinbutton


If you cut and paste these lines make sure the whitespace before $(CC) and rm is a tab character. When you compile and run this program you will see the following window appear:



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.

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.


Copyright © 2004-2005 The XFC Development Team Top
XFC 4.4