Tutorial

Inti Logo

« Advanced Event and Signal Handling
Drag and Drop »

Managing Selections

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

bool is_valid() const;

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.


Selection Test

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.


« Advanced Event and Signal Handling
Index
Top
Drag and Drop »