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