Search code examples
pythonuser-interfacegtkpygtkgtk3

How do I use keyboard shortcuts with Gtk StackSwitcher?


What I want to do is make a shortcut keyboard key to switch between Page 1 and Page 2. For instance, pressing Ctrl+S would take me to Page 1 if I am not already there and likewise pressing Ctrl+R would take me to Page 2. I searched the documentation but I couldn't find anything related to what I need. Is there a way to implement it? Please see the below image:

Stack Switcher

enter image description here

Here's the snippet:

class App(Gtk.Application):
def __init__(self, *args, **kwargs):
    super(App, self).__init__(*args, **kwargs)
    self.connect('activate', self.on_activate)

    self.send_stack = None
    self.receive_stack = None
    self.send_receive_stack = None
    self.header_button_handler_id = None
    self.pre_sign_widget = None

def on_activate(self, app):
    ui_file_path = os.path.join(
        os.path.dirname(os.path.abspath(__file__)),
        "app.ui")
    appwindow = 'applicationwindow1'
    builder = Gtk.Builder()
    builder.add_objects_from_file(ui_file_path, [appwindow])
    window = builder.get_object(appwindow)
    window.set_wmclass ("sign", "sign")
    window.set_title("sign")
    window.connect("delete-event", self.on_delete_window)
    self.headerbar = window.get_titlebar()
    self.header_button = builder.get_object("back_refresh_button")
    self.header_button.connect('clicked', self.on_header_button_clicked)

    sw = builder.get_object('stackswitcher1')
    # I want to be able to press Alt+S and Alt+R respectively
    # to switch the stack pages to Send and Receive.
    # sw.get_children()
    self.stack_switcher = sw

    self.send_receive_stack = builder.get_object("send_receive_stack")
    self.send_receive_stack.connect('notify::visible-child',
        self.on_sr_stack_switch)

    ## Load Send part
    self.send = SendApp()
    ss = self.send.stack
    p = ss.get_parent()
    if p:
        p.remove(ss)
    ss.connect('notify::visible-child', self.on_send_stack_switch)
    ss.connect('map', self.on_send_stack_mapped)
    klw = self.send.klw
    klw.connect("key-activated", self.on_key_activated)
    klw.connect("map", self.on_keylist_mapped)
    klw.props.margin_left = klw.props.margin_right = 15
    self.send_stack = ss
    ## End of loading send part

    # Load Receive part
    self.receive = PswMappingReceiveApp(self.on_presign_mapped)
    rs = self.receive.stack

    rs.connect('notify::visible-child',
        self.on_receive_stack_switch)


    scanner = self.receive.scanner
    scanner.connect("map", self.on_scanner_mapped)
    self.receive_stack = rs

    self.send_receive_stack.add_titled(self.send_stack,
        "send_stack", _("Send"))
    self.send_receive_stack.add_titled(rs,
        "receive_stack", _("Receive"))

    accel_group = Gtk.AccelGroup()
    window.add_accel_group(accel_group)
    self.receive.accept_button.add_accelerator("clicked", accel_group, ord('o'), Gdk.ModifierType.MOD1_MASK,
                                               Gtk.AccelFlags.VISIBLE)
    self.receive.accept_button.set_can_default(True)

    window.show_all()
    self.add_window(window)

Solution

  • Here's a hacky way to get to the first and last children of a stack with two children:

    #!/usr/bin/env python
    
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, GdkPixbuf, Gdk
    import os, sys
    
    class GUI:
        def __init__(self):
    
            self.stack  = Gtk.Stack()
            switcher = Gtk.StackSwitcher()
            switcher.set_stack(self.stack)
            label1 = Gtk.Label("label 1")
            label2 = Gtk.Label("label 2")
            label3 = Gtk.Label("label 3")
            self.stack.add_titled (label1, "1", "Page 1")
            self.stack.add_titled (label2, "2", "Page 2")
            self.stack.add_titled (label3, "3", "Page 3")
            box = Gtk.Box()
            box.pack_start(switcher, True, False, 0)
            box.pack_start(self.stack, True, True, 0)
            box.set_orientation(Gtk.Orientation.VERTICAL)
            window = Gtk.Window()
            window.add(box)
    
            window.show_all()
            window.connect("key-press-event", self.key_press)
    
        def key_press (self, window, event):
            keyname = Gdk.keyval_name(event.keyval)
            if not Gdk.ModifierType.CONTROL_MASK:
                #only continue when the CTRL key is down
                return         
    
            #get the last child
    
            if keyname == "r":
                for child in self.stack.get_children(): 
                    self.stack.set_visible_child(child)
    
            #get the first child
    
            if keyname == "s":
                for child in self.stack.get_children():
                    self.stack.set_visible_child(child)
                    return
    
        def on_window_destroy(self, window):
            Gtk.main_quit()
    
    def main():
        app = GUI()
        Gtk.main()
    
    if __name__ == "__main__":
        sys.exit(main())
    

    Here's a hacky way to scroll forward and backward through a stack with more than two children:

    #!/usr/bin/env python
    
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, GdkPixbuf, Gdk
    import os, sys
    
    class GUI:
        def __init__(self):
    
            self.stack  = Gtk.Stack()
            switcher = Gtk.StackSwitcher()
            switcher.set_stack(self.stack)
            label1 = Gtk.Label("label 1")
            label2 = Gtk.Label("label 2")
            label3 = Gtk.Label("label 3")
            self.stack.add_titled (label1, "1", "Page 1")
            self.stack.add_titled (label2, "2", "Page 2")
            self.stack.add_titled (label3, "3", "Page 3")
            box = Gtk.Box()
            box.pack_start(switcher, True, False, 0)
            box.pack_start(self.stack, True, True, 0)
            box.set_orientation(Gtk.Orientation.VERTICAL)
            window = Gtk.Window()
            window.add(box)
    
            window.show_all()
            window.connect("key-press-event", self.key_press)
    
        def key_press (self, window, event):
            keyname = Gdk.keyval_name(event.keyval)
            if not Gdk.ModifierType.CONTROL_MASK:
                #only continue when the CTRL key is down
                return         
    
            #forward scroll
    
            #variable to capture the active widget
            previous_child_active = False  
            if keyname == "r":
                #iterate over the stack children
                for child in self.stack.get_children(): 
                    if previous_child_active == True: 
                        # the last widget was the one that was active
                        self.stack.set_visible_child(child)
                        #finished
                        return                          
                    #remember if the previous child was active
                    previous_child_active = self.stack.get_visible_child() == child
    
            #reverse scroll
    
            #variable to capture the last widget we iterated
            previous_child = None        
            if keyname == "s":
                #iterate over the stack children
                for child in self.stack.get_children():
                    if self.stack.get_visible_child() == child and previous_child != None: 
                        #this is the active widget, so we set the previous active
                        self.stack.set_visible_child(previous_child)
                        #finished
                        return                    
                    #remember the last widget       
                    previous_child = child       
    
        def on_window_destroy(self, window):
            Gtk.main_quit()
    
    def main():
        app = GUI()
        Gtk.main()
    
    if __name__ == "__main__":
        sys.exit(main())