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

Source Code for Module flumotion.admin.gtk.componentlist

  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,2008 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  """widget to display a list of components. 
 23  This file contains a collection of widgets used to compose the list 
 24  of components used in the administration interface. 
 25  It contains: 
 26    - ComponentList: a treeview + treemodel abstraction 
 27    - ContextMenu: the menu which pops up when you right click 
 28  """ 
 29   
 30  import gettext 
 31  import os 
 32   
 33  import gobject 
 34  import gtk 
 35  from zope.interface import implements 
 36   
 37  from flumotion.configure import configure 
 38  from flumotion.common import log, planet 
 39  from flumotion.common.planet import moods 
 40  from flumotion.common.pygobject import gsignal, gproperty 
 41  from flumotion.common.xmlwriter import cmpComponentType 
 42  from flumotion.twisted import flavors 
 43   
 44  __version__ = "$Rev: 7071 $" 
 45  _ = gettext.gettext 
 46  MOODS_INFO = { 
 47      moods.sad: _('Sad'), 
 48      moods.happy: _('Happy'), 
 49      moods.sleeping: _('Sleeping'), 
 50      moods.waking: _('Waking'), 
 51      moods.hungry: _('Hungry'), 
 52      moods.lost: _('Lost') 
 53      } 
 54   
 55  (COL_MOOD, 
 56   COL_NAME, 
 57   COL_WORKER, 
 58   COL_PID, 
 59   COL_STATE, 
 60   COL_MOOD_VALUE, # to sort COL_MOOD 
 61   COL_CPU) = range(7) 
 62   
 63   
