1
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
4
# ubuntu-fr <LoCo Team>
5
# kanor <Romain DUBREIL> <contact@kanor.fr>
6
# cm-t <Rudy ANDRÉ> <arudy@ubuntu-fr.org>
7
# olive <Olivier FRAYSSE> <olive@picapo.org>
8
# quesh <Frédéric MANDE> <quesh@quesh.fr>
9
# This program is free software: you can redistribute it and/or modify it
10
# under the terms of the GNU General Public License version 3, as published
11
# by the Free Software Foundation.
13
# This program is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranties of
15
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16
# PURPOSE. See the GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License along
19
# with this program. If not, see <http://www.gnu.org/licenses/>.
22
### DO NOT EDIT THIS FILE ###
24
'''Enhances builder connections, provides object to access glade objects'''
26
from gi.repository import GObject, Gtk # pylint: disable=E0611
31
logger = logging.getLogger('dash_privacy_interface_lib')
33
from xml.etree.cElementTree import ElementTree
35
# this module is big so uses some conventional prefixes and postfixes
36
# *s list, except self.widgets is a dictionary
39
# ele_* element in a ElementTree
42
# pylint: disable=R0904
43
# the many public methods is a feature of Gtk.Builder
44
class Builder(Gtk.Builder):
46
connects glade defined handler to default_handler if necessary
47
auto connects widget to handler with matching name or alias
48
auto connects several widgets to a handler via multiple aliases
49
allow handlers to lookup widget name
50
logs every connection made, and any on_* not made
54
Gtk.Builder.__init__(self)
56
self.glade_handler_dict = {}
58
self._reverse_widget_dict = {}
60
# pylint: disable=R0201
61
# this is a method so that a subclass of Builder can redefine it
62
def default_handler(self,
63
handler_name, filename, *args, **kwargs):
64
'''helps the apprentice guru
66
glade defined handlers that do not exist come here instead.
67
An apprentice guru might wonder which signal does what he wants,
68
now he can define any likely candidates in glade and notice which
69
ones get triggered when he plays with the project.
70
this method does not appear in Gtk.Builder'''
71
logger.debug('''tried to call non-existent function:%s()
74
kwargs:%s''', handler_name, filename, args, kwargs)
75
# pylint: enable=R0201
77
def get_name(self, widget):
78
''' allows a handler to get the name (id) of a widget
80
this method does not appear in Gtk.Builder'''
81
return self._reverse_widget_dict.get(widget)
83
def add_from_file(self, filename):
84
'''parses xml file and stores wanted details'''
85
Gtk.Builder.add_from_file(self, filename)
87
# extract data for the extra interfaces
91
ele_widgets = tree.getiterator("object")
92
for ele_widget in ele_widgets:
93
name = ele_widget.attrib['id']
94
widget = self.get_object(name)
96
# populate indexes - a dictionary of widgets
97
self.widgets[name] = widget
99
# populate a reversed dictionary
100
self._reverse_widget_dict[widget] = name
102
# populate connections list
103
ele_signals = ele_widget.findall("signal")
107
ele_signal.attrib['name'],
108
ele_signal.attrib['handler']) for ele_signal in ele_signals]
111
self.connections.extend(connections)
113
ele_signals = tree.getiterator("signal")
114
for ele_signal in ele_signals:
115
self.glade_handler_dict.update(
116
{ele_signal.attrib["handler"]: None})
118
def connect_signals(self, callback_obj):
119
'''connect the handlers defined in glade
121
reports successful and failed connections
122
and logs call to missing handlers'''
123
filename = inspect.getfile(callback_obj.__class__)
124
callback_handler_dict = dict_from_callback_obj(callback_obj)
126
connection_dict.update(self.glade_handler_dict)
127
connection_dict.update(callback_handler_dict)
128
for item in connection_dict.items():
130
# the handler is missing so reroute to default_handler
131
handler = functools.partial(
132
self.default_handler, item[0], filename)
134
connection_dict[item[0]] = handler
136
# replace the run time warning
137
logger.warn("expected handler '%s' in %s",
140
# connect glade define handlers
141
Gtk.Builder.connect_signals(self, connection_dict)
143
# let's tell the user how we applied the glade design
144
for connection in self.connections:
145
widget_name, signal_name, handler_name = connection
146
logger.debug("connect builder by design '%s', '%s', '%s'",
147
widget_name, signal_name, handler_name)
149
def get_ui(self, callback_obj=None, by_name=True):
150
'''Creates the ui object with widgets as attributes
152
connects signals by 2 methods
153
this method does not appear in Gtk.Builder'''
155
result = UiFactory(self.widgets)
157
# Hook up any signals the user defined in glade
158
if callback_obj is not None:
159
# connect glade define handlers
160
self.connect_signals(callback_obj)
163
auto_connect_by_name(callback_obj, self)
168
# pylint: disable=R0903
169
# this class deliberately does not provide any public interfaces
170
# apart from the glade widgets
172
''' provides an object with attributes as glade widgets'''
173
def __init__(self, widget_dict):
174
self._widget_dict = widget_dict
175
for (widget_name, widget) in widget_dict.items():
176
setattr(self, widget_name, widget)
178
# Mangle any non-usable names (like with spaces or dashes)
180
cannot_message = """cannot bind ui.%s, name already exists
181
consider using a pythonic name instead of design name '%s'"""
182
consider_message = """consider using a pythonic name instead of design name '%s'"""
184
for (widget_name, widget) in widget_dict.items():
185
pyname = make_pyname(widget_name)
186
if pyname != widget_name:
187
if hasattr(self, pyname):
188
logger.debug(cannot_message, pyname, widget_name)
190
logger.debug(consider_message, widget_name)
191
setattr(self, pyname, widget)
194
'''Support 'for o in self' '''
195
return iter(widget_dict.values())
196
setattr(self, '__iter__', iterator)
198
def __getitem__(self, name):
199
'access as dictionary where name might be non-pythonic'
200
return self._widget_dict[name]
201
# pylint: enable=R0903
204
def make_pyname(name):
205
''' mangles non-pythonic names into pythonic ones'''
207
for character in name:
208
if (character.isalpha() or character == '_' or
209
(pyname and character.isdigit())):
216
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
217
# need to reimplement inspect.getmembers. GObject introspection doesn't
219
def getmembers(obj, check):
223
attr = getattr(obj, k)
227
members.append((k, attr))
232
def dict_from_callback_obj(callback_obj):
233
'''a dictionary interface to callback_obj'''
234
methods = getmembers(callback_obj, inspect.ismethod)
236
aliased_methods = [x[1] for x in methods if hasattr(x[1], 'aliases')]
238
# a method may have several aliases
239
#~ @alias('on_btn_foo_clicked')
240
#~ @alias('on_tool_foo_activate')
241
#~ on_menu_foo_activate():
243
alias_groups = [(x.aliases, x) for x in aliased_methods]
246
for item in alias_groups:
247
for alias in item[0]:
248
aliases.append((alias, item[1]))
250
dict_methods = dict(methods)
251
dict_aliases = dict(aliases)
254
results.update(dict_methods)
255
results.update(dict_aliases)
260
def auto_connect_by_name(callback_obj, builder):
261
'''finds handlers like on_<widget_name>_<signal> and connects them
263
i.e. find widget,signal pair in builder and call
264
widget.connect(signal, on_<widget_name>_<signal>)'''
266
callback_handler_dict = dict_from_callback_obj(callback_obj)
268
for item in builder.widgets.items():
269
(widget_name, widget) = item
272
widget_type = type(widget)
274
signal_ids.extend(GObject.signal_list_ids(widget_type))
275
widget_type = GObject.type_parent(widget_type)
276
except RuntimeError: # pylint wants a specific error
278
signal_names = [GObject.signal_name(sid) for sid in signal_ids]
280
# Now, automatically find any the user didn't specify in glade
281
for sig in signal_names:
282
# using convention suggested by glade
283
sig = sig.replace("-", "_")
284
handler_names = ["on_%s_%s" % (widget_name, sig)]
286
# Using the convention that the top level window is not
287
# specified in the handler name. That is use
288
# on_destroy() instead of on_windowname_destroy()
289
if widget is callback_obj:
290
handler_names.append("on_%s" % sig)
292
do_connect(item, sig, handler_names,
293
callback_handler_dict, builder.connections)
295
log_unconnected_functions(callback_handler_dict, builder.connections)
298
def do_connect(item, signal_name, handler_names,
299
callback_handler_dict, connections):
300
'''connect this signal to an unused handler'''
301
widget_name, widget = item
303
for handler_name in handler_names:
304
target = handler_name in callback_handler_dict.keys()
305
connection = (widget_name, signal_name, handler_name)
306
duplicate = connection in connections
307
if target and not duplicate:
308
widget.connect(signal_name, callback_handler_dict[handler_name])
309
connections.append(connection)
311
logger.debug("connect builder by name '%s','%s', '%s'",
312
widget_name, signal_name, handler_name)
315
def log_unconnected_functions(callback_handler_dict, connections):
316
'''log functions like on_* that we could not connect'''
318
connected_functions = [x[2] for x in connections]
320
handler_names = callback_handler_dict.keys()
321
unconnected = [x for x in handler_names if x.startswith('on_')]
323
for handler_name in connected_functions:
325
unconnected.remove(handler_name)
329
for handler_name in unconnected:
330
logger.debug("Not connected to builder '%s'", handler_name)