Handling spreadsheet data through the clipboard in GTK

clipboard, gtk, linux, pygtk, python

Solution

To catch the paste event, you need to first create a custom entry class (`PastableEntry` in this example) that inherits from `gtksheet.ItemEntry`. During its initialisation, we connect to the `paste-clipboard` signal to trap paste events:

class PastableEntry(gtksheet.ItemEntry):
    def __init__(self):
        gtksheet.ItemEntry.__init__(self)
        self.connect('paste-clipboard', self.__on_paste)

The hard work is in the event handler. First we need to get the clipboard contents. In Unix, clipboard sources can advertise multiple data formats. Based on your screenshot, I assume you're trying to copy data from Gnumeric. Gnumeric supports `application/x-gnumeric`, `text/html`, `UTF8_STRING`, `COMPOUND_TEXT`, and `STRING`. For this example we'll use the UTF8_STRING format, which looks like this:

1,1 <tab> 1,2 <tab> 1,3 <newline>
2,1 <tab> 2,2 <tab> 2,3 <newline>
3,1 <tab> 3,2 <tab> 3,3

Obviously this fails horribly if any of the cells contain a tab or newline character, but we'll use this for simplicity. In a real world application you may want to parse the `application/x-gnumeric` or `text/html` formatted data.

Back to our PastableEntry class, now we define the paste event handler:

    def __on_paste(self, entry):
        clip = gtk.Clipboard()
        data = clip.wait_for_contents('UTF8_STRING')
        text = data.get_text()
        sheet = self.parent
        o_row, o_col = sheet.get_active_cell()
        for i_row, row in enumerate(text.split('\n')):
            for i_col, cell in enumerate(row.split('\t')):
                sheet.set_cell_text(o_row + i_row, o_col + i_col, cell)
        self.stop_emission('paste-clipboard')

It should be quite self-explanatory. We split the clipboard data into rows (by newline characters) and then into cells (by tab characters), and set the Sheet cell values accordingly.

The `stop_emission` is there to stop GTK+ from running the default handler for paste operations. Without that line, the selected cell will be overwritten with the raw data.

We then register the class with GObject:

gobject.type_register(PastableEntry)

Finally, to actually use our custom entry class, pass it to the constructor of `gtksheet.Sheet`:

s = gtksheet.Sheet(20, 20, "Sheet 1", entry_type=PastableEntry)

Problem

I'm using a `GtkSheet` widget in PyGTK to power my application's spreadsheet, and it gives me an API to pull and push data out of cells. (I looked at using GtkTreeView, but it seemed to be too much work) What I don't understand is how to intercept paste requests (via ie. CTRL+V) so that I can process them rather than passing it through to the widget. Currently, when pasting from a spreadsheet the data shows up as follows: becomes Is there a signal I should intercept? I'm on Ubuntu 9.10, Python 2.6.

Original source