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