How can I add cut/copy/paste functionality to my application?
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.
Asier Iturralde Sarasola
Updated on September 18, 2022Comments
-
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.htmlBut 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 almost 12 yearsSeems 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):
becomeson_entry_focus(self, widget, event):
whereself.focus = widget
and remove theentrywidget
from the copy event. -
Asier Iturralde Sarasola almost 12 yearsThank 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 almost 12 yearsOk, 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 almost 12 yearsBut 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. over 11 yearsI 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 over 11 yearsA
gtk.TextBuffer
is not needed, the implementedgtk.Editable
interface contains everything you need. Will update this answer with a full working example based on the original code.