1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import gettext
23 import os
24
25 import gtk
26 import gtk.glade
27 from twisted.python import util
28 from twisted.internet import defer
29 from zope.interface import implements
30
31 from flumotion.common import errors, log, messages
32 from flumotion.common.i18n import N_, gettexter
33 from flumotion.configure import configure
34 from flumotion.twisted import flavors
35 from flumotion.ui.fgtk import ProxyWidgetMapping
36
37 _ = gettext.gettext
38 __version__ = "$Rev: 7522 $"
39 T_ = gettexter()
40
41
43 """
44 I am a base class for all GTK+-based Admin UI nodes.
45 I am a view on a set of properties for a component.
46
47 @ivar widget: the main widget representing this node
48 @type widget: L{gtk.Widget}
49 @ivar wtree: the widget tree representation for this node
50 """
51
52 implements(flavors.IStateListener)
53
54 logCategory = "admingtk"
55 gladeFile = None
56
57 gettextDomain = configure.PACKAGE
58
59 - def __init__(self, state, admin, title=None):
60 """
61 @param state: state of component this is a UI node for
62 @type state: L{flumotion.common.planet.AdminComponentState}
63 @param admin: the admin model that interfaces with the manager for us
64 @type admin: L{flumotion.admin.admin.AdminModel}
65 @param title: the (translated) title to show this node with
66 @type title: str
67 """
68 self._debugEnabled = False
69 self.state = state
70 self.admin = admin
71 self.statusbar = None
72 self.title = title
73 self.nodes = util.OrderedDict()
74 self.wtree = None
75 self.widget = None
76 self.uiState = None
77 self._pendingUIState = None
78
79
80
81 self._gladefilepath = None
82
84 """Set if debug should be enabled.
85 Not all pages are visible unless debugging is set to true
86
87 @param enabled: whether debug should be enabled
88 @type enabled: bool
89 """
90 self._debugEnabled = enabled
91
95
99
103
104 - def callRemote(self, methodName, *args, **kwargs):
107
108
109
111 """
112 Returns: a deferred returning the widget tree from the glade file.
113 """
114
115 def _getBundledFileCallback(result, gladeFile):
116 path = result
117 if not os.path.exists(path):
118 self.warning("Glade file %s not found in path %s" % (
119 gladeFile, path))
120 self.debug("loading widget tree from %s" % path)
121
122 old = gtk.glade.textdomain()
123 self.debug("Switching glade text domain from %s to %s" % (
124 old, domain))
125 self._gladefilepath = path
126 gtk.glade.textdomain(domain)
127
128 self.wtree = gtk.glade.XML(path,
129 typedict=ProxyWidgetMapping())
130
131 self.debug("Switching glade text domain back from %s to %s" % (
132 domain, old))
133 gtk.glade.textdomain(old)
134 return self.wtree
135
136
137
138 gladeFile = gladeFile.replace(os.path.sep, '/')
139
140
141 self.debug("requesting bundle for glade file %s" % gladeFile)
142 d = self.admin.bundleLoader.getFile(gladeFile)
143 d.addCallback(_getBundledFileCallback, gladeFile)
144 return d
145
154
169
178
184
194
196 "Override me"
197 pass
198
200 "Override me"
201 pass
202
204 "Override me"
205 pass
206
208 "Override me"
209 pass
210
212 "Override me"
213 pass
214
216 """
217 Render the GTK+ admin view for this component.
218
219 Returns: a deferred returning the main widget for embedding
220 """
221 self.debug('BaseAdminGtkNode.render() for %s' % self.title)
222
223
224 allmessages = self.state.get('messages', [])
225 for message in allmessages:
226
227
228 if message.id == 'render-%s' % self.title:
229 self.debug('Removing previous messages %r' % message)
230 self.state.observe_remove('messages', message)
231
232 def error(debug):
233
234
235 self.warning("error rendering component UI; debug %s", debug)
236 m = messages.Error(T_(N_(
237 "Internal error in component UI's '%s' tab. "
238 "Please file a bug against the component."), self.title),
239 debug=debug, mid="render-%s" % self.title)
240 self.addMessage(m)
241
242 label = gtk.Label(_("Internal error.\nSee component error "
243 "message\nfor more details."))
244
245
246
247 self.widget = label
248
249 return label
250
251 def loadGladeFile():
252
253 if hasattr(self, 'glade_file'):
254 self.gladeFile = self.glade_file
255 debug = "class %r should have glade_file " \
256 "changed to gladeFile" % self.__class__
257 import warnings
258 warnings.warn(debug, DeprecationWarning)
259 m = messages.Warning(T_(N_(
260 "Internal error in component UI's '%s' tab. "
261 "Please file a bug against the component."), self.title),
262 debug=debug, mid="render-%s" % self.title)
263 self.addMessage(m)
264
265 if not self.gladeFile:
266 return defer.succeed(None)
267
268 def haveWtree(wtree):
269 self.wtree = wtree
270 self.debug('render: calling haveWidgetTree')
271 try:
272 self.haveWidgetTree()
273 except Exception, e:
274 return error(log.getExceptionMessage(e))
275
276 self.debug('render: loading glade file %s in text domain %s',
277 self.gladeFile, self.gettextDomain)
278
279 d = self.loadGladeFile(self.gladeFile, self.gettextDomain)
280 d.addCallback(haveWtree)
281 return d
282
283 def loadGladeFileErrback(failure):
284 if failure.check(RuntimeError):
285 return error(
286 'Could not load glade file %s.' % self.gladeFile)
287 if failure.check(errors.NoBundleError):
288 return error(
289 'No bundle found containing %s.' % self.gladeFile)
290
291 return failure
292
293 def renderFinishedCallback(_):
294 if not self.widget:
295 self.debug('render: no self.widget, failing')
296 raise TypeError(
297 '%r.haveWidgetTree should have set self.widget' %
298 self.__class__)
299
300 if self._pendingUIState:
301 self.debug('render: calling setUIState on the node')
302 self.setUIState(self._pendingUIState)
303
304 self.debug('renderFinished: returning widget %s', self.widget)
305 return self.widget
306
307 def renderFinishedErrback(failure):
308 return error(log.getFailureMessage(failure))
309
310 d = loadGladeFile()
311 d.addErrback(loadGladeFileErrback)
312 d.addCallback(renderFinishedCallback)
313 d.addErrback(renderFinishedErrback)
314 return d
315
317 """
318 Add a message to the component.
319 Since this is called in a component view and only relevant to the
320 component view, the message only exists in the view, and is not
321 replicated to the manager state.
322
323 The message will be displayed in the usual message view.
324
325 @type message: L{flumotion.common.messages.Message}
326 """
327 self.state.observe_append('messages', message)
328