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

Source Code for Module flumotion.admin.gtk.parts

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_parts -*- 
  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  import os 
 23   
 24  from gettext import gettext as _ 
 25   
 26  import gobject 
 27  import gtk 
 28  import gtk.glade 
 29   
 30  from flumotion.configure import configure 
 31  from flumotion.common import log, planet, pygobject 
 32  from flumotion.twisted import flavors 
 33  from flumotion.twisted.compat import implements 
 34  from flumotion.common.planet import moods 
 35  from flumotion.common.pygobject import gsignal, gproperty 
 36  from flumotion.common.pygobject import with_construct_properties 
 37   
 38  COL_MOOD       = 0 
 39  COL_NAME       = 1 
 40  COL_WORKER     = 2 
 41  COL_PID        = 3 
 42  COL_STATE      = 4 
 43  COL_MOOD_VALUE = 5 # to sort COL_MOOD 
 44  COL_CPU        = 6 
 45   
46 -class AdminStatusbar:
47 """ 48 I implement the status bar used in the admin UI. 49 """
50 - def __init__(self, widget):
51 """ 52 @param widget: a gtk.Statusbar to wrap. 53 """ 54 self._widget = widget 55 56 self._cids = {} # hash of context -> context id 57 self._mids = {} # hash of context -> message id lists 58 self._contexts = ['main', 'notebook'] 59 60 for context in self._contexts: 61 self._cids[context] = widget.get_context_id(context) 62 self._mids[context] = []
63
64 - def clear(self, context=None):
65 """ 66 Clear the status bar for the given context, or for all contexts 67 if none specified. 68 """ 69 if context: 70 self._clear_context(context) 71 return 72 73 for context in self._contexts: 74 self._clear_context(context)
75
76 - def push(self, context, message):
77 """ 78 Push the given message for the given context. 79 80 @returns: message id 81 """ 82 mid = self._widget.push(self._cids[context], message) 83 self._mids[context].append(mid) 84 return mid
85
86 - def pop(self, context):
87 """ 88 Pop the last message for the given context. 89 90 @returns: message id popped, or None 91 """ 92 if len(self._mids[context]): 93 mid = self._mids[context].pop() 94 self._widget.remove(self._cids[context], mid) 95 return mid 96 97 return None
98
99 - def set(self, context, message):
100 """ 101 Replace the current top message for this context with this new one. 102 103 @returns: the message id of the message pushed 104 """ 105 self.pop(context) 106 return self.push(context, message)
107
108 - def remove(self, context, mid):
109 """ 110 Remove the message with the given id from the given context. 111 112 @returns: whether or not the given mid was valid. 113 """ 114 if not mid in self._mids[context]: 115 return False 116 117 self._mids[context].remove(mid) 118 self._widget.remove(self._cids[context], mid) 119 return True
120
121 - def _clear_context(self, context):
122 if not context in self._cids.keys(): 123 return 124 125 for mid in self._mids[context]: 126 self.remove(context, mid)
127
128 -class ComponentsView(log.Loggable, gobject.GObject):
129 """ 130 I present a view on the list of components logged in to the manager. 131 """ 132 133 implements(flavors.IStateListener) 134 135 logCategory = 'components' 136 137 gsignal('has-selection', object) # state-or-None 138 gsignal('activated', object, str) # state, action name 139 #gsignal('right-clicked', object, int, float) 140 141 gproperty(bool, 'can-start-any', 'True if any component can be started', 142 False, construct=True) 143 gproperty(bool, 'can-stop-any', 'True if any component can be stopped', 144 False, construct=True) 145 _model = _view = _moodPixbufs = None # i heart pychecker 146
147 - def __init__(self, tree_widget):
148 """ 149 @param tree_widget: the gtk.TreeWidget to put the view in. 150 """ 151 self.__gobject_init__() 152 153 self._view = tree_widget 154 self._model = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str, object, int, str) 155 156 self._view.connect('cursor-changed', self._view_cursor_changed_cb) 157 self._view.connect('button-press-event', 158 self._view_button_press_event_cb) 159 self._view.set_model(self._model) 160 self._view.set_headers_visible(True) 161 162 self._add_columns() 163 164 self._moodPixbufs = self._getMoodPixbufs() 165 self._iters = {} # componentState -> model iter 166 self._last_state = None
167 __init__ = with_construct_properties (__init__) 168
169 - def _add_columns(self):
170 # put in all the columns 171 col = gtk.TreeViewColumn(_('Mood'), gtk.CellRendererPixbuf(), 172 pixbuf=COL_MOOD) 173 col.set_sort_column_id(COL_MOOD_VALUE) 174 self._view.append_column(col) 175 176 col = gtk.TreeViewColumn(_('Component'), gtk.CellRendererText(), 177 text=COL_NAME) 178 col.set_sort_column_id(COL_NAME) 179 self._view.append_column(col) 180 181 col = gtk.TreeViewColumn(_('Worker'), gtk.CellRendererText(), 182 markup=COL_WORKER) 183 col.set_sort_column_id(COL_WORKER) 184 self._view.append_column(col) 185 186 def type_pid_datafunc(column, cell, model, iter): 187 state = model.get_value(iter, COL_STATE) 188 pid = state.get('pid') 189 cell.set_property('text', pid and str(pid) or '')
190 191 t = gtk.CellRendererText() 192 col = gtk.TreeViewColumn('PID', t, text=COL_PID) 193 col.set_cell_data_func(t, type_pid_datafunc) 194 col.set_sort_column_id(COL_PID) 195 self._view.append_column(col) 196 197 def type_cpu_datafunc(column, cell, model, iter): 198 state = model.get_value(iter, COL_STATE) 199 cpu = state.get('cpu') 200 if isinstance(cpu, float): 201 cell.set_property('text', '%.2f' % (cpu * 100.0)) 202 else: 203 cell.set_property('text', '')
204 205 t = gtk.CellRendererText() 206 col = gtk.TreeViewColumn('CPU %', t, text=COL_CPU) 207 col.set_cell_data_func(t, type_cpu_datafunc) 208 col.set_sort_column_id(COL_CPU) 209 self._view.append_column(col) 210 211 # the additional columns need not be added 212 213 # load all pixbufs for the moods
214 - def _getMoodPixbufs(self):
215 pixbufs = {} 216 for i in range(0, len(moods)): 217 name = moods.get(i).name 218 pixbufs[i] = gtk.gdk.pixbuf_new_from_file(os.path.join( 219 configure.imagedir, 'mood-%s.png' % name)) 220 221 return pixbufs
222
223 - def _view_cursor_changed_cb(self, *args):
224 # name needs to exist before being used in the child functions 225 state = self.get_selected_state() 226 227 if not state: 228 self.debug('no component selected, emitting has-selection None') 229 self.emit('has-selection', None) 230 return 231 232 if state == self._last_state: 233 return 234 235 self._last_state = state 236 self.debug('component selected, emitting has-selection') 237 self.emit('has-selection', state)
238
239 - def _view_button_press_event_cb(self, treeview, event):
240 # right-click ? 241 if event.button != 3: 242 return 243 244 # get iter from coordinates 245 x = int(event.x) 246 y = int(event.y) 247 time = event.time 248 pthinfo = treeview.get_path_at_pos(x, y) 249 if pthinfo == None: 250 return 251 252 path, col, cellx, celly = pthinfo 253 model = treeview.get_model() 254 iter = model.get_iter(path) 255 state = model.get(iter, COL_STATE)[0] 256 257 popup = ComponentMenu(state) 258 popup.popup(None, None, None, event.button, time) 259 popup.connect('activated', self._activated_cb, state) 260 gtk.main_iteration()
261
262 - def _activated_cb(self, menu, action, state):
263 self.debug('emitting activated') 264 self.emit('activated', state, action)
265
266 - def get_selected_name(self):
267 """ 268 Get the name of the currently selected component, or None. 269 270 @rtype: string 271 """ 272 selection = self._view.get_selection() 273 sel = selection.get_selected() 274 if not sel: 275 return None 276 model, iter = sel 277 if not iter: 278 return 279 280 return model.get(iter, COL_NAME)[0]
281
282 - def get_selected_state(self):
283 """ 284 Get the state of the currently selected component, or None. 285 286 @rtype: L{flumotion.common.component.AdminComponentState} 287 """ 288 selection = self._view.get_selection() 289 if not selection: 290 return None 291 sel = selection.get_selected() 292 if not sel: 293 return None 294 model, iter = sel 295 if not iter: 296 return 297 298 return model.get(iter, COL_STATE)[0]
299
300 - def update_start_stop_props(self):
301 oldstop = self.get_property('can-stop-any') 302 oldstart = self.get_property('can-start-any') 303 moodnames = [moods.get(x[COL_MOOD_VALUE]).name for x in self._model] 304 can_stop = bool([x for x in moodnames if (x!='lost' and x!='sleeping')]) 305 can_start = bool([x for x in moodnames if (x=='sleeping')]) 306 if oldstop != can_stop: 307 self.set_property('can-stop-any', can_stop) 308 if oldstart != can_start: 309 self.set_property('can-start-any', can_start)
310
311 - def _removeListenerForeach(self, model, path, iter):
312 # remove the listener for each state object 313 state = model.get(iter, COL_STATE)[0] 314 state.removeListener(self)
315
316 - def update(self, components):
317 """ 318 Update the components view by removing all old components and 319 showing the new ones. 320 321 @param components: dictionary of name -> 322 L{flumotion.common.component.AdminComponentState} 323 """ 324 # remove all Listeners 325 self._model.foreach(self._removeListenerForeach) 326 327 self.debug('updating components view') 328 # clear and rebuild 329 self._model.clear() 330 self._iters = {} 331 332 # get a dictionary of components 333 names = components.keys() 334 names.sort() 335 336 for name in names: 337 component = components[name] 338 self.debug('adding component %r to listview' % component) 339 component.addListener(self, self.stateSet) 340 341 iter = self._model.append() 342 self._iters[component] = iter 343 344 mood = component.get('mood') 345 self.debug('component has mood %r' % mood) 346 messages = component.get('messages') 347 self.debug('component has messages %r' % messages) 348 349 if mood != None: 350 self._set_mood_value(iter, mood) 351 352 self._model.set(iter, COL_STATE, component) 353 354 self._model.set(iter, COL_NAME, component.get('name')) 355 356 self._updateWorker(iter, component) 357 self.debug('updated components view') 358 359 self.update_start_stop_props()
360
361 - def _updateWorker(self, iter, componentState):
362 # update the worker name: 363 # - italic [any worker] if no workerName/workerRequested 364 # - italic if workerName, or no workerName but workerRequested 365 # - normal if running 366 367 workerName = componentState.get('workerName') 368 workerRequested = componentState.get('workerRequested') 369 if not workerName: 370 workerName = "%s" % workerRequested 371 if not workerRequested: 372 workerName = _("[any worker]") 373 374 mood = componentState.get('mood') 375 markup = workerName 376 if mood == moods.sleeping.value: 377 markup = "<i>%s</i>" % workerName 378 self._model.set(iter, COL_WORKER, markup)
379
380 - def stateSet(self, state, key, value):
381 if not isinstance(state, planet.AdminComponentState): 382 self.warning('Got state change for unknown object %r' % state) 383 return 384 385 iter = self._iters[state] 386 self.log('stateSet: state %r, key %s, value %r' % (state, key, value)) 387 388 if key == 'mood': 389 self._set_mood_value(iter, value) 390 self._updateWorker(iter, state) 391 elif key == 'name': 392 if value: 393 self._model.set(iter, COL_NAME, value) 394 elif key == 'workerName': 395 self._updateWorker(iter, state) 396 elif key == 'cpu': 397 self._model.set(iter, COL_CPU, value)
398
399 - def _set_mood_value(self, iter, value):
400 """ 401 Set the mood value on the given component name. 402 403 @type value: int 404 """ 405 self._model.set(iter, COL_MOOD, self._moodPixbufs[value]) 406 self._model.set(iter, COL_MOOD_VALUE, value) 407 408 self.update_start_stop_props()
409 410 pygobject.type_register(ComponentsView) 411
412 -class ComponentMenu(gtk.Menu):
413 414 gsignal('activated', str) 415
416 - def __init__(self, state):
417 """ 418 @param state: L{flumotion.common.component.AdminComponentState} 419 """ 420 gtk.Menu.__init__(self) 421 self._items = {} # name -> gtk.MenuItem 422 423 self.set_title(_('Component')) 424 425 i = gtk.MenuItem(_('_Restart')) 426 self.append(i) 427 self._items['restart'] = i 428 429 i = gtk.MenuItem(_('_Start')) 430 mood = moods.get(state.get('mood')) 431 if mood == moods.happy: 432 i.set_property('sensitive', False) 433 self.append(i) 434 self._items['start'] = i 435 436 i = gtk.MenuItem(_('St_op')) 437 if mood == moods.sleeping: 438 i.set_property('sensitive', False) 439 self.append(i) 440 self._items['stop'] = i 441 442 self.append(gtk.SeparatorMenuItem()) 443 444 i = gtk.MenuItem(_('Reload _code')) 445 self.append(i) 446 self._items['reload'] = i 447 448 i = gtk.MenuItem(_('_Modify element property ...')) 449 self.append(i) 450 self._items['modify'] = i 451 452 # connect callback 453 for name in self._items.keys(): 454 i = self._items[name] 455 i.connect('activate', self._activated_cb, name) 456 457 self.show_all()
458
459 - def _activated_cb(self, item, name):
460 self.emit('activated', name)
461 462 pygobject.type_register(ComponentMenu) 463