Package flumotion :: Package admin :: Package gtk :: Module componentview
[hide private]

Source Code for Module flumotion.admin.gtk.componentview

  1  # -*- Mode: Python -*- 
  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  """widget holder displaying a component specific views""" 
 19   
 20  import gettext 
 21  import os 
 22   
 23  import gobject 
 24  import gtk 
 25   
 26  from flumotion.common import componentui, log, errors, messages 
 27  from flumotion.common.common import pathToModuleName 
 28  from flumotion.common.planet import AdminComponentState, moods 
 29  from flumotion.common.i18n import N_, gettexter 
 30  from twisted.internet import defer 
 31  from gettext import gettext as _ 
 32   
 33  T_ = gettexter() 
 34   
 35  # ensure unjellier registered 
 36  componentui # pyflakes 
 37   
 38  __version__ = "$Rev$" 
 39  _ = gettext.gettext 
 40  _DEBUG_ONLY_PAGES = ['Eaters', 'Feeders', 'Properties'] 
 41  (COMPONENT_UNSET, 
 42   COMPONENT_INACTIVE, 
 43   COMPONENT_ACTIVE) = range(3) 
 44   
 45   
46 -class Placeholder(object):
47 """A placeholder contains a Widget subclass of a specific 48 component. 49 """ 50
51 - def getWidget(self):
52 raise NotImplementedError( 53 "%r must implement a getWidget() method")
54
55 - def setDebugEnabled(self, enabled):
56 """Set if debug should be enabled. 57 Not all pages are visible unless debugging is set to true 58 @param enable: if debug should be enabled 59 """
60
61 - def removed(self):
62 """Called when the placeholder is inactivated, eg 63 detached from the parent"""
64 65
66 -class SingleNodePlaceholder(Placeholder, log.Loggable):
67 """This is a placeholder containing a vbox 68 """ 69 logCategory = 'nodebook' 70
71 - def __init__(self, admingtk):
72 """ 73 @param admingtk: the GTK Admin with its nodes 74 @type admingtk: L{flumotion.component.base.admin_gtk.BaseAdminGtk} 75 """ 76 self._debugEnabled = False 77 self._admingtk = admingtk 78 self.widget = self._admingtk.getWidget() 79 self.widget.show()
80 81 # BaseComponentHolder 82
83 - def getWidget(self):
84 return self.widget
85
86 - def removed(self):
87 if self._admingtk: 88 # needed for compatibility with managers with old code 89 if hasattr(self._admingtk, 'cleanup'): 90 self._admingtk.cleanup() 91 self._admingtk = None
92 93
94 -class NotebookPlaceholder(Placeholder, log.Loggable):
95 """This is a placeholder containing a notebook with tabs 96 """ 97 logCategory = 'nodebook' 98
99 - def __init__(self, admingtk):
100 """ 101 @param admingtk: the GTK Admin with its nodes 102 @type admingtk: L{flumotion.component.base.admin_gtk.BaseAdminGtk} 103 """ 104 self._debugEnabled = False 105 self._admingtk = admingtk 106 self._notebook = None 107 self._pageWidgets = {} 108 109 self._notebook = gtk.Notebook() 110 admingtk.setup() 111 self.nodes = admingtk.getNodes() 112 self._appendPages() 113 self._notebook.show()
114 115 # BaseComponentHolder 116
117 - def getWidget(self):
118 return self._notebook
119
120 - def removed(self):
121 if self._admingtk: 122 # needed for compatibility with managers with old code 123 if hasattr(self._admingtk, 'cleanup'): 124 self._admingtk.cleanup() 125 self._admingtk = None
126
127 - def setDebugEnabled(self, enabled):
128 self._debugEnabled = enabled 129 if self._admingtk: 130 self._admingtk.setDebugEnabled(enabled) 131 for name in _DEBUG_ONLY_PAGES: 132 widget = self._pageWidgets.get(name) 133 if widget is None: 134 continue 135 widget.set_property('visible', enabled)
136
137 - def _renderWidget(self, widget, table):
138 # dumb dumb dumb dumb 139 old_parent = widget.get_parent() 140 if old_parent: 141 old_parent.remove(widget) 142 map(table.remove, table.get_children()) 143 table.add(widget) 144 widget.show()
145
146 - def _addPage(self, name):
147 node = self.nodes.get(name) 148 assert node is not None, name 149 150 table = gtk.Table(1, 1) 151 table.add(gtk.Label(_('Loading UI for %s...') % name)) 152 label = self._getTitleLabel(node, name) 153 label.show() 154 self._notebook.append_page(table, label) 155 156 d = node.render() 157 d.addCallback(self._renderWidget, table) 158 return table
159
160 - def _appendPages(self):
161 for name in self.nodes.keys(): 162 table = self._addPage(name) 163 self._pageWidgets[name] = table 164 165 if name in _DEBUG_ONLY_PAGES: 166 if self._debugEnabled: 167 continue 168 table.show()
169
170 - def _getTitleLabel(self, node, name):
171 title = node.title 172 if not title: 173 # FIXME: we have no way of showing an error message ? 174 # This should be added so users can file bugs. 175 self.warning("Component node %s does not have a " 176 "translatable title. Please file a bug." % name) 177 178 # fall back for now 179 title = name 180 181 return gtk.Label(title)
182 183
184 -class LabelPlaceholder(Placeholder):
185 """This is a placeholder with a label, with or without a text""" 186
187 - def __init__(self, text=''):
188 self._label = gtk.Label(text)
189
190 - def getWidget(self):
191 return self._label
192 193
194 -class PlanetPlaceholder(Placeholder):
195 """This is a placeholder used to display a Planet""" 196
197 - def __init__(self):
198 self._widget = gtk.Label('')
199
200 - def getWidget(self):
201 return self._widget
202 203
204 -class MultipleAdminComponentStates(log.Loggable):
205 """ 206 I represent the state of a list of components in the admin client. 207 See L{flumotion.common.planet.AdminComponentState}. 208 """ 209
210 - def __init__(self, states):
211 self._componentStates = states 212 self._state = dict(mood=moods.happy, 213 name='multiple-components', 214 type='MultipleComponents')
215
216 - def addListener(self, *args, **kwargs):
217 for state in self._componentStates: 218 try: 219 state.addListener(*args, **kwargs) 220 except KeyError, e: 221 self.debug('Error adding listener for component %s', 222 state.get('name'))
223
224 - def removeListener(self, listener):
225 for state in self._componentStates: 226 try: 227 state.removeListener(listener) 228 except KeyError: 229 self.debug('Error removing listener for component %s', 230 state.get('name'))
231
232 - def hasKey(self, key):
233 return key in self._state.keys()
234
235 - def get(self, key):
236 return self._state.get(key, None)
237
238 - def getComponentStates(self):
239 return self._componentStates
240 241
242 -class ComponentView(gtk.VBox, log.Loggable):
243 logCategory = 'componentview' 244
245 - def __init__(self):
246 gtk.VBox.__init__(self) 247 self._admin = None 248 self._currentComponentState = None 249 self._currentPlaceholder = None 250 self._debugEnabled = False 251 self._state = COMPONENT_UNSET 252 253 self._planetPlaceholder = PlanetPlaceholder() 254 self._addPlaceholder(self._planetPlaceholder)
255 256 # Public API 257
258 - def getDebugEnabled(self):
259 """Find out if debug is enabled 260 @returns: if debug is enabled 261 @rtype: bool 262 """ 263 return self._debugEnabled
264
265 - def setDebugEnabled(self, enabled):
266 """Sets if debug should be enabled 267 @param enabled: if debug should be enabled 268 @type enabled: bool 269 """ 270 self._debugEnabled = enabled 271 if self._currentPlaceholder: 272 self._currentPlaceholder.setDebugEnabled(enabled)
273
274 - def activateComponent(self, component):
275 """Activates a component in the view 276 @param component: component to show 277 @type component: L{flumotion.common.component.AdminComponentState} 278 """ 279 self._setState(COMPONENT_UNSET) 280 if component: 281 self._currentComponentState = component 282 self._setState(COMPONENT_INACTIVE)
283
284 - def setSingleAdmin(self, admin):
285 """ 286 Sets a single global admin for the component view 287 288 @param admin: the admin 289 @type admin: L{flumotion.admin.admin.AdminModel} 290 """ 291 self._admin = admin
292
293 - def getAdminForComponent(self, component):
294 """ 295 Get the admin for a specific component 296 297 @param component: component 298 @type component: L{flumotion.common.component.AdminComponentState} 299 300 @returns: the admin 301 @rtype: L{flumotion.admin.admin.AdminModel} 302 """ 303 # override me to do e.g. multi.getAdminForComponent 304 return self._admin
305 306 # Private 307
308 - def _addPlaceholder(self, placeholder):
309 if not isinstance(placeholder, Placeholder): 310 raise AssertionError( 311 "placeholder must be a Placeholder subclass, not %r" % ( 312 placeholder, )) 313 314 widget = placeholder.getWidget() 315 widget.show() 316 map(self.remove, self.get_children()) 317 self.pack_start(widget, True, True) 318 319 placeholder.setDebugEnabled(self._debugEnabled) 320 self._currentPlaceholder = placeholder
321
322 - def _removePlaceholder(self, placeholder):
323 widget = placeholder.getWidget() 324 self.remove(widget) 325 326 placeholder.removed()
327
328 - def _getWidgetConstructor(self, componentState):
329 330 def noBundle(failure): 331 failure.trap(errors.NoBundleError) 332 self.debug( 333 'No specific GTK admin for this component, using default') 334 return ("flumotion/component/base/admin_gtk.py", "BaseAdminGtk")
335 336 def oldVersion(failure): 337 # This is compatibility with platform-3 338 # FIXME: It would be better to do this using strict 339 # version checking of the manager 340 341 # File ".../flumotion/manager/admin.py", line 278, in 342 # perspective_getEntryByType 343 # exceptions.AttributeError: 'str' object has no attribute 'get' 344 failure.trap(AttributeError) 345 346 return admin.callRemote( 347 'getEntryByType', componentState, 'admin/gtk')
348 349 def gotEntryPoint((filename, procname)): 350 # The manager always returns / as a path separator, replace them 351 # with the separator since the rest of our infrastructure depends 352 # on that. 353 filename = filename.replace('/', os.path.sep) 354 # getEntry for admin/gtk returns a factory function for creating 355 # flumotion.component.base.admin_gtk.BaseAdminGtk 356 # subclass instances 357 modname = pathToModuleName(filename) 358 359 # we call hostile code, so we should handle exceptions: 360 d = admin.getBundledFunction(modname, procname) 361 d.addErrback(admin.bundleErrback, filename) 362 363 def handleSyntaxError(failure): 364 failure.trap(errors.EntrySyntaxError) 365 msg = failure.value.args[0] 366 367 m = messages.Error(T_( 368 N_("This component has a UI bug.")), debug=msg) 369 componentState.observe_append('messages', m) 370 371 raise errors.HandledException(failure.value) 372 373 d.addErrback(handleSyntaxError) 374 375 return d 376 377 def gotFactory(factory, placeholder=NotebookPlaceholder): 378 # instantiate from factory and wrap in a NotebookPlaceHolder 379 widget = factory(componentState, admin) 380 return placeholder(widget) 381 382 def sleepingComponent(failure): 383 failure.trap(errors.SleepingComponentError) 384 return LabelPlaceholder(_("Component '%s' is still sleeping.") % 385 componentState.get('name')) 386 387 def noMultipleComponents(failure): 388 # admin connected to an old manager without multiple 389 # components view 390 failure.trap(errors.RemoteRunError) 391 return LabelPlaceholder() 392 393 def handledExceptionErrback(failure): 394 # already handle, so let call chain short-circuit here and 395 # just return 396 failure.trap(errors.HandledException) 397 return LabelPlaceholder(_("Component '%s' has a UI bug.") % 398 componentState.get('name')) 399 400 if isinstance(componentState, AdminComponentState): 401 admin = self.getAdminForComponent(componentState) 402 componentType = componentState.get('type') 403 d = admin.callRemote('getEntryByType', componentType, 'admin/gtk') 404 d.addErrback(oldVersion) 405 d.addErrback(noBundle) 406 d.addCallback(gotEntryPoint) 407 d.addCallback(gotFactory) 408 d.addErrback(sleepingComponent) 409 d.addErrback(handledExceptionErrback) 410 return d 411 elif isinstance(componentState, MultipleAdminComponentStates): 412 admin = self.getAdminForComponent(componentState) 413 d = gotEntryPoint(("flumotion/component/base/multiple.py", 414 "MultipleComponentsAdminGtk")) 415 d.addCallback(gotFactory, SingleNodePlaceholder) 416 d.addErrback(sleepingComponent) 417 d.addErrback(handledExceptionErrback) 418 d.addErrback(noMultipleComponents) 419 return d 420 else: 421 return defer.succeed(LabelPlaceholder()) 422
423 - def _componentUnsetToInactive(self):
424 425 def invalidate(_): 426 self._setState(COMPONENT_UNSET)
427 428 def set_(state, key, value): 429 if key != 'mood': 430 return 431 if value not in [moods.lost.value, 432 moods.sleeping.value, 433 moods.sad.value]: 434 self._setState(COMPONENT_ACTIVE) 435 else: 436 self._setState(COMPONENT_INACTIVE) 437 438 current = self._currentComponentState 439 assert current is not None 440 current.addListener(self, invalidate=invalidate, set_=set_) 441 if current.hasKey('mood'): 442 set_(current, 'mood', current.get('mood')) 443
444 - def _componentInactiveToActive(self):
445 446 def gotWidgetConstructor(placeholder, oldComponentState): 447 if oldComponentState != self._currentComponentState: 448 # in the time that _get_widget_constructor was running, 449 # perhaps the user selected another component; only update 450 # the ui if that did not happen 451 self.debug('ignoring component %r, state %d, state %r/%r' % ( 452 placeholder, self._state, 453 oldComponentState, self._currentComponentState)) 454 return 455 self._removePlaceholder(self._planetPlaceholder) 456 self._addPlaceholder(placeholder)
457 458 d = self._getWidgetConstructor(self._currentComponentState) 459 d.addCallback(gotWidgetConstructor, self._currentComponentState) 460
461 - def _componentActiveToInactive(self):
462 self._removePlaceholder(self._currentPlaceholder) 463 self._addPlaceholder(self._planetPlaceholder)
464
465 - def _componentInactiveToUnset(self):
466 if self._currentComponentState: 467 self._currentComponentState.removeListener(self) 468 self._currentComponentState = None
469
470 - def _setState(self, state):
471 uptable = [self._componentUnsetToInactive, 472 self._componentInactiveToActive] 473 downtable = [self._componentInactiveToUnset, 474 self._componentActiveToInactive] 475 if self._state < state: 476 while self._state < state: 477 self.log('component %r state change: %d++', 478 self._currentComponentState, self._state) 479 self._state += 1 480 uptable[self._state - 1]() 481 else: 482 while self._state > state: 483 self.log('component %r state change: %d--', 484 self._currentComponentState, self._state) 485 self._state -= 1 486 downtable[self._state]()
487 488 gobject.type_register(ComponentView) 489