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