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: 8746 $"
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 if not self.gladeFile:
253 return defer.succeed(None)
254
255 def haveWtree(wtree):
256 self.wtree = wtree
257 self.debug('render: calling haveWidgetTree')
258 try:
259 self.haveWidgetTree()
260 except Exception, e:
261 return error(log.getExceptionMessage(e))
262
263 self.debug('render: loading glade file %s in text domain %s',
264 self.gladeFile, self.gettextDomain)
265
266 d = self.loadGladeFile(self.gladeFile, self.gettextDomain)
267 d.addCallback(haveWtree)
268 return d
269
270 def loadGladeFileErrback(failure):
271 if failure.check(RuntimeError):
272 return error(
273 'Could not load glade file %s.' % self.gladeFile)
274 if failure.check(errors.NoBundleError):
275 return error(
276 'No bundle found containing %s.' % self.gladeFile)
277
278 return failure
279
280 def renderFinishedCallback(_):
281 if not self.widget:
282 self.debug('render: no self.widget, failing')
283 raise TypeError(
284 '%r.haveWidgetTree should have set self.widget' %
285 self.__class__)
286
287 if self._pendingUIState:
288 self.debug('render: calling setUIState on the node')
289 self.setUIState(self._pendingUIState)
290
291 self.debug('renderFinished: returning widget %s', self.widget)
292 return self.widget
293
294 def renderFinishedErrback(failure):
295 return error(log.getFailureMessage(failure))
296
297 d = loadGladeFile()
298 d.addErrback(loadGladeFileErrback)
299 d.addCallback(renderFinishedCallback)
300 d.addErrback(renderFinishedErrback)
301 return d
302
304 """
305 Add a message to the component.
306 Since this is called in a component view and only relevant to the
307 component view, the message only exists in the view, and is not
308 replicated to the manager state.
309
310 The message will be displayed in the usual message view.
311
312 @type message: L{flumotion.common.messages.Message}
313 """
314 self.state.observe_append('messages', message)
315