From de065c56aff4fae26e1ebcc0dfe03e9dbf1429e8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Oct 2014 13:29:22 +0530 Subject: [PATCH] Sample GTK+3 program to explore use of GMenuModel --- src/calibre/gui2/dbus_export/gtk.py | 259 ++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 src/calibre/gui2/dbus_export/gtk.py diff --git a/src/calibre/gui2/dbus_export/gtk.py b/src/calibre/gui2/dbus_export/gtk.py new file mode 100644 index 0000000000..eefeb4f329 --- /dev/null +++ b/src/calibre/gui2/dbus_export/gtk.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +# Demo program to explore the GTK DBus interface, which is only partially documented +# at https://wiki.gnome.org/Projects/GLib/GApplication/DBusAPI + +import sys, dbus, subprocess +from threading import Thread +from pprint import pformat + +from gi.repository import Gtk, Gdk, GdkX11 # noqa + +UI_INFO = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" + +class MenuExampleWindow(Gtk.ApplicationWindow): + + def __init__(self, app): + Gtk.Window.__init__(self, application=app, title="Menu Example") + + self.set_default_size(800, 600) + self.scroll = s = Gtk.ScrolledWindow() + s.set_hexpand(True), s.set_vexpand(True) + s.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + s.set_min_content_height(450) + self.label = la = Gtk.TextView() + la.set_text = la.get_buffer().set_text + s.add(la) + + action_group = Gtk.ActionGroup("my_actions") + + self.add_file_menu_actions(action_group) + self.add_edit_menu_actions(action_group) + self.add_choices_menu_actions(action_group) + + uimanager = self.create_ui_manager() + uimanager.insert_action_group(action_group) + + menubar = uimanager.get_widget("/MenuBar") + + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + box.pack_start(menubar, False, False, 0) + + toolbar = uimanager.get_widget("/ToolBar") + box.pack_start(toolbar, False, False, 0) + + eventbox = Gtk.EventBox() + eventbox.connect("button-press-event", self.on_button_press_event) + box.pack_start(s, False, False, 0) + box.pack_start(eventbox, True, True, 0) + + label = Gtk.Label("Right-click to see the popup menu.") + eventbox.add(label) + + self.popup = uimanager.get_widget("/PopupMenu") + + self.add(box) + + def add_file_menu_actions(self, action_group): + action_filemenu = Gtk.Action("FileMenu", "File", None, None) + action_group.add_action(action_filemenu) + + action_filenewmenu = Gtk.Action("FileNew", None, None, Gtk.STOCK_NEW) + action_group.add_action(action_filenewmenu) + + action_new = Gtk.Action("FileNewStandard", "_New", + "Create a new file", Gtk.STOCK_NEW) + action_new.connect("activate", self.on_menu_file_new_generic) + action_group.add_action_with_accel(action_new, 'N') + + action_group.add_actions([ + ("FileNewFoo", None, "New Foo", None, "Create new foo", + self.on_menu_file_new_generic), + ("FileNewGoo", None, "_New Goo", None, "Create new goo", + self.on_menu_file_new_generic), + ]) + + action_filequit = Gtk.Action("FileQuit", None, None, Gtk.STOCK_QUIT) + action_filequit.connect("activate", self.on_menu_file_quit) + action_group.add_action_with_accel(action_filequit, 'Q') + + def add_edit_menu_actions(self, action_group): + action_group.add_actions([ + ("EditMenu", None, "Edit"), + ("EditCopy", Gtk.STOCK_COPY, None, None, None, + self.on_menu_others), + ("EditPaste", Gtk.STOCK_PASTE, None, None, None, + self.on_menu_others), + ("EditSomething", None, "Something", "S", None, + self.on_menu_others) + ]) + + def add_choices_menu_actions(self, action_group): + action_group.add_action(Gtk.Action("ChoicesMenu", "Choices", None, + None)) + + action_group.add_radio_actions([ + ("ChoiceOne", None, "One", None, None, 1), + ("ChoiceTwo", None, "Two", None, None, 2) + ], 1, self.on_menu_choices_changed) + + three = Gtk.ToggleAction("ChoiceThree", "Three", None, None) + three.connect("toggled", self.on_menu_choices_toggled) + action_group.add_action(three) + + def create_ui_manager(self): + uimanager = Gtk.UIManager() + + # Throws exception if something went wrong + uimanager.add_ui_from_string(UI_INFO) + + # Add the accelerator group to the toplevel window + accelgroup = uimanager.get_accel_group() + self.add_accel_group(accelgroup) + return uimanager + + def on_menu_file_new_generic(self, widget): + print("A File|New menu item was selected.") + + def on_menu_file_quit(self, widget): + app.quit() + + def on_menu_others(self, widget): + print("Menu item " + widget.get_name() + " was selected") + + def on_menu_choices_changed(self, widget, current): + print(current.get_name() + " was selected.") + + def on_menu_choices_toggled(self, widget): + if widget.get_active(): + print(widget.get_name() + " activated") + else: + print(widget.get_name() + " deactivated") + + def on_button_press_event(self, widget, event): + # Check if right mouse button was preseed + if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: + self.popup.popup(None, None, None, None, event.button, event.time) + return True # event has been handled + +def convert(v): + if isinstance(v, basestring): + return unicode(v) + if isinstance(v, dbus.Struct): + return tuple(convert(val) for val in v) + if isinstance(v, list): + return [convert(val) for val in v] + if isinstance(v, dict): + return {convert(k):convert(val) for k, val in v.iteritems()} + if isinstance(v, dbus.Boolean): + return bool(v) + if isinstance(v, (dbus.UInt32, dbus.UInt16)): + return int(v) + return v + +class MyApplication(Gtk.Application): + + def do_activate(self): + win = self.window = MenuExampleWindow(self) + win.show_all() + self.get_xprop_data() + Thread(target=self.print_dbus_data).start() + + def get_xprop_data(self): + property_names = {GdkX11.x11_get_xatom_name(i) for i in xrange(691)} + property_names = {name for name in property_names if + name.startswith('_GTK') or name.startswith('_UNITY') or name.startswith('_GNOME')} + props = [] + for name in sorted(property_names): + props.append(subprocess.check_output(['xprop', '-id', str(self.window.get_window().get_xid()), name])) + raw = props[-1] + if raw.startswith('_UNITY_OBJECT_PATH'): + self.object_path = eval(raw.split('=')[-1].strip()) + elif raw.startswith('_GTK_UNIQUE_BUS_NAME'): + self.bus_name = eval(raw.split('=')[-1].strip()) + self.xprop_data = '\n'.join(props) + + def print(self, *args): + self.data.append(' '.join(map(str, args))) + + def print_menu_start(self, bus, group=0, seen=None): + groups = set() + seen = seen or set() + seen.add(group) + print = self.print + print ('\nMenu description (Group %d)' % group) + for item in bus.call_blocking(self.bus_name, self.object_path, 'org.gtk.Menus', 'Start', 'au', ([group],)): + print ('Subscription group:', item[0]) + print ('Menu number:', item[1]) + for menu_item in item[2]: + menu_item = {unicode(k):convert(v) for k, v in menu_item.iteritems()} + if ':submenu' in menu_item: + groups.add(menu_item[':submenu'][0]) + if ':section' in menu_item: + groups.add(menu_item[':section'][0]) + print (pformat(menu_item)) + for other_group in sorted(groups - seen): + self.print_menu_start(bus, other_group, seen) + + def print_dbus_data(self): + bus = dbus.SessionBus() + self.data = [] + self.get_actions_description(bus) + self.print_menu_start(bus) + self.data.append(self.xprop_data) + self.window.label.set_text('\n'.join(self.data)) + + def get_actions_description(self, bus): + print = self.print + print('\nActions description') + self.actions_desc = d = {} + for name, data in bus.call_blocking(self.bus_name, self.object_path, 'org.gtk.Actions', 'DescribeAll', '', ()).iteritems(): + d[name] = {'enabled':convert(data[0]), 'param type': convert(data[1]), 'state':convert(data[2])} + print ('Name:', name) + print (pformat(d[name])) + + def do_startup(self): + Gtk.Application.do_startup(self) + +app = MyApplication(application_id='com.calibre-ebook.test-gtk') +sys.exit(app.run(sys.argv))