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