How can I add cut/copy/paste functionality to my application?

5,724

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.

Share:
5,724
Asier Iturralde Sarasola
Author by

Asier Iturralde Sarasola

Updated on September 18, 2022

Comments

  • Asier Iturralde Sarasola
    Asier Iturralde Sarasola over 1 year

    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()
    
  • Timo
    Timo almost 12 years
    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.
  • Asier Iturralde Sarasola
    Asier Iturralde Sarasola almost 12 years
    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'.
  • Asier Iturralde Sarasola
    Asier Iturralde Sarasola almost 12 years
    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.
  • Asier Iturralde Sarasola
    Asier Iturralde Sarasola almost 12 years
    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?
  • Ian B.
    Ian B. over 11 years
    I think you need to use a textbuffer to do that. I don't know if you can associate a Gtk.Entry with one, I've only used them with a Gtk.TextView. The textbuffer stores the cursor position and selected text allowing better control. @Timo: Nice change!
  • Timo
    Timo over 11 years
    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.