So I want to have my small Python Gtk window have 2 switches. When one switch is ON, the other is turned OFF, and vice versa. I am not too sure how to control both switches. If anyone can lead me in the right direction, it'd be much appreciated.
#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class SwitcherWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Alt Switch Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=10)
self.add(hbox)
switch1 = Gtk.Switch()
switch1.connect("notify::active", self.on_switch_activated)
switch1.set_active(True)
hbox.pack_start(switch1, True, True, 0)
switch2 = Gtk.Switch()
switch2.connect("notify::active", self.on_switch_activated)
switch2.set_active(False)
hbox.pack_start(switch2, True, True, 0)
if switch1.get_active():
switch2.set_active(False)
else:
switch2.set_active(True)
def on_switch_activated(self, switch, gparam):
builder = Gtk.Builder()
sw1 = builder.get_object("switch1")
sw2 = builder.get_object("switch2")
if switch.get_active():
state = "on"
sw2.set_active(False)
else:
state = "off"
print("Switch was turned", state)
win = SwitcherWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
I would bind the properties of both switches, inverted and sync'ed on creation:
#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject
class SwitcherWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Alt Switch Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=10)
self.add(hbox)
switch1 = Gtk.Switch()
switch1.set_active(True)
hbox.pack_start(switch1, True, True, 0)
switch2 = Gtk.Switch()
switch2.set_active(False)
hbox.pack_start(switch2, True, True, 0)
switch1.bind_property("active", switch2, "active", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN)
win = SwitcherWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
The solution resides on this line of code:
switch1.bind_property("active", switch2, "active", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN)
Here you bind the "active" property of switch1 and switch2 with the binding flags: bidirectional, sync on create and invert boolean
You can implement a similar logic to what I wrote:
#!/usr/bin/env python
class Switch:
_State = False
def __init__(self, StartingPosition=False):
self._State = StartingPosition
def SwitchON(self):
self._State = True
def SwitchOFF(self):
self._State = False
def Switch(self):
self._State = not self._State
def GetInfo(self):
print(self._State)
class Switcher:
def __init__(self, switchDependencyList=[]):
self.SwitchDependencyList = switchDependencyList
if len(self.SwitchDependencyList) == 0:
return None
if not len(self.SwitchDependencyList) % 2:
return None
def SwitchByIndex(self, Index):
for i, switch in enumerate(self.SwitchDependencyList):
if i == Index:
switch.SwitchON()
else:
switch.SwitchOFF()
def GetInfo(self):
for switch in self.SwitchDependencyList:
switch.GetInfo()
sw1 = Switch()
sw2 = Switch()
SwitcherModule = Switcher([sw1, sw2])
SwitcherModule.SwitchByIndex(1)
SwitcherModule.GetInfo()
No need for anything as complex as the prelisted answers. gtk already has a radiobutton widget that does it all for you. Only thing is that when it is initialised you have no buttons set, so you have to pick one to set.
Related
I have this code for creating my own password dialog in Gtk. I need to scale it up by 2-3 times for the display it's going to be used on. How do I do this?
#!/usr/bin/env python3
import gi
import sys
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class EntryDialog(Gtk.Dialog):
def __init__(self, labeltext=None):
super().__init__(title="Password", modal=True, focus_on_map=True)
content = self.get_content_area()
self.timeout_id = None
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
spacing=15, margin=30)
content.add(vbox)
if labeltext is None:
labeltext = "Please enter a password:"
label = Gtk.Label(labeltext)
vbox.pack_start(label, True, True, 0)
hbox = Gtk.Box(spacing=1)
self.entry = Gtk.Entry()
self.entry.set_text("")
self.entry.set_max_length(256)
self.entry.set_invisible_char('•')
self.entry.set_visibility(False)
hbox.pack_start(self.entry, True, True, 0)
self.show = Gtk.ToggleButton(label="show")
self.show.set_active(False)
hbox.pack_start(self.show, True, True, 0)
vbox.pack_start(hbox, True, True, 0)
#self.entry.connect("activate", lambda x: print("Enter"))
self.show.connect("toggled", self.on_show_toggled)
self.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK
)
self.set_default_response(Gtk.ResponseType.OK)
self.entry.connect("activate", self.on_entry_enter)
def on_show_toggled(self, button):
active = button.get_active()
self.entry.set_visibility(active)
button.set_label("hide" if active else "show")
def on_entry_enter(self, button):
self.activate_default()
def get_password(self):
return self.entry.get_text()
def run_dialog(argv):
if len(argv) == 1:
win = EntryDialog()
elif len(argv) == 2:
win = EntryDialog(argv[1])
else:
print(f"Usage: {argv[0]} [<prompt text>]", file=sys.stderr)
sys.exit(2)
win.show_all()
result = win.run()
if result == Gtk.ResponseType.OK:
print(win.get_password())
else:
sys.exit(1)
if __name__ == '__main__':
run_dialog(sys.argv)
Using the GDK_SCALE environment variable was suggested on IRC. This works for my use-case, but it seems a poor solution for general use. In my case, this is what I had to change my program to this:
#!/usr/bin/env python3
import sys
import os
if __name__ == '__main__':
scale = os.environ.get('GDK_SCALE', '1')
scale = float(scale) * 2.5
os.environ['GDK_SCALE'] = str(scale)
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib
class EntryDialog(Gtk.Dialog):
def __init__(self, labeltext=None):
super().__init__(title="Password", modal=True, focus_on_map=True)
content = self.get_content_area()
self.timeout_id = None
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
spacing=15, margin=30)
content.add(vbox)
if labeltext is None:
labeltext = "Please enter a password:"
label = Gtk.Label(label=labeltext)
vbox.pack_start(label, True, True, 0)
hbox = Gtk.Box(spacing=1)
self.entry = Gtk.Entry()
self.entry.set_text("")
self.entry.set_max_length(256)
self.entry.set_invisible_char('•')
self.entry.set_visibility(False)
hbox.pack_start(self.entry, True, True, 0)
self.show = Gtk.ToggleButton(label="show")
self.show.set_active(False)
hbox.pack_start(self.show, True, True, 0)
vbox.pack_start(hbox, True, True, 0)
#self.entry.connect("activate", lambda x: print("Enter"))
self.show.connect("toggled", self.on_show_toggled)
self.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK
)
self.set_default_response(Gtk.ResponseType.OK)
self.entry.connect("activate", self.on_entry_enter)
def on_show_toggled(self, button):
active = button.get_active()
self.entry.set_visibility(active)
button.set_label("hide" if active else "show")
def on_entry_enter(self, button):
self.activate_default()
def get_password(self):
return self.entry.get_text()
def run_dialog(argv):
if len(argv) == 1:
win = EntryDialog()
elif len(argv) == 2:
win = EntryDialog(argv[1])
else:
print(f"Usage: {argv[0]} [<prompt text>]", file=sys.stderr)
sys.exit(2)
win.show_all()
result = win.run()
if result == Gtk.ResponseType.OK:
print(win.get_password())
else:
sys.exit(1)
if __name__ == '__main__':
run_dialog(sys.argv)
Note that I had to make sure the environment variable was set before I imported the gi package.
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()
. Calling the action-specific functions in editor_buffer(GtkSourceBuffer) which simulate the default handling of (cut/copy/paste/select all).
def on_toolbutton_select_all_clicked(self, widget):
print("Select all")
if self.editor.is_focus():
self.editor_buffer.select_range(self.editor_buffer.get_start_iter(), self.editor_buffer.get_end_iter())
else:
self.entry.select_region(0,-1)
return True
def on_toolbutton_cut_clicked(self, widget):
print("Cut")
if self.editor.is_focus():
self.editor_buffer.cut_clipboard(self.clipboard,self.editor_buffer)
else:
self.entry.emit("cut-clipboard")
return True
def on_toolbutton_copy_clicked(self, widget):
if self.editor.is_focus():
self.editor_buffer.copy_clipboard(self.clipboard)
else:
self.entry.emit("copy-clipboard")
return True
def on_toolbutton_paste_clicked(self, widget):
if self.editor.is_focus():
self.editor_buffer.paste_clipboard(self.clipboard, None, self.editor_buffer)
else:
self.entry.emit("paste-clipboard")
return True
for more details, you can look into this (https://developer.gnome.org/pygtk/stable/class-gtktextbuffer.html). for other widgets emitting signals work and this implementation should work for SourceView also.
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()
I'm working on a GTK+ frontend for libvlc in python using python-vlc. So far I followed this guide, which is working fine, except that when I resize the window, the DrawingArea gets messed up as you can see in the picture (there's probably a word for that phenomenon I don't know).
I'm getting these warnings in the console, but am not sure if this is related:
[00007fce1c014eb0] main filter error: Failed to create video converter
[00007fce2807ff70] vdpau_avcodec generic error: Xlib is required for VDPAU
I already tried setting the background color of the window using the css styling for GTK+ but it had no effect.
I think this should not happen, am I missing something? I'm on wayland by the way.
Using Google and checking different examples I created code which fills background in DrawingArea with black color.
Assign drawing function to DrawingArea
self.draw_area = Gtk.DrawingArea()
self.draw_area.connect("draw", self.da_draw_event)
Function which fills area
def da_draw_event(self, widget, cairo_ctx):
cairo_ctx.set_source_rgb(0, 0, 0)
cairo_ctx.paint()
Full code
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('GdkX11', '3.0')
from gi.repository import GdkX11
import vlc
MRL = ""
class ApplicationWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Python-Vlc Media Player")
self.player_paused=False
self.is_player_active = False
self.connect("destroy", Gtk.main_quit)
def show(self):
self.show_all()
def setup_objects_and_events(self):
self.playback_button = Gtk.Button()
self.stop_button = Gtk.Button()
self.play_image = Gtk.Image.new_from_icon_name(
"gtk-media-play",
Gtk.IconSize.MENU
)
self.pause_image = Gtk.Image.new_from_icon_name(
"gtk-media-pause",
Gtk.IconSize.MENU
)
self.stop_image = Gtk.Image.new_from_icon_name(
"gtk-media-stop",
Gtk.IconSize.MENU
)
self.playback_button.set_image(self.play_image)
self.stop_button.set_image(self.stop_image)
self.playback_button.connect("clicked", self.toggle_player_playback)
self.stop_button.connect("clicked", self.stop_player)
self.draw_area = Gtk.DrawingArea()
self.draw_area.set_size_request(300, 300)
self.draw_area.connect("realize",self._realized)
self.draw_area.connect("draw", self.da_draw_event)
self.hbox = Gtk.Box(spacing=6)
self.hbox.pack_start(self.playback_button, True, True, 0)
self.hbox.pack_start(self.stop_button, True, True, 0)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.add(self.vbox)
self.vbox.pack_start(self.draw_area, True, True, 0)
self.vbox.pack_start(self.hbox, False, False, 0)
def da_draw_event(self, widget, cairo_ctx):
#print('da_draw_event')
#print('widget:', widget)
#print('cairo_ctx:', cairo_ctx)
cairo_ctx.set_source_rgb(0, 0, 0)
cairo_ctx.paint()
def stop_player(self, widget, data=None):
self.player.stop()
self.is_player_active = False
self.playback_button.set_image(self.play_image)
def toggle_player_playback(self, widget, data=None):
"""
Handler for Player's Playback Button (Play/Pause).
"""
if self.is_player_active == False and self.player_paused == False:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
elif self.is_player_active == True and self.player_paused == True:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.player_paused = False
elif self.is_player_active == True and self.player_paused == False:
self.player.pause()
self.playback_button.set_image(self.play_image)
self.player_paused = True
else:
pass
def _realized(self, widget, data=None):
self.vlcInstance = vlc.Instance("--no-xlib")
self.player = self.vlcInstance.media_player_new()
win_id = widget.get_window().get_xid()
self.player.set_xwindow(win_id)
self.player.set_mrl(MRL)
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
if __name__ == '__main__':
if not sys.argv[1:]:
print("Exiting \nMust provide the MRL.")
sys.exit(1)
if len(sys.argv[1:]) == 1:
MRL = sys.argv[1]
window = ApplicationWindow()
window.setup_objects_and_events()
window.show()
Gtk.main()
window.player.stop()
window.vlcInstance.release()
As #furas pointed out in the comments, constantly drawing a black rectangle on the DrawingArea works very well. I use the following code inside a widget that derives from Gtk.DrawingArea:
def draw(self, c, *args):
rect = self.get_allocation()
win = self.get_window().get_position()
c.set_source_rgb(0, 0, 0)
c.rectangle(rect.x - win.x, rect.y - win.y, rect.width, rect.height)
c.fill()
self.connect("draw", draw)
Edit: See #furas answer for an even simpler approach.
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
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)
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())
I am very new to GTK and Gnome app development, so apologies for my naiveté. (My development language is Python). I would like to use a ListBox to display some data, and the individual row views will be quite complicated (i.e. composed of multiple different widgets). As a result I would prefer not to use a TreeView, because that will require a bunch of custom drawing/event handling. I noticed that ListBox has a bind_model method, but it appears I can't use it to bind a ListStore model, even thought ListStore implements the ListModel interface. Does anybody know how to accomplish this?
A simple exampe:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, GObject
import sys
class Item(GObject.GObject):
text = GObject.property(type = str)
def __init__(self):
GObject.GObject.__init__(self)
class GUI:
def __init__(self):
item1 = Item()
item1.text = "Hello"
item2 = Item()
item2.text = "World"
liststore = Gio.ListStore()
liststore.append(item1)
liststore.append(item2)
listbox=Gtk.ListBox()
listbox.bind_model(liststore, self.create_widget_func)
window = Gtk.Window()
window.add(listbox)
window.connect("destroy", self.on_window_destroy)
window.show_all()
def create_widget_func(self,item):
label=Gtk.Label(item.text)
return label
def on_window_destroy(self, window):
Gtk.main_quit()
def main():
app = GUI()
Gtk.main()
if __name__ == "__main__":
sys.exit(main())
This is condensed code from my open source accounting program.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf, Gdk
import os, sys
class GUI :
def __init__(self):
listbox = Gtk.ListBox()
employee_name_label = Gtk.Label("Henry", xalign=1)
combo = Gtk.ComboBoxText()
combo.set_property("can-focus", True)
for name in ["bar", "foo", "python"]:
combo.append('0', name)
list_box_row = Gtk.ListBoxRow()
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
list_box_row.add(hbox)
switch = Gtk.Switch()
switch.props.valign = Gtk.Align.CENTER
project_time_label = Gtk.Label("0:00:00", xalign=1 )
project_time_label.set_property('width-chars', 8)
hbox.pack_start(employee_name_label, True, False, 5)
hbox.pack_end(project_time_label, False, False, 5)
hbox.pack_end(switch, False, False, 5)
hbox.pack_end(combo, False, False, 5)
listbox.add(list_box_row)
window = Gtk.Window()
window.add(listbox)
window.connect("destroy", self.on_window_destroy)
window.show_all()
def on_window_destroy(self, window):
Gtk.main_quit()
def main():
app = GUI()
Gtk.main()
if __name__ == "__main__":
sys.exit(main())
It may not answer your question exactly, but it does work and it shows a way to use ListBox. ListBox is a very good choice for complicated setups. In my case I was doing so much operations every second that it crashed Treeviews.