3

I'm developing an Electoral Calculator (https://launchpad.net/electoralcalculator) using Python 2.7, Gtk+3 and Glade and I want to add cut/copy/paste functionality to my application.

I want to give the user the opportunity to cut/copy/paste text using menu items (Edit > Cut, Edit > Copy, and Edit > Paste), toolbar buttons or keyboard shortcuts (Ctrl+X, Ctrl+C, and Ctrl+V).

How can I get the selected text to cut/copy it? There are many text entry widgets and the selected text can be in any of them.

How can I know where the cursor is, so I can paste the contents of the clipboard there?

I found about this example:
http://python-gtk-3-tutorial.readthedocs.org/en/latest/clipboard.html

But while in this example there is only one text entry widget, in my application there are plenty of them.
How can I know where the selected text is (in which text entry widget) to cut/copy it?
How can I know where the cursor is for the paste functionality?

English is not my first language, please forgive my errors.

Thanks for your help

EDIT:
I wrote an example with working cut, copy, and paste buttons based on Ian's/Timo's code.
Thank you both, Timo and Ian B., for your help. I really appreciate it.

Let me know if there's something wrong in the example.

The keyboard shortcuts (Ctrl+X, Ctrl+C, and Ctrl+V) work automagically without adding any code.

from gi.repository import Gtk, Gdk

class TwotextWindow(Gtk.Window):
    __gtype_name__ = "TwotextWindow"

    def __init__(self):
        super(TwotextWindow, self).__init__()
        self.connect('delete-event', Gtk.main_quit)

        self.vbox = Gtk.VBox(False, 8)
        for x in range(4):
            self._build_entry()

        button_cut = Gtk.Button(label='Cut')
        button_cut.connect('clicked', self.on_cut_clicked)
        self.vbox.pack_start(button_cut, False, False, 0)

        button_copy = Gtk.Button(label='Copy')
        button_copy.connect('clicked', self.on_copy_clicked)
        self.vbox.pack_start(button_copy, False, False, 0)

        button_paste = Gtk.Button(label='Paste')
        button_paste.connect('clicked', self.on_paste_clicked)
        self.vbox.pack_start(button_paste, False, False, 0)

        self.add(self.vbox)
        self.show_all()

        # Code for other initialization actions should be added here.
        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)

    def _build_entry(self):
        entry = Gtk.Entry(text='Hello, World!')
        entry.connect('focus-in-event', self.on_entry_focus)
        self.vbox.pack_start(entry, False, False, 0)

    def on_cut_clicked(self, widget):
        # Get the bounds of the selected text
        bounds = self.focus.get_selection_bounds()

        # if the bounds of the selection are not an empty tuple,
        # put the selection in the variable chars
        # and copy it to the clipboard
        # (get_selection_bounds returns an empty tuple if there is no selection)
        # then delete the selection
        if bounds:
            chars = self.focus.get_chars(*bounds)
            print "Copying '%s' from: %s" % (chars, self.focus)
            self.clipboard.set_text(chars, -1)
            print "Deleting text selection: characters from position %s to %s" % (bounds[0], bounds[1])
            self.focus.delete_text(bounds[0], bounds[1])
        else:
            print "Can't cut if you don't select text"

    def on_copy_clicked(self, widget):
        # Get the bounds of the selected text
        bounds = self.focus.get_selection_bounds()

        # if the bounds of the selection are not an empty tuple,
        # put the selection in the variable chars
        # and copy it to the clipboard
        # (get_selection_bounds returns an empty tuple if there is no selection)
        if bounds:
            chars = self.focus.get_chars(*bounds)
            print "Copying '%s' from: %s" % (chars, self.focus)
            self.clipboard.set_text(chars, -1)
        else:
            print "Can't copy if you don't select text"

    def on_paste_clicked(self, widget):
        # Get the text from the clipboard
        text = self.clipboard.wait_for_text()

        if text != None:
            # If there's text selected in the target
            # delete it and paste the contents of the clipboard
            bounds = self.focus.get_selection_bounds()
            if bounds:
                print "Deleting text selection: characters from position %s to %s" % (bounds[0], bounds[1])
                self.focus.delete_text(bounds[0], bounds[1])
                print "Pasting '%s' into: '%s' at the position %s" % (text, self.focus, bounds[0])
                self.focus.insert_text(text, bounds[0])

            # else insert the text in the current position of the cursor in the target
            else:
                pos = self.focus.get_position()
                #print "Cursor position in the target: %s" % pos
                print "Pasting '%s' into: '%s' at the position %s" % (text, self.focus, pos)
                self.focus.insert_text(text, pos)
        else:
            print "No text on the clipboard."

    def on_entry_focus(self, widget, event):
        print "Focused:", widget
        self.focus = widget


