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

Source Code for Module flumotion.component.base.admin_gtk

  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 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  """ 
 23  Base classes for component UI's using GTK+ 
 24  """ 
 25   
 26  import os 
 27  import time 
 28   
 29  import gtk 
 30  import gtk.glade 
 31   
 32  from twisted.python import util 
 33  from twisted.internet import defer 
 34   
 35  from flumotion.common import errors, log, common 
 36  from flumotion.twisted import flavors 
 37  from flumotion.twisted.defer import defer_generator_method 
 38  from flumotion.twisted.compat import implements 
 39   
 40  from gettext import gettext as _ 
 41   
42 -class BaseAdminGtk(log.Loggable):
43 """ 44 I am a base class for all GTK+-based Admin views. 45 I am a view on one component's properties. 46 47 @type nodes: L{twisted.python.util.OrderedDict} 48 @ivar nodes: an ordered dict of name -> L{BaseAdminGtkNode} 49 """ 50 51 logCategory = "admingtk" 52
53 - def __init__(self, state, admin):
54 """ 55 @param state: state of component this is a UI for 56 @type state: L{flumotion.common.planet.AdminComponentState} 57 @type admin: L{flumotion.admin.admin.AdminModel} 58 @param admin: the admin model that interfaces with the manager for us 59 """ 60 self.state = state 61 self.name = state.get('name') 62 self.admin = admin 63 self.debug('creating admin gtk for state %r' % state) 64 self.uiState = None 65 self.nodes = util.OrderedDict() 66 67 d = admin.componentCallRemote(state, 'getUIState') 68 d.addCallback(self.setUIState)
69
70 - def setUIState(self, state):
71 self.debug('starting listening to state %r', state) 72 state.addListener(self, self.stateSet, self.stateAppend, 73 self.stateRemove) 74 self.uiState = state 75 for node in self.getNodes().values(): 76 node.gotUIState(state) 77 self.uiStateChanged(state)
78
79 - def propertyErrback(self, failure, window):
80 failure.trap(errors.PropertyError) 81 self.warning("%s." % failure.getErrorMessage()) 82 #window.error_dialog("%s." % failure.getErrorMessage()) 83 return None
84
85 - def setElementProperty(self, elementName, propertyName, value):
86 """ 87 Set the given property on the element with the given name. 88 """ 89 d = self.admin.setProperty(self.state, elementName, propertyName, value) 90 d.addErrback(self.propertyErrback, self) 91 return d
92
93 - def getElementProperty(self, elementName, propertyName):
94 """ 95 Get the value of the given property of the element with the given name. 96 97 Returns: L{twisted.internet.defer.Deferred} returning the value. 98 """ 99 d = self.admin.getProperty(self.state, elementName, propertyName) 100 d.addErrback(self.propertyErrback, self) 101 return d
102
103 - def callRemote(self, methodName, *args, **kwargs):
104 return self.admin.componentCallRemote(self.state, methodName, 105 *args, **kwargs)
106
107 - def propertyChanged(self, name, value):
108 """ 109 Override this method to be notified of component's properties that 110 have changed. 111 112 I am meant to be overridden. 113 """ 114 self.debug("property %s changed to %r" % (name, value))
115 116 # FIXME: .setup() is subclassable, while .render() on nodes has 117 # haveWidgetTree. choose one of the two patterns in general
118 - def setup(self):
119 """ 120 Set up the admin view so it can display nodes. 121 """ 122 self.debug('BaseAdminGtk.setup()') 123 124 config = self.state.get('config') 125 if config['feed']: 126 self.debug("Component has feeders, show Feeders node") 127 self.nodes['Feeders'] = FeedersAdminGtkNode(self.state, self.admin) 128 129 if 'source' in config: 130 self.debug("Component has eaters, show Eaters node") 131 self.nodes['Eaters'] = EatersAdminGtkNode(self.state, self.admin) 132 133 # set up translations 134 if not hasattr(self, 'gettext_domain'): 135 yield None 136 137 lang = common.getLL() 138 self.debug("loading bundle for %s locales" % lang) 139 bundleName = '%s-locale-%s' % (self.gettext_domain, lang) 140 d = self.admin.bundleLoader.getBundleByName(bundleName) 141 yield d 142 143 try: 144 localedatadir = d.value() 145 except errors.NoBundleError: 146 self.debug("Failed to find locale bundle %s" % bundleName) 147 yield None 148 149 localeDir = os.path.join(localedatadir, 'locale') 150 self.debug("Loading locales for %s from %s" % ( 151 self.gettext_domain, localeDir)) 152 gettext.bindtextdomain(self.gettext_domain, localeDir) 153 gtk.glade.bindtextdomain(self.gettext_domain, localeDir) 154 yield None
155 setup = defer_generator_method(setup) 156
157 - def getNodes(self):
158 """ 159 Return a dict of admin UI nodes. 160 """ 161 return self.nodes
162 163 # FIXME: deprecated
164 - def render(self):
165 """ 166 Render the GTK+ admin view for this component and return the 167 main widget for embedding. 168 """ 169 raise NotImplementedError
170
171 - def uiStateChanged(self, stateObject):
172 # so, this is still here, but I'd prefer people to (1) just use 173 # the nodes and not the global admin; and (2) use the state 174 # listener stuff more than the chunkier 'uistatechanged' 175 pass
176
177 - def stateSet(self, object, key, value):
178 self.uiStateChanged(object)
179
180 - def stateAppend(self, object, key, value):
181 self.uiStateChanged(object)
182
183 - def stateRemove(self, object, key, value):
184 self.uiStateChanged(object)
185
186 -class BaseAdminGtkNode(log.Loggable):
187 """ 188 I am a base class for all GTK+-based Admin UI nodes. 189 I am a view on a set of properties for a component. 190 191 @ivar widget: the main widget representing this node 192 @type widget: L{gtk.Widget} 193 @ivar wtree: the widget tree representation for this node 194 """ 195 196 implements(flavors.IStateListener) 197 198 logCategory = "admingtk" 199 glade_file = None ## Relative path of the glade file. 200 ## e.g. "flumotion/ui.glade" 201 gettext_domain = 'flumotion' 202
203 - def __init__(self, state, admin, title=None):
204 """ 205 @param state: state of component this is a UI node for 206 @type state: L{flumotion.common.planet.AdminComponentState} 207 @param admin: the admin model that interfaces with the manager for us 208 @type admin: L{flumotion.admin.admin.AdminModel} 209 @param title: the (translated) title to show this node with 210 @type title: str 211 """ 212 self.state = state 213 self.admin = admin 214 self.statusbar = None 215 self.title = title 216 self.nodes = util.OrderedDict() 217 self.wtree = None 218 self.widget = None 219 self.uiState = None 220 ## Absolute path to the glade file. 221 ## e.g. "/home/flu/.flumotion/cache/test/80...df7/flumotion/ui.glade 222 self._gladefilepath = None
223 224
225 - def status_push(self, str):
226 if self.statusbar: 227 return self.statusbar.push('notebook', str)
228
229 - def status_pop(self, mid):
230 if self.statusbar: 231 return self.statusbar.remove('notebook', mid)
232
233 - def propertyErrback(self, failure, window):
234 failure.trap(errors.PropertyError) 235 self.warning("%s." % failure.getErrorMessage()) 236 #window.error_dialog("%s." % failure.getErrorMessage()) 237 return None
238
239 - def setElementProperty(self, elementName, propertyName, value):
240 """ 241 Set the given property on the element with the given name. 242 """ 243 d = self.admin.setProperty(self.state, elementName, propertyName, value) 244 d.addErrback(self.propertyErrback, self) 245 return d
246
247 - def getElementProperty(self, elementName, propertyName):
248 """ 249 Get the value of the given property of the element with the given name. 250 251 Returns: L{twisted.internet.defer.Deferred} returning the value. 252 """ 253 d = self.admin.getProperty(self.state, elementName, propertyName) 254 d.addErrback(self.propertyErrback, self) 255 return d
256
257 - def callRemote(self, methodName, *args, **kwargs):
258 return self.admin.componentCallRemote(self.state, methodName, 259 *args, **kwargs)
260 261 # FIXME: do this automatically if there is a gladeFile class attr set
262 - def loadGladeFile(self, gladeFile, domain='flumotion'):
263 """ 264 Returns: a deferred returning the widget tree from the glade file. 265 """ 266 def _getBundledFileCallback(result, gladeFile): 267 path = result 268 if not os.path.exists(path): 269 self.warning("Glade file %s not found in path %s" % ( 270 gladeFile, path)) 271 self.debug("Switching glade text domain to %s" % domain) 272 self.debug("loading widget tree from %s" % path) 273 self._gladefilepath = path 274 old = gtk.glade.textdomain() 275 gtk.glade.textdomain(domain) 276 self.wtree = gtk.glade.XML(path) 277 self.debug("Switching glade text domain back to %s" % old) 278 gtk.glade.textdomain(old) 279 return self.wtree
280 281 self.debug("requesting bundle for glade file %s" % gladeFile) 282 d = self.admin.bundleLoader.getFile(gladeFile) 283 d.addCallback(_getBundledFileCallback, gladeFile) 284 return d
285
286 - def getWidget(self, name):
287 if not self.wtree: 288 raise IndexError 289 widget = self.wtree.get_widget(name) 290 if not widget: 291 self.warning('Could not get widget %s' % name) 292 293 return widget
294
295 - def createWidget(self, name):
296 """ 297 Create a new widget instance from the glade file. 298 Can be used to make multiple instances of the same widget. 299 """ 300 if not self._gladefilepath: 301 raise IndexError 302 wtree = gtk.glade.XML(self._gladefilepath, name) 303 widget = wtree.get_widget(name) 304 if not widget: 305 self.warning('Could not create widget %s' % name) 306 307 return widget
308
309 - def haveWidgetTree(self):
310 """ 311 I am called when the widget tree has been gotten from the glade 312 file. Responsible for setting self.widget. 313 314 Override me to act on it. 315 """ 316 pass
317
318 - def propertyChanged(self, name, value):
319 """ 320 I am meant to be overridden. 321 """ 322 self.debug("property %s changed to %r" % (name, value))
323
324 - def gotUIState(self, state):
325 self.uiState = state 326 if self.widget: 327 self.setUIState(self.uiState)
328
329 - def setUIState(self, state):
330 """ 331 Called by the BaseAdminGtk when it gets the UI state and the GUI 332 is ready. Chain up if you provide your own implementation. 333 """ 334 self.uiState = state 335 state.addListener(self, self.stateSet, self.stateAppend, 336 self.stateRemove, self.stateSetitem, 337 self.stateDelitem)
338
339 - def stateSet(self, state, key, value):
340 "Override me" 341 pass
342
343 - def stateAppend(self, state, key, value):
344 "Override me" 345 pass
346
347 - def stateRemove(self, state, key, value):
348 "Override me" 349 pass
350
351 - def stateSetitem(self, state, key, subkey, value):
352 "Override me" 353 pass
354
355 - def stateDelitem(self, state, key, subkey, value):
356 "Override me" 357 pass
358
359 - def render(self):
360 """ 361 Render the GTK+ admin view for this component. 362 363 Returns: a deferred returning the main widget for embedding 364 """ 365 if self.glade_file: 366 self.debug('render: loading glade file %s in text domain %s' % ( 367 self.glade_file, self.gettext_domain)) 368 dl = self.loadGladeFile(self.glade_file, self.gettext_domain) 369 yield dl 370 371 try: 372 self.wtree = dl.value() 373 except RuntimeError: 374 msg = 'Could not load glade file %s' % self.glade_file 375 self.warning(msg) 376 yield gtk.Label("%s. Kill the programmer." % msg) 377 378 self.debug('render: calling haveWidgetTree') 379 self.haveWidgetTree() 380 381 if not self.widget: 382 self.debug('render: no self.widget, failing') 383 yield defer.fail(IndexError) 384 385 if self.uiState: 386 self.debug('calling setUIState on the node') 387 self.setUIState(self.uiState) 388 389 self.debug('render: yielding widget %s' % self.widget) 390 yield self.widget
391 render = defer_generator_method(render) 392 393 # this class is a bit of an experiment 394 # editor's note: "experiment" is an excuse for undocumented and uncommented
395 -class _StateWatcher(object):
396 - def __init__(self, state, setters, appenders, removers, 397 setitemers=None, delitemers=None):
398 self.state = state 399 self.setters = setters 400 self.appenders = appenders 401 self.removers = removers 402 self.setitemers = setitemers 403 self.delitemers = delitemers 404 self.shown = False 405 406 state.addListener(self, set=self.onSet, append=self.onAppend, 407 remove=self.onRemove, setitem=self.onSetItem, 408 delitem=self.onDelItem) 409 410 for k in appenders: 411 for v in state.get(k): 412 self.onAppend(state, k, v)
413
414 - def hide(self):
415 if self.shown: 416 for k in self.setters: 417 self.onSet(self.state, k, None) 418 self.shown = False
419
420 - def show(self):
421 # "show" the watcher by triggering all the registered setters 422 if not self.shown: 423 self.shown = True 424 for k in self.setters: 425 self.onSet(self.state, k, self.state.get(k))
426
427 - def onSet(self, obj, k, v):
428 if self.shown and k in self.setters: 429 self.setters[k](self.state, v)
430
431 - def onAppend(self, obj, k, v):
432 if k in self.appenders: 433 self.appenders[k](self.state, v)
434
435 - def onRemove(self, obj, k, v):
436 if k in self.removers: 437 self.removers[k](self.state, v)
438
439 - def onSetItem(self, obj, k, sk, v):
440 if self.shown and k in self.setitemers: 441 self.setitemers[k](self.state, sk, v)
442
443 - def onDelItem(self, obj, k, sk, v):
444 if self.shown and k in self.setitemers: 445 self.setitemers[k](self.state, sk, v)
446
447 - def unwatch(self):
448 if self.state: 449 self.hide() 450 for k in self.removers: 451 for v in self.state.get(k): 452 self.onRemove(self.state, k, v) 453 self.state.removeListener(self) 454 self.state = None
455
456 -class FeedersAdminGtkNode(BaseAdminGtkNode):
457 glade_file = os.path.join('flumotion', 'component', 'base', 'feeders.glade') 458
459 - def __init__(self, state, admin):
460 BaseAdminGtkNode.__init__(self, state, admin, title=_("Feeders")) 461 # tree model is a model of id, uiState, _StateWatcher, type 462 # tree model contains feeders and their feeder clients 463 # type is a str, 'feeder' or 'client' 464 self.treemodel = None 465 self.treeview = None 466 self.selected = None 467 self.labels = {} 468 self._lastConnect = 0 469 self._lastDisconnect = 0
470
471 - def select(self, watcher):
472 if self.selected: 473 self.selected.hide() 474 if watcher: 475 self.selected = watcher 476 self.selected.show() 477 else: 478 self.selected = None
479
480 - def setFeederName(self, state, value):
481 self.labels['feeder-name'].set_markup(_('Feeder <b>%s</b>') % value)
482
483 - def setFeederClientName(self, state, value):
484 self.labels['feeder-name'].set_markup(_('Feeding to <b>%s</b>') 485 % value)
486
487 - def setFeederClientBytesReadCurrent(self, state, value):
488 txt = value and (common.formatStorage(value) + _('Byte')) or '' 489 self.labels['bytes-read-current'].set_text(txt) 490 self.updateConnectionTime() 491 self.updateDisconnectionTime()
492
493 - def setFeederClientBuffersDroppedCurrent(self, state, value):
494 if value is None: 495 # no support for counting dropped buffers 496 value = _("Unknown") 497 self.labels['buffers-dropped-current'].set_text(str(value)) 498 self.updateConnectionTime() 499 self.updateDisconnectionTime()
500
501 - def setFeederClientBytesReadTotal(self, state, value):
502 txt = value and (common.formatStorage(value) + _('Byte')) or '' 503 self.labels['bytes-read-total'].set_text(txt)
504
505 - def setFeederClientBuffersDroppedTotal(self, state, value):
506 if value is None: 507 # no support for counting dropped buffers 508 value = _("Unknown") 509 self.labels['buffers-dropped-total'].set_text(str(value))
510
511 - def setFeederClientReconnects(self, state, value):
512 self.labels['connections-total'].set_text(str(value))
513
514 - def setFeederClientLastConnect(self, state, value):
515 if value: 516 text = time.strftime("%c", time.localtime(value)) 517 self.labels['connected-since'].set_text(text) 518 self._lastConnect = value 519 self.updateConnectionTime()
520
521 - def setFeederClientLastDisconnect(self, state, value):
522 if value: 523 text = time.strftime("%c", time.localtime(value)) 524 self.labels['disconnected-since'].set_text(text) 525 self._lastDisconnect = value 526 self.updateDisconnectionTime()
527
528 - def setFeederClientLastActivity(self, state, value):
529 if value: 530 text = time.strftime("%c", time.localtime(value)) 531 self.labels['last-activity'].set_text(text)
532
533 - def setFeederClientFD(self, state, value):
534 if value == None: 535 # disconnected 536 self._table_connected.hide() 537 self._table_disconnected.show() 538 else: 539 self._table_disconnected.hide() 540 self._table_connected.show()
541 542 # FIXME: add a timeout to update this ?
543 - def updateConnectionTime(self):
544 if self._lastConnect: 545 text = common.formatTime(time.time() - self._lastConnect) 546 self.labels['connection-time'].set_text(text)
547 548 # FIXME: add a timeout to update this ?
549 - def updateDisconnectionTime(self):
550 if self._lastDisconnect: 551 text = common.formatTime(time.time() - self._lastDisconnect) 552 self.labels['disconnection-time'].set_text(text)
553
554 - def addFeeder(self, uiState, state):
555 """ 556 @param uiState: the component's uiState 557 @param state: the feeder's uiState 558 """ 559 feederId = state.get('feedId') 560 i = self.treemodel.append(None) 561 self.treemodel.set(i, 0, feederId, 1, state) 562 w = _StateWatcher(state, 563 {'feedId': self.setFeederName}, 564 {'clients': self.addFeederClient}, 565 {'clients': self.removeFeederClient}) 566 self.treemodel.set(i, 2, w, 3, 'feeder') 567 self.treeview.expand_all()
568
569 - def addFeederClient(self, feederState, state):
570 """ 571 @param uiState: the component's uiState 572 @param state: the feeder client's uiState 573 """ 574 575 clientId = state.get('clientId') 576 for row in self.treemodel: 577 if self.treemodel.get_value(row.iter, 1) == feederState: 578 break 579 i = self.treemodel.append(row.iter) 580 self.treemodel.set(i, 0, clientId, 1, state) 581 w = _StateWatcher(state, { 582 'clientId': self.setFeederClientName, 583 'bytesReadCurrent': self.setFeederClientBytesReadCurrent, 584 'buffersDroppedCurrent': self.setFeederClientBuffersDroppedCurrent, 585 'bytesReadTotal': self.setFeederClientBytesReadTotal, 586 'buffersDroppedTotal': self.setFeederClientBuffersDroppedTotal, 587 'reconnects': self.setFeederClientReconnects, 588 'lastConnect': self.setFeederClientLastConnect, 589 'lastDisconnect': self.setFeederClientLastDisconnect, 590 'lastActivity': self.setFeederClientLastActivity, 591 'fd': self.setFeederClientFD, 592 }, {}, {}) 593 self.treemodel.set(i, 2, w, 3, 'client') 594 self.treeview.expand_all()
595
596 - def removeFeederClient(self, feederState, state):
597 for row in self.treemodel: 598 if self.treemodel.get_value(row.iter, 1) == feederState: 599 break 600 for row in row.iterchildren(): 601 if self.treemodel.get_value(row.iter, 1) == state: 602 break 603 state, watcher = self.treemodel.get(row.iter, 1, 2) 604 if watcher == self.selected: 605 self.select(None) 606 watcher.unwatch() 607 self.treemodel.remove(row.iter)
608
609 - def setUIState(self, state):
610 # will only be called when we have a widget tree 611 BaseAdminGtkNode.setUIState(self, state) 612 self.widget.show_all() 613 for feeder in state.get('feeders'): 614 self.addFeeder(state, feeder)
615
616 - def haveWidgetTree(self):
617 self.labels = {} 618 self.widget = self.wtree.get_widget('feeders-widget') 619 self.treeview = self.wtree.get_widget('treeview-feeders') 620 self.treemodel = gtk.TreeStore(str, object, object, str) 621 self.treeview.set_model(self.treemodel) 622 col = gtk.TreeViewColumn('Feeder', gtk.CellRendererText(), 623 text=0) 624 self.treeview.append_column(col) 625 sel = self.treeview.get_selection() 626 sel.set_mode(gtk.SELECTION_SINGLE) 627 def sel_changed(sel): 628 model, i = sel.get_selected() 629 self.select(i and model.get_value(i, 2)) 630 # don't show the feeder client stuff for a feeder 631 if model.get_value(i, 3) == 'feeder': 632 self._table_feedclient.hide() 633 else: 634 self._table_feedclient.show()
635 636 sel.connect('changed', sel_changed) 637 638 def set_label(name): 639 self.labels[name] = self.wtree.get_widget('label-' + name) 640 # zeroes out all value labels 641 self.labels[name].set_text('')
642 643 set_label('feeder-name') 644 for type in ( 645 'bytes-read-current', 'buffers-dropped-current', 646 'connected-since', 'connection-time', 647 'disconnected-since', 'disconnection-time', 648 'bytes-read-total', 'buffers-dropped-total', 649 'connections-total', 'last-activity', 650 ): 651 set_label(type) 652 653 self._table_connected = self.wtree.get_widget('table-current-connected') 654 self._table_disconnected = self.wtree.get_widget( 655 'table-current-disconnected') 656 self._table_feedclient = self.wtree.get_widget('table-feedclient') 657 self._table_connected.hide() 658 self._table_disconnected.hide() 659 self._table_feedclient.hide() 660 self.wtree.get_widget('box-right').hide() 661 662 # FIXME: do not show all; 663 # hide bytes fed and buffers dropped until something is selected 664 # see #575 665 return self.widget 666
667 -class EatersAdminGtkNode(BaseAdminGtkNode):
668 glade_file = os.path.join('flumotion', 'component', 'base', 'eaters.glade') 669
670 - def __init__(self, state, admin):
671 BaseAdminGtkNode.__init__(self, state, admin, title=_("Eaters")) 672 # tree model is a model of id, uiState, _StateWatcher 673 # tree model contains eaters 674 self.treemodel = None 675 self.treeview = None 676 self._selected = None # the watcher of the currently selected row 677 self.labels = {} 678 self._lastConnect = 0 679 self._lastDisconnect = 0
680
681 - def select(self, watcher):
682 if self._selected: 683 self._selected.hide() 684 if watcher: 685 self._selected = watcher 686 self._selected.show() 687 else: 688 self._selected = None
689
690 - def _setEaterFD(self, state, value):
691 if value is None: 692 self._table_connected.hide() 693 self._table_disconnected.show() 694 else: 695 self._table_disconnected.hide() 696 self._table_connected.show()
697
698 - def _setEaterName(self, state, value):
699 self.labels['eater-name'].set_markup(_('Eater <b>%s</b>') % value)
700
701 - def _setEaterBytesReadCurrent(self, state, value):
702 txt = value and (common.formatStorage(value) + _('Byte')) or '' 703 self.labels['bytes-read-current'].set_text(txt) 704 self._updateConnectionTime() 705 self._updateDisconnectionTime()
706
707 - def _setEaterConnectionItem(self, state, key, value):
708 # timestamps 709 if key == 'countTimestampDiscont': 710 self.labels['timestamp-discont-count-current'].set_text(str(value)) 711 if value > 0: 712 self._expander_discont_current.show() 713 elif key == 'timeTimestampDiscont': 714 text = time.strftime("%c", time.localtime(value)) 715 self.labels['timestamp-discont-time-current'].set_text(text) 716 if value is not None: 717 self._vbox_timestamp_discont_current.show() 718 elif key == 'lastTimestampDiscont': 719 text = common.formatTime(value, fractional=9) 720 self.labels['timestamp-discont-last-current'].set_text(text) 721 if value > 0.0: 722 self._vbox_timestamp_discont_current.show() 723 elif key == 'totalTimestampDiscont': 724 text = common.formatTime(value, fractional=9) 725 self.labels['timestamp-discont-total-current'].set_text(text) 726 if value > 0.0: 727 self._vbox_timestamp_discont_current.show() 728 elif key == 'timestampTimestampDiscont': 729 if value is None: 730 return 731 text = common.formatTime(value, fractional=9) 732 self.labels['timestamp-discont-timestamp-current'].set_text(text) 733 # offsets 734 elif key == 'countOffsetDiscont': 735 self.labels['offset-discont-count-current'].set_text(str(value)) 736 if value > 0: 737 self._expander_discont_current.show() 738 elif key == 'timeOffsetDiscont': 739 text = time.strftime("%c", time.localtime(value)) 740 self.labels['offset-discont-time-current'].set_text(text) 741 if value is not None: 742 self._vbox_offset_discont_current.show() 743 elif key == 'lastOffsetDiscont': 744 text = _("%d units") % value 745 self.labels['offset-discont-last-current'].set_text(text) 746 if value > 0: 747 self._vbox_offset_discont_current.show() 748 elif key == 'totalOffsetDiscont': 749 text = _("%d units") % value 750 self.labels['offset-discont-total-current'].set_text(text) 751 if value > 0: 752 self._vbox_offset_discont_current.show() 753 elif key == 'offsetOffsetDiscont': 754 if value is None: 755 return 756 text = _("%d units") % value 757 self.labels['offset-discont-offset-current'].set_text(text) 758 if value > 0: 759 self._vbox_offset_discont_current.show()
760
761 - def _setEaterCountTimestampDiscont(self, state, value):
762 if value is None: 763 return 764 self.labels['timestamp-discont-count-total'].set_text(str(value)) 765 if value > 0.0: 766 self._expander_discont_total.show()
767
768 - def _setEaterTotalTimestampDiscont(self, state, value):
769 if value is None: 770 return 771 text = common.formatTime(value, fractional=9) 772 self.labels['timestamp-discont-total'].set_text(text) 773 if value > 0.0: 774 self._vbox_timestamp_discont_total.show()
775
776 - def _setEaterCountOffsetDiscont(self, state, value):
777 if value is None: 778 return 779 self.labels['offset-discont-count-total'].set_text(str(value)) 780 if value != 0: 781 self._expander_discont_total.show()
782
783 - def _setEaterTotalOffsetDiscont(self, state, value):
784 if value is None: 785 return 786 text = _("%d units") % value 787 self.labels['offset-discont-total'].set_text(text) 788 if value != 0: 789 self._vbox_offset_discont_total.show()
790
791 - def _setEaterLastConnect(self, state, value):
792 if value: 793 text = time.strftime("%c", time.localtime(value)) 794 self.labels['connected-since'].set_text(text) 795 self._table_connected.show() 796 self._table_disconnected.hide() 797 self._lastConnect = value 798 self._updateConnectionTime()
799
800 - def _setEaterTotalConnections(self, state, value):
801 self.labels['connections-total'].set_text(str(value))
802 803 # when we initially get the uiState, connection is an already set dict 804 # this makes sure we handle getting that dict initially
805 - def _setEaterConnection(self, state, value):
806 # can be called with None value due to StateWatcher 807 if value is None: 808 return 809 for k, v in value.items(): 810 self._setEaterConnectionItem(state, k, v)
811 812 # FIXME: add a timeout to update this ?
813 - def _updateConnectionTime(self):
814 if self._lastConnect: 815 text = common.formatTime(time.time() - self._lastConnect) 816 self.labels['connection-time'].set_text(text)
817 818 # FIXME: add a timeout to update this ?
819 - def _updateDisconnectionTime(self):
820 if self._lastDisconnect: 821 text = common.formatTime(time.time() - self._lastDisconnect) 822 self.labels['disconnection-time'].set_text(text)
823
824 - def addEater(self, uiState, state):
825 """ 826 @param uiState: the component's uiState 827 @param state: the eater's uiState 828 """ 829 eaterId = state.get('eaterId') 830 i = self.treemodel.append(None) 831 self.treemodel.set(i, 0, eaterId, 1, state) 832 w = _StateWatcher(state, 833 { 834 'fd': self._setEaterFD, 835 'eaterId': self._setEaterName, 836 'lastConnect': self._setEaterLastConnect, 837 'countTimestampDiscont': self._setEaterCountTimestampDiscont, 838 'totalTimestampDiscont': self._setEaterTotalTimestampDiscont, 839 'countOffsetDiscont': self._setEaterCountOffsetDiscont, 840 'totalOffsetDiscont': self._setEaterTotalOffsetDiscont, 841 'totalConnections': self._setEaterTotalConnections, 842 # need to have a setter for connection to be able to show 843 # it initially 844 'connection': self._setEaterConnection, 845 }, 846 {}, 847 {}, 848 setitemers={ 849 'connection': self._setEaterConnectionItem, 850 }, 851 delitemers={ 852 } 853 ) 854 self.treemodel.set(i, 2, w)
855
856 - def setUIState(self, state):
857 # will only be called when we have a widget tree 858 BaseAdminGtkNode.setUIState(self, state) 859 #self.widget.show_all() 860 for eater in state.get('eaters'): 861 self.addEater(state, eater)
862
863 - def haveWidgetTree(self):
864 self.labels = {} 865 self.widget = self.wtree.get_widget('eaters-widget') 866 self.treeview = self.wtree.get_widget('treeview-eaters') 867 # tree model is a model of id, uiState, _StateWatcher 868 self.treemodel = gtk.TreeStore(str, object, object) 869 self.treeview.set_model(self.treemodel) 870 col = gtk.TreeViewColumn('Eater', gtk.CellRendererText(), 871 text=0) 872 self.treeview.append_column(col) 873 sel = self.treeview.get_selection() 874 sel.set_mode(gtk.SELECTION_SINGLE) 875 876 # get to know and set labels 877 def set_label(name): 878 self.labels[name] = self.wtree.get_widget('label-' + name) 879 if self.labels[name] is None: 880 raise KeyError(name) 881 # zeroes out all value labels 882 self.labels[name].set_text('')
883 884 set_label('eater-name') 885 for type in ( 886 'connected-since', 'connection-time', 887 'timestamp-discont-timestamp-current', 888 'offset-discont-offset-current', 889 'timestamp-discont-count-current', 'offset-discont-count-current', 890 'timestamp-discont-total-current', 'offset-discont-total-current', 891 'timestamp-discont-last-current', 'offset-discont-last-current', 892 'timestamp-discont-time-current', 'offset-discont-time-current', 893 'timestamp-discont-count-total', 'offset-discont-count-total', 894 'timestamp-discont-total', 'offset-discont-total', 895 'connections-total', 896 ): 897 set_label(type) 898 899 # handle selection changes on the tree widget 900 def sel_changed(sel): 901 model, i = sel.get_selected() 902 self.select(i and model.get_value(i, 2)) 903 self.wtree.get_widget('box-right').show()
904 905 sel.connect('changed', sel_changed) 906 907 # manage visibility of parts of the widget 908 self._table_connected = self.wtree.get_widget('table-current-connected') 909 self._table_disconnected = self.wtree.get_widget( 910 'table-current-disconnected') 911 self._table_eater = self.wtree.get_widget('table-eater') 912 self._expander_discont_current = self.wtree.get_widget( 913 'expander-discont-current') 914 self._vbox_timestamp_discont_current = self.wtree.get_widget( 915 'vbox-timestamp-discont-current') 916 self._vbox_offset_discont_current = self.wtree.get_widget( 917 'vbox-offset-discont-current') 918 919 self._expander_discont_total = self.wtree.get_widget( 920 'expander-discont-total') 921 self._vbox_timestamp_discont_total = self.wtree.get_widget( 922 'vbox-timestamp-discont-total') 923 self._vbox_offset_discont_total = self.wtree.get_widget( 924 'vbox-offset-discont-total') 925 926 # show the tree view always 927 self.wtree.get_widget('scrolledwindow').show_all() 928 929 # hide the specifics of the eater 930 self._expander_discont_current.hide() 931 self._table_connected.hide() 932 self._table_disconnected.hide() 933 self._expander_discont_total.hide() 934 935 # show the right box only when an eater is selected 936 self.wtree.get_widget('box-right').hide() 937 938 # FIXME: do not show all; 939 # hide bytes fed and buffers dropped until something is selected 940 # see #575 941 self.widget.show() 942 return self.widget 943
944 -class EffectAdminGtkNode(BaseAdminGtkNode):
945 """ 946 I am a base class for all GTK+-based component effect Admin UI nodes. 947 I am a view on a set of properties for an effect on a component. 948 """
949 - def __init__(self, state, admin, effectName, title=None):
950 """ 951 @param state: state of component this is a UI for 952 @type state: L{flumotion.common.planet.AdminComponentState} 953 @param admin: the admin model that interfaces with the manager for us 954 @type admin: L{flumotion.admin.admin.AdminModel} 955 """ 956 BaseAdminGtkNode.__init__(self, state, admin, title) 957 self.effectName = effectName
958
959 - def effectCallRemote(self, methodName, *args, **kwargs):
960 return self.admin.componentCallRemote(self.state, 961 "effect", self.effectName, methodName, *args, **kwargs)
962