Package flumotion :: Package component :: Package base :: Module baseadminnode
[hide private]

Source Code for Module flumotion.component.base.baseadminnode

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_feedcomponent010 -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 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   
38 -class BaseAdminGtkNode(log.Loggable):
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 ## Relative path of the glade file. 52 ## e.g. "flumotion/ui.glade" 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 # glade.XML instance (optionally set) 71 self.widget = None # the top level widget that will be visible 72 self.uiState = None # set if we are listening 73 self._pendingUIState = None # set if we are waiting for the ui 74 # to load 75 ## Absolute path to the glade file. 76 ## e.g. "/home/flu/.flumotion/cache/test/80...df7/flumotion/ui.glade 77 self._gladefilepath = None
78
79 - def setDebugEnabled(self, enabled):
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
88 - def cleanup(self):
89 if self.uiState: 90 self.uiState.removeListener(self)
91
92 - def status_push(self, str):
93 if self.statusbar: 94 return self.statusbar.push('notebook', str)
95
96 - def status_pop(self, mid):
97 if self.statusbar: 98 return self.statusbar.remove('notebook', mid)
99
100 - def callRemote(self, methodName, *args, **kwargs):
101 return self.admin.componentCallRemote(self.state, methodName, 102 *args, **kwargs)
103 104 # FIXME: do this automatically if there is a gladeFile class attr set 105
106 - def loadGladeFile(self, gladeFile, domain=configure.PACKAGE):
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 # The manager is always using / as a path separator, to avoid 133 # confusion, convert os.path.sep -> / here. 134 gladeFile = gladeFile.replace(os.path.sep, '/') 135 # FIXME: this does needless roundtrips; should instead be 136 # loading from the already-downloaded paths 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
142 - def getWidget(self, name):
143 if not self.wtree: 144 raise IndexError 145 widget = self.wtree.get_widget(name) 146 if not widget: 147 self.warning('Could not get widget %s' % name) 148 149 return widget
150
151 - def createWidget(self, name):
152 """ 153 Create a new widget instance from the glade file. 154 Can be used to make multiple instances of the same widget. 155 """ 156 if not self._gladefilepath: 157 raise IndexError 158 wtree = gtk.glade.XML(self._gladefilepath, name, 159 typedict=ProxyWidgetMapping()) 160 widget = wtree.get_widget(name) 161 if not widget: 162 self.warning('Could not create widget %s' % name) 163 164 return widget
165
166 - def haveWidgetTree(self):
167 """ 168 I am called when the widget tree has been gotten from the glade 169 file. Responsible for setting self.widget. 170 171 Override me to act on it. 172 """ 173 pass
174
175 - def gotUIState(self, state):
176 if self.widget: 177 self.setUIState(state) 178 else: 179 self._pendingUIState = state
180
181 - def setUIState(self, state):
182 """ 183 Called by the BaseAdminGtk when it gets the UI state and the GUI 184 is ready. Chain up if you provide your own implementation. 185 """ 186 self.uiState = state 187 state.addListener(self, set_=self.stateSet, append=self.stateAppend, 188 remove=self.stateRemove, setitem=self.stateSetitem, 189 delitem=self.stateDelitem)
190
191 - def stateSet(self, state, key, value):
192 "Override me" 193 pass
194
195 - def stateAppend(self, state, key, value):
196 "Override me" 197 pass
198
199 - def stateRemove(self, state, key, value):
200 "Override me" 201 pass
202
203 - def stateSetitem(self, state, key, subkey, value):
204 "Override me" 205 pass
206
207 - def stateDelitem(self, state, key, subkey, value):
208 "Override me" 209 pass
210
211 - def render(self):
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 # clear up previous error messages 220 allmessages = self.state.get('messages', []) 221 for message in allmessages: 222 # since we can have multiple nodes, only remove the one from 223 # ours; this assumes each node's title is unique for a component 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 # add an error message to the component and return 230 # an error label, given a debug string 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 # if we don't set this error as our label, we will raise 242 # a TypeError below and obscure this more meaningful error 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
299 - def addMessage(self, message):
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