64 -def getComponentLabel(state):
65 config = state.get('config') 66 return config and config.get('label', config['name'])
67 68
69 -class ComponentList(log.Loggable, gobject.GObject):
70 """ 71 I present a view on the list of components logged in to the manager. 72 """ 73 74 implements(flavors.IStateListener) 75 76 logCategory = 'components' 77 78 gsignal('selection-changed', object) # state-or-None 79 gsignal('show-popup-menu', int, int) # button, click time 80 81 gproperty(bool, 'can-start-any', 'True if any component can be started', 82 False) 83 gproperty(bool, 'can-stop-any', 'True if any component can be stopped', 84 False)
85 - def __init__(self, treeView):
86 """ 87 @param treeView: the gtk.TreeView to put the view in. 88 """ 89 gobject.GObject.__init__(self) 90 self.set_property('can-start-any', False) 91 self.set_property('can-stop-any', False) 92 93 self._iters = {} # componentState -> model iter 94 self._lastStates = None 95 self._moodPixbufs = self._getMoodPixbufs() 96 self._createUI(treeView)
97
98 - def _createUI(self, treeView):
99 treeView.connect('button-press-event', 100 self._view_button_press_event_cb) 101 treeView.set_headers_visible(True) 102 103 treeModel = gtk.ListStore( 104 gtk.gdk.Pixbuf, # mood 105 str, # name 106 str, # worker 107 str, # pid 108 object, # state 109 int, # mood-value 110 str, # cpu 111 ) 112 treeView.set_model(treeModel) 113 114 treeSelection = treeView.get_selection() 115 treeSelection.set_mode(gtk.SELECTION_MULTIPLE) 116 treeSelection.connect('changed', self._view_cursor_changed_cb) 117 118 # put in all the columns 119 col = gtk.TreeViewColumn('', gtk.CellRendererPixbuf(), 120 pixbuf=COL_MOOD) 121 col.set_sort_column_id(COL_MOOD_VALUE) 122 treeView.append_column(col) 123 124 col = gtk.TreeViewColumn(_('Component'), gtk.CellRendererText(), 125 text=COL_NAME) 126 col.set_sort_column_id(COL_NAME) 127 treeView.append_column(col) 128 129 col = gtk.TreeViewColumn(_('Worker'), gtk.CellRendererText(), 130 markup=COL_WORKER) 131 col.set_sort_column_id(COL_WORKER) 132 treeView.append_column(col) 133 134 t = gtk.CellRendererText() 135 col = gtk.TreeViewColumn(_('PID'), t, text=COL_PID) 136 col.set_sort_column_id(COL_PID) 137 treeView.append_column(col) 138 139 # PyGTK bug #479012 was fixed in 2.12.1 and prevents this from crashing 140 if gtk.pygtk_version >= (2, 12, 1): 141 treeView.props.has_tooltip = True 142 treeView.connect("query-tooltip", self._tree_view_query_tooltip_cb) 143 def selection_changed_cb(selection): 144 treeView.trigger_tooltip_query()
145 treeSelection.connect('changed', selection_changed_cb) 146 if hasattr(gtk.TreeView, 'set_rubber_banding'): 147 treeView.set_rubber_banding(False) 148 149 self._model = treeModel 150 self._view = treeView
151
152 - def getSelectedNames(self):
153 """ 154 Get the names of the currently selected component, or None. 155 156 @rtype: string 157 """ 158 return self._getSelected(COL_NAME)
159
160 - def getSelectedStates(self):
161 """ 162 Get the states of the currently selected component, or None. 163 164 @rtype: L{flumotion.common.component.AdminComponentState} 165 """ 166 return self._getSelected(COL_STATE)
167
168 - def getComponentNames(self):
169 """Fetches a list of all component names 170 @returns: component names 171 @rtype: list of strings 172 """ 173 names = [] 174 for row in self._model: 175 names.append(row[COL_NAME]) 176 return names
177
178 - def getComponentStates(self):
179 """Fetches a list of all component states 180 @returns: component states 181 @rtype: list of L{AdminComponentState} 182 """ 183 names = [] 184 for row in self._model: 185 names.append(row[COL_STATE]) 186 return names
187
188 - def canStart(self):
189 """ 190 Get whether the selected components can be started. 191 192 @rtype: bool 193 """ 194 states = self.getSelectedStates() 195 if not states: 196 return False 197 canStart = True 198 for state in states: 199 moodname = moods.get(state.get('mood')).name 200 canStart = canStart and moodname == 'sleeping' 201 return canStart
202
203 - def canStop(self):
204 """ 205 Get whether the selected components can be stoped. 206 207 @rtype: bool 208 """ 209 states = self.getSelectedStates() 210 if not states: 211 return False 212 canStop = True 213 for state in states: 214 moodname = moods.get(state.get('mood')).name 215 canStop = canStop and moodname != 'sleeping' 216 return canStop
217
218 - def clearAndRebuild(self, components):
219 """ 220 Update the components view by removing all old components and 221 showing the new ones. 222 223 @param components: dictionary of name -> 224 L{flumotion.common.component.AdminComponentState} 225 """ 226 # remove all Listeners 227 self._model.foreach(self._removeListenerForeach) 228 229 self.debug('updating components view') 230 # clear and rebuild 231 self._model.clear() 232 self._iters = {} 233 234 # FIXME: When we can depend on Python 2.4, use 235 # sorted(components.values(), 236 # cmp=cmpComponentType, 237 # key=operator.attrgetter('type')) 238 # 239 def componentSort(a, b): 240 return cmpComponentType(a.get('type'), 241 b.get('type'))
242 componentsSorted = components.values() 243 componentsSorted.sort(cmp=componentSort) 244 245 for component in componentsSorted: 246 self.debug('adding component %r to listview' % component) 247 component.addListener(self, set_=self.stateSet) 248 249 titer = self._model.append() 250 self._iters[component] = titer 251 252 mood = component.get('mood') 253 self.debug('component has mood %r' % mood) 254 messages = component.get('messages') 255 self.debug('component has messages %r' % messages) 256 257 if mood != None: 258 self._setMoodValue(titer, mood) 259 260 self._model.set(titer, COL_STATE, component) 261 262 self._model.set(titer, COL_NAME, getComponentLabel(component)) 263 264 pid = component.get('pid') 265 self._model.set(titer, COL_PID, (pid and str(pid)) or '') 266 267 self._updateWorker(titer, component) 268 self.debug('updated components view') 269 270 self._updateStartStop() 271 272 # IStateListener implementation 273
274 - def stateSet(self, state, key, value):
275 if not isinstance(state, planet.AdminComponentState): 276 self.warning('Got state change for unknown object %r' % state) 277 return 278 279 titer = self._iters[state] 280 self.log('stateSet: state %r, key %s, value %r' % (state, key, value)) 281 282 if key == 'mood': 283 self._setMoodValue(titer, value) 284 self._updateWorker(titer, state) 285 elif key == 'name': 286 if value: 287 self._model.set(titer, COL_NAME, value) 288 elif key == 'workerName': 289 self._updateWorker(titer, state) 290 elif key == 'pid': 291 self._model.set(titer, COL_PID, (value and str(value) or ''))
292 293 # Private 294
295 - def _updateStartStop(self):
296 oldstop = self.get_property('can-stop-any') 297 oldstart = self.get_property('can-start-any') 298 moodnames = [moods.get(x[COL_MOOD_VALUE]).name for x in self._model] 299 canStop = bool([x for x in moodnames if (x!='sleeping')]) 300 canStart = bool([x for x in moodnames if (x=='sleeping')]) 301 if oldstop != canStop: 302 self.set_property('can-stop-any', canStop) 303 if oldstart != canStart: 304 self.set_property('can-start-any', canStart)
305
306 - def _updateWorker(self, titer, componentState):
307 # update the worker name: 308 # - italic [any worker] if no workerName/workerRequested 309 # - italic if workerName, or no workerName but workerRequested 310 # - normal if running 311 312 workerName = componentState.get('workerName') 313 workerRequested = componentState.get('workerRequested') 314 if not workerName: 315 workerName = "%s" % workerRequested 316 if not workerRequested: 317 workerName = _("[any worker]") 318 319 mood = componentState.get('mood') 320 markup = workerName 321 if mood == moods.sleeping.value: 322 markup = "<i>%s</i>" % workerName 323 self._model.set(titer, COL_WORKER, markup)
324
325 - def _removeListenerForeach(self, model, path, titer):
326 # remove the listener for each state object 327 state = model.get(titer, COL_STATE)[0] 328 state.removeListener(self)
329
330 - def _setMoodValue(self, titer, value):
331 """ 332 Set the mood value on the given component name. 333 334 @type value: int 335 """ 336 self._model.set(titer, COL_MOOD, self._moodPixbufs[value]) 337 self._model.set(titer, COL_MOOD_VALUE, value) 338 339 self._updateStartStop()
340
341 - def _getSelected(self, col_name):
342 selection = self._view.get_selection() 343 if not selection: 344 return None 345 model, selected_tree_rows = selection.get_selected_rows() 346 selected = [] 347 for tree_row in selected_tree_rows: 348 component_state = model[tree_row][col_name] 349 selected.append(component_state) 350 return selected
351
352 - def _tooltipsGetContext(self, treeview, keyboard_tip, x, y):
353 if keyboard_tip: 354 path = treeview.get_cursor() 355 if not path: 356 return 357 else: 358 x, y = treeview.convert_widget_to_bin_window_coords(x, y) 359 path = treeview.get_path_at_pos(x, y) 360 if not path: 361 return 362 363 return path
364
365 - def _getMoodPixbufs(self):
366 # load all pixbufs for the moods 367 pixbufs = {} 368 for i in range(0, len(moods)): 369 name = moods.get(i).name 370 pixbufs[i] = gtk.gdk.pixbuf_new_from_file_at_size( 371 os.path.join(configure.imagedir, 'mood-%s.png' % name), 372 24, 24) 373 374 return pixbufs
375
376 - def _selectionChanged(self):
377 states = self.getSelectedStates() 378 379 if not states: 380 self.debug('no component selected, emitting selection-changed None') 381 # Emit this in an idle, since popups will not be shown 382 # before this has completed, and it might possibly take a long 383 # time to finish all the callbacks connected to selection-changed 384 # This is not the proper fix, but makes the popups show up faster 385 gobject.idle_add(self.emit, 'selection-changed', []) 386 return 387 388 if states is self._lastStates: 389 self.debug('no new components selected, no emitting signal') 390 return 391 392 self.debug('components selected, emitting selection-changed') 393 self.emit('selection-changed', states) 394 self._lastStates = states
395
396 - def _updateTooltip(self, path, tooltip):
397 mood = self._model[path[0]][COL_MOOD_VALUE] 398 tooltip.set_markup("<b>%s</b>" % _("Component is %s") % ( 399 MOODS_INFO[moods.get(mood)]))
400
401 - def _showPopupMenu(self, event):
402 selection = self._view.get_selection() 403 retval = self._view.get_path_at_pos(int(event.x), int(event.y)) 404 if retval is None: 405 selection.unselect_all() 406 return 407 clicked_path = retval[0] 408 selected_path = selection.get_selected_rows()[1] 409 if clicked_path not in selected_path: 410 selection.unselect_all() 411 selection.select_path(clicked_path) 412 self.emit('show-popup-menu', event.button, event.time)
413 414 # Callbacks 415
416 - def _tree_view_query_tooltip_cb(self, treeview, x, y, keyboard_tip, 417 tooltip):
418 path = self._tooltipsGetContext(treeview, keyboard_tip, x, y) 419 if path is None: 420 return 421 self._updateTooltip(path, tooltip) 422 return True
423
424 - def _view_cursor_changed_cb(self, *args):
425 self._selectionChanged()
426
427 - def _view_button_press_event_cb(self, treeview, event):
428 if event.button == 3: 429 self._showPopupMenu(event) 430 return True 431 return False
432 433 434 gobject.type_register(ComponentList) 435