Search code examples
pythonclipboardgtk3pygtkpygobject

Cut, Copy, Paste, Select All Functions - How to simply allow default handling after clicking on menu item?


The "Ctrl+C" and "Ctrl+V" shortcuts (as well as the "right click menu") are available by default in any GTK application, for example a simple hello world app with only a SourceView (see below). But if I add a menu item "Edit->Copy" and assign the "Ctrl+C" accelerator to it and a corresponding callback function, than it obviously stops working since I am intercepting the signal with my own method. So, how can I trigger the default cut/copy/paste/select_all functionalities inside my custom method?

Note: returning False works for the Paste function but not for Copy/Cut/Select All

Simple example - In this case all functions (cut/copy/paste/select all) work fine.

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0') 
from gi.repository import Gtk, Gdk, Pango, GObject, GtkSource

class MyOwnApp(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")
        self.set_default_size(500, 500)

        self.vbox = Gtk.VBox()

        editor = GtkSource.View.new()
        editor.set_show_line_numbers(True)
        editor.set_auto_indent(True)
        editor_buffer = editor.get_buffer()
        self.vbox.pack_start(editor, False, False, 0)

        self.add(self.vbox)

 win = MyOwnApp()
 win.connect("destroy", Gtk.main_quit)
 win.show_all()
 Gtk.main()

If I add a menu item with a callback they don't work anymore.

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0')
from gi.repository import Gtk, Gdk, Pango, GObject, GtkSource

class MyOwnApp(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Hello World")

        self.set_default_size(900, 900)

        box_outer = Gtk.VBox()

        # MENUBAR setup
        menuBar = Gtk.MenuBar()
        # Set accelerators
        agr = Gtk.AccelGroup()
        self.add_accel_group(agr)

        # File menu
        file_menu_dropdown = Gtk.MenuItem("File")
        menuBar.append(file_menu_dropdown)
        file_menu = Gtk.Menu()
        file_menu_dropdown.set_submenu(file_menu)

        # File menu Items
        file_exit = Gtk.MenuItem("Exit")
        key, mod = Gtk.accelerator_parse("<Control>Q")
        file_exit.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        file_exit.connect("activate", self.quit)

        file_menu.append(file_exit)

        # Edit menu
        edit_menu_dropdown = Gtk.MenuItem("Edit")
        menuBar.append(edit_menu_dropdown)
        edit_menu = Gtk.Menu()
        edit_menu_dropdown.set_submenu(edit_menu)

        # Edit menu Items
        edit_cut = Gtk.MenuItem("Cut")
        key, mod = Gtk.accelerator_parse("<Control>X")
        edit_cut.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_cut.connect("activate", self.on_toolbutton_cut_clicked)

        edit_copy = Gtk.MenuItem("Copy")
        key, mod = Gtk.accelerator_parse("<Control>C")
        edit_copy.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_copy.connect("activate", self.on_toolbutton_copy_clicked)

        edit_paste = Gtk.MenuItem("Paste")
        key, mod = Gtk.accelerator_parse("<Control>V")
        edit_paste.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_paste.connect("activate", self.on_toolbutton_paste_clicked)

        edit_select_all = Gtk.MenuItem("Select All")
        key, mod = Gtk.accelerator_parse("<Control>A")
        edit_select_all.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
        edit_select_all.connect("activate", self.on_toolbutton_select_all_clicked)

        edit_menu.append(edit_select_all)
        edit_menu.append(edit_cut)
        edit_menu.append(edit_copy)
        edit_menu.append(edit_paste)

        box_outer.pack_start(menuBar, False, False, 0)

        # SourceView
        editor = GtkSource.View.new()
        editor.set_show_line_numbers(True)
        editor.set_auto_indent(True)
        editor_buffer = editor.get_buffer()
        box_outer.pack_start(editor, True, True, 0)

        self.add(box_outer)

    def quit(self,widget=None):
        Gtk.main_quit()

    def on_toolbutton_select_all_clicked(self, widget):
        return False

    def on_toolbutton_cut_clicked(self, widget):
        return False

    def on_toolbutton_copy_clicked(self, widget):
        return False

    def on_toolbutton_paste_clicked(self, widget):
        return False

win = MyOwnApp()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

Solution

  • After too many hours of research, I'm happy to post this solution for all the GTK enthusiast out there!!
    Thanks to @SivaGuru for contributing!!

    With this solution, you can use the cut/copy/paste/selectAll functions across multiple widgets inside a window (with both Gtk.Entry and GtkSource.View).
    The key point is that these two widgets use different methods for the cut/copy/paste/selectAll functionalities, but (as expected) they both have default methods to manage these basic functionalities. No need to reinvent the wheel.
    Note: The Gtk.Entry widget inherits from the Gtk.Editable interface, which has all the necessary functions to fallback to the default handling of cut/copy/past/selectAll.

    import gi
    gi.require_version('Gtk', '3.0')
    gi.require_version('GtkSource', '3.0')
    from gi.repository import Gtk, Gdk, GtkSource
    
    class MyWindow(Gtk.Window):
    
        def __init__(self):
            Gtk.Window.__init__(self, title="Hello World")
    
            self.set_default_size(900, 900)
    
            box_outer = Gtk.VBox()
    
            # MENUBAR setup
            menuBar = Gtk.MenuBar()
            # Set accelerators
            agr = Gtk.AccelGroup()
            self.add_accel_group(agr)
    
            # File menu
            file_menu_dropdown = Gtk.MenuItem("File")
            menuBar.append(file_menu_dropdown)
            file_menu = Gtk.Menu()
            file_menu_dropdown.set_submenu(file_menu)
    
            #File menu Items
            file_exit = Gtk.MenuItem("Exit")
            key, mod = Gtk.accelerator_parse("<Control>Q")
            file_exit.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            file_exit.connect("activate", self.quit)
    
            file_menu.append(file_exit)
    
            # Edit menu
            edit_menu_dropdown = Gtk.MenuItem("Edit")
            menuBar.append(edit_menu_dropdown)
            edit_menu = Gtk.Menu()
            edit_menu_dropdown.set_submenu(edit_menu)
    
            # Edit menu Items
            edit_cut = Gtk.MenuItem("Cut")
            key, mod = Gtk.accelerator_parse("<Control>X")
            edit_cut.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_cut.connect("activate", self.on_toolbutton_cut_clicked)
    
            edit_copy = Gtk.MenuItem("Copy")
            key, mod = Gtk.accelerator_parse("<Control>C")
            edit_copy.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_copy.connect("activate", self.on_toolbutton_copy_clicked)
    
            edit_paste = Gtk.MenuItem("Paste")
            key, mod = Gtk.accelerator_parse("<Control>V")
            edit_paste.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_paste.connect("activate", self.on_toolbutton_paste_clicked)
    
            edit_select_all = Gtk.MenuItem("Select All")
            key, mod = Gtk.accelerator_parse("<Control>A")
            edit_select_all.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE)
            edit_select_all.connect("activate", self.on_toolbutton_select_all_clicked)
    
            edit_menu.append(edit_select_all)
            edit_menu.append(edit_cut)
            edit_menu.append(edit_copy)
            edit_menu.append(edit_paste)
    
            box_outer.pack_start(menuBar, False, False, 0)
    
            entry = Gtk.Entry()
            box_outer.pack_start(entry, False, False, 0)
    
            editor = GtkSource.View.new()
            editor.set_show_line_numbers(True)
            editor.set_auto_indent(True)
            box_outer.pack_start(editor, True, True, 0)
    
            self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
    
            self.add(box_outer)
    
        def quit(self,widget=None):
            Gtk.main_quit()
    
        def on_toolbutton_select_all_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.select_region(0, -1)
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.select_range(editor_buffer.get_start_iter(), editor_buffer.get_end_iter())
                    else:
                        pass
    
        def on_toolbutton_cut_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.cut_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.cut_clipboard(self.clipboard, editor_buffer)
                    else:
                        pass
    
        def on_toolbutton_copy_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.copy_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.copy_clipboard(self.clipboard)
                    else:
                        pass
    
        def on_toolbutton_paste_clicked(self, widget):
            focusedWidget = self.get_focus()
            if focusedWidget is not None:
                if focusedWidget.has_focus():
                    if str(type(focusedWidget)) == "<class 'gi.repository.Gtk.Entry'>":
                        focusedWidget.paste_clipboard()
                    elif str(type(focusedWidget)) == "<class 'gi.repository.GtkSource.View'>":
                        editor_buffer = focusedWidget.get_buffer()
                        editor_buffer.paste_clipboard(self.clipboard, None, editor_buffer)
                    else:
                        pass
    
    win = MyWindow()
    win.connect("destroy", Gtk.main_quit)
    win.show_all()
    Gtk.main()