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 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 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$" 
 39  T_ = gettexter() 
 40   
 41   
42 -class BaseAdminGtkNode(log.Loggable):
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 ## Relative path of the glade file. 56 ## e.g. "flumotion/ui.glade" 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 # glade.XML instance (optionally set) 75 self.widget = None # the top level widget that will be visible 76 self.uiState = None # set if we are listening 77 self._pendingUIState = None # set if we are waiting for the ui 78 # to load 79 ## Absolute path to the glade file. 80 ## e.g. "/home/flu/.flumotion/cache/test/80...df7/flumotion/ui.glade 81 self._gladefilepath = None
82
83 - def setDebugEnabled(self, enabled):
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
92 - def cleanup(self):
93 if self.uiState: 94 self.uiState.removeListener(self)
95
96 - def status_push(self, str):
97 if self.statusbar: 98 return self.statusbar.push('notebook', str)
99
100 - def status_pop(self, mid):
101 if self.statusbar: 102 return self.statusbar.remove('notebook', mid)
103
104 - def callRemote(self, methodName, *args, **kwargs):
105 return self.admin.componentCallRemote(self.state, methodName, 106 *args, **kwargs)
107 108 # FIXME: do this automatically if there is a gladeFile class attr set 109
110 - def loadGladeFile(self, gladeFile, domain=configure.PACKAGE):
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 # The manager is always using / as a path separator, to avoid 137 # confusion, convert os.path.sep -> / here. 138 gladeFile = gladeFile.replace(os.path.sep, '/') 139 # FIXME: this does needless roundtrips; should instead be 140 # loading from the already-downloaded paths 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
146 - def getWidget(self, name):
147 if not self.wtree: 148 raise IndexError 149 widget = self.wtree.get_widget(name) 150 if not widget: 151 self.warning('Could not get widget %s' % name) 152 153 return widget
154
155 - def createWidget(self, name):
156 """ 157 Create a new widget instance from the glade file. 158 Can be used to make multiple instances of the same widget. 159 """ 160 if not self._gladefilepath: 161 raise IndexError 162 wtree = gtk.glade.XML(self._gladefilepath, name, 163 typedict=ProxyWidgetMapping()) 164 widget = wtree.get_widget(name) 165 if not widget: 166 self.warning('Could not create widget %s' % name) 167 168 return widget
169
170 - def haveWidgetTree(self):
171 """ 172 I am called when the widget tree has been gotten from the glade 173 file. Responsible for setting self.widget. 174 175 Override me to act on it. 176 """ 177 pass
178
179 - def gotUIState(self, state):
180 if self.widget: 181 self.setUIState(state) 182 else: 183 self._pendingUIState = state
184
185 - def setUIState(self, state):
186 """ 187 Called by the BaseAdminGtk when it gets the UI state and the GUI 188 is ready. Chain up if you provide your own implementation. 189 """ 190 self.uiState = state 191 state.addListener(self, set_=self.stateSet, append=self.stateAppend, 192 remove=self.stateRemove, setitem=self.stateSetitem, 193 delitem=self.stateDelitem)
194
195 - def stateSet(self, state, key, value):
196 "Override me" 197 pass
198
199 - def stateAppend(self, state, key, value):
200 "Override me" 201 pass
202
203 - def stateRemove(self, state, key, value):
204 "Override me" 205 pass
206
207 - def stateSetitem(self, state, key, subkey, value):
208 "Override me" 209 pass
210
211 - def stateDelitem(self, state, key, subkey, value):
212 "Override me" 213 pass
214
215 - def render(self):
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 # clear up previous error messages 224 allmessages = self.state.get('messages', []) 225 for message in allmessages: 226 # since we can have multiple nodes, only remove the one from 227 # ours; this assumes each node's title is unique for a component 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 # add an error message to the component and return 234 # an error label, given a debug string 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 # if we don't set this error as our label, we will raise 246 # a TypeError below and obscure this more meaningful error 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
303 - def addMessage(self, message):
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