if __name__ == '__main__':
    win = TwotextWindow()
    Gtk.main()

1 Answer 1

2

How about using an internal variable to store the last active widget? Use the entry's focus-in-event signal (when keyboard takes focus) to modify that variable with its name (can use a common callback for all text entries). Then when you need to copy or paste something, you can use that variable to know where to put it (via a getattr). Here's a little example I cooked up.

Original code edited to work standalone and solve questions

from gi.repository import Gtk, Gdk

class TwotextWindow(Gtk.Window):
    __gtype_name__ = "TwotextWindow"

    def __init__(self):
        super(TwotextWindow, self).__init__()
        self.connect('delete-event', Gtk.main_quit)

        self.vbox = Gtk.VBox(False, 8)
        for x in range(4):
            self._build_entry()

        button = Gtk.Button(label='Copy')
        button.connect('clicked', self.on_copy_clicked)
        self.vbox.pack_start(button, False, False, 0)

        self.add(self.vbox)
        self.show_all()

        # Code for other initialization actions should be added here.
        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)

    def _build_entry(self):
        entry = Gtk.Entry(text='Hello, World!')
        entry.connect('focus-in-event', self.on_entry_focus)
        self.vbox.pack_start(entry, False, False, 0)

    def on_copy_clicked(self, widget):
        bounds = self.focus.get_selection_bounds()
        chars = self.focus.get_chars(*bounds)
        print "Copying '%s' from: %s" % (chars, self.focus)
        #TODO: do the actual copying

    def on_entry_focus(self, widget, event):
        print "Focused:", widget
        self.focus = widget


if __name__ == '__main__':
    win = TwotextWindow()
    Gtk.main()

Don't know if there's a better way to do this. I'm pretty new to this too.

6
  • 2
    Seems like a good idea, but I would change something. Instead of passing the entry name to the focus event and retrieving it in the copy event, it's easier to store the actual widget and save some coding. on_entry_focus(self,widget,flag,entry): becomes on_entry_focus(self, widget, event): where self.focus = widget and remove the entrywidget from the copy event.
    – Timo
    Jul 27, 2012 at 22:22
  • Thank you both, Ian B. and Timo. Both ways work but there are two problems: I can't copy text from my app to other app and all the text from the entry is copied. For example, it's not possible to write 'Hello world' in a text entry and copy only 'world'. Jul 28, 2012 at 0:00
  • Ok, I solved the first problem after adding the code that actually copies the text to the clipboard in on_mnu_copy_activate: self.clipboard.set_text(self.focus.get_text(), -1) Now I can copy text from my app to other app. Jul 28, 2012 at 0:19
  • But I can't solve the second problem. Only the selected text should be copied to the clipboard, not all the text from the entry. Any ideas on how to do that? Jul 28, 2012 at 0:22
  • 2
    A gtk.TextBuffer is not needed, the implemented gtk.Editable interface contains everything you need. Will update this answer with a full working example based on the original code.
    – Timo
    Jul 28, 2012 at 13:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .