1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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: 8152 $"
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 (COL_MOOD,
55 COL_NAME,
56 COL_WORKER,
57 COL_PID,
58 COL_STATE,
59 COL_MOOD_VALUE,
60 COL_CPU,
61 COL_TOOLTIP) = range(8)
62
63
67
68
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)
79 gsignal('show-popup-menu', int, int)
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
87 """
88 @param treeView: the gtk.TreeView to put the view in.
89 """
90 gobject.GObject.__init__(self)
91 self.set_property('can-start-any', False)
92 self.set_property('can-stop-any', False)
93
94 self._iters = {}
95 self._lastStates = None
96 self._model = None
97 self._workers = []
98 self._view = None
99 self._moodPixbufs = self._getMoodPixbufs()
100 self._createUI(treeView)
101
103 treeView.connect('button-press-event',
104 self._view_button_press_event_cb)
105 treeView.set_headers_visible(True)
106
107 treeModel = gtk.ListStore(
108 gtk.gdk.Pixbuf,
109 str,
110 str,
111 str,
112 object,
113 int,
114 str,
115 str,
116 )
117 treeView.set_model(treeModel)
118
119 treeSelection = treeView.get_selection()
120 treeSelection.set_mode(gtk.SELECTION_MULTIPLE)
121 treeSelection.connect('changed', self._view_cursor_changed_cb)
122
123
124 col = gtk.TreeViewColumn('', gtk.CellRendererPixbuf(),
125 pixbuf=COL_MOOD)
126 col.set_sort_column_id(COL_MOOD_VALUE)
127 treeView.append_column(col)
128
129 col = gtk.TreeViewColumn(_('Component'), gtk.CellRendererText(),
130 text=COL_NAME)
131 col.set_sort_column_id(COL_NAME)
132 treeView.append_column(col)
133
134 col = gtk.TreeViewColumn(_('Worker'), gtk.CellRendererText(),
135 markup=COL_WORKER)
136 col.set_sort_column_id(COL_WORKER)
137 treeView.append_column(col)
138
139 t = gtk.CellRendererText()
140 col = gtk.TreeViewColumn(_('PID'), t, text=COL_PID)
141 col.set_sort_column_id(COL_PID)
142 treeView.append_column(col)
143 if gtk.pygtk_version >= (2, 12):
144 treeView.set_tooltip_column(COL_TOOLTIP)
145
146 if hasattr(gtk.TreeView, 'set_rubber_banding'):
147 treeView.set_rubber_banding(False)
148
149 self._model = treeModel
150 self._view = treeView
151
153 """
154 Get the names of the currently selected component, or None.
155
156 @rtype: string
157 """
158 return self._getSelected(COL_NAME)
159
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
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
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
189 """
190 Get whether the selected components can be deleted.
191
192 Returns True if all components are sleeping.
193
194 Also returns False if no components are selected.
195
196 @rtype: bool
197 """
198 states = self.getSelectedStates()
199 if not states:
200 return False
201 canDelete = True
202 for state in states:
203 moodname = moods.get(state.get('mood')).name
204 workerName = state.get('workerRequested')
205 canDelete = canDelete and moodname == 'sleeping'
206 return canDelete
207
209 """
210 Get whether the selected components can be started.
211
212 @rtype: bool
213 """
214
215 if not self.canDelete():
216 return False
217
218 canStart = True
219 states = self.getSelectedStates()
220 for state in states:
221 workerName = state.get('workerRequested')
222 canStart = canStart and workerName in self._workers
223
224 return canStart
225
227 """
228 Get whether the selected components can be stoped.
229
230 @rtype: bool
231 """
232 states = self.getSelectedStates()
233 if not states:
234 return False
235 canStop = True
236 for state in states:
237 moodname = moods.get(state.get('mood')).name
238 canStop = canStop and moodname != 'sleeping'
239 return canStop
240
242 """
243 Update the components view by removing all old components and
244 showing the new ones.
245
246 @param components: dictionary of name ->
247 L{flumotion.common.component.AdminComponentState}
248 @param componentNameToSelect: name of the component to select or None
249 """
250
251 self._model.foreach(self._removeListenerForeach)
252
253 self.debug('updating components view')
254
255 self._model.clear()
256 self._iters = {}
257
258
259
260
261
262
263
264 def componentSort(a, b):
265 return cmpComponentType(a.get('type'),
266 b.get('type'))
267 componentsSorted = components.values()
268 componentsSorted.sort(cmp=componentSort)
269
270 for component in componentsSorted:
271 self.appendComponent(component, componentNameToSelect)
272
273 self.debug('updated components view')
274
305
314
315
316
318 if not isinstance(state, planet.AdminComponentState):
319 self.warning('Got state change for unknown object %r' % state)
320 return
321
322 titer = self._iters[state]
323 self.log('stateSet: state %r, key %s, value %r' % (state, key, value))
324
325 if key == 'mood':
326 self.debug('stateSet: mood of %r changed to %r' % (state, value))
327
328 if value == moods.sleeping.value:
329 self.debug('sleeping, removing local messages on %r' % state)
330 for message in state.get('messages', []):
331 state.observe_remove('messages', message)
332
333 self._setMoodValue(titer, value)
334 self._updateWorker(titer, state)
335 elif key == 'name':
336 if value:
337 self._model.set(titer, COL_NAME, value)
338 elif key == 'workerName':
339 self._updateWorker(titer, state)
340 elif key == 'pid':
341 self._model.set(titer, COL_PID, (value and str(value) or ''))
342
343
344
346 oldstop = self.get_property('can-stop-any')
347 oldstart = self.get_property('can-start-any')
348 moodnames = [moods.get(x[COL_MOOD_VALUE]).name for x in self._model]
349 canStop = bool([x for x in moodnames if (x!='sleeping')])
350 canStart = bool([x for x in moodnames if (x=='sleeping')])
351 if oldstop != canStop:
352 self.set_property('can-stop-any', canStop)
353 if oldstart != canStart:
354 self.set_property('can-start-any', canStart)
355
358
363
365
366
367
368
369 workerName = componentState.get('workerName')
370 workerRequested = componentState.get('workerRequested')
371 if not workerName and not workerRequested:
372
373
374 workerName = _("[any worker]")
375
376 markup = workerName or workerRequested
377 if markup not in self._workers:
378 self._model.set(titer, COL_TOOLTIP,
379 _("<b>Worker %s is not connected</b>") % markup)
380 markup = "<i>%s</i>" % markup
381 self._model.set(titer, COL_WORKER, markup)
382
387
389 """
390 Set the mood value on the given component name.
391
392 @type value: int
393 """
394 self._model.set(titer, COL_MOOD, self._moodPixbufs[value])
395 self._model.set(titer, COL_MOOD_VALUE, value)
396 mood = moods.get(value)
397 self._model.set(titer, COL_TOOLTIP,
398 _("<b>Component is %s</b>") % (MOODS_INFO[mood].lower(), ))
399
400 self._updateStartStop()
401
403 selection = self._view.get_selection()
404 if not selection:
405 return None
406 model, selected_tree_rows = selection.get_selected_rows()
407 selected = []
408 for tree_row in selected_tree_rows:
409 component_state = model[tree_row][col_name]
410 selected.append(component_state)
411 return selected
412
414
415 pixbufs = {}
416 for i in range(0, len(moods)):
417 name = moods.get(i).name
418 pixbufs[i] = gtk.gdk.pixbuf_new_from_file_at_size(
419 os.path.join(configure.imagedir, 'mood-%s.png' % name),
420 24, 24)
421
422 return pixbufs
423
425 states = self.getSelectedStates()
426
427 if not states:
428 self.debug(
429 'no component selected, emitting selection-changed None')
430
431
432
433
434 gobject.idle_add(self.emit, 'selection-changed', [])
435 return
436
437 if states == self._lastStates:
438 self.debug('no new components selected, no emitting signal')
439 return
440
441 self.debug('components selected, emitting selection-changed')
442 self.emit('selection-changed', states)
443 self._lastStates = states
444
446 selection = self._view.get_selection()
447 retval = self._view.get_path_at_pos(int(event.x), int(event.y))
448 if retval is None:
449 selection.unselect_all()
450 return
451 clicked_path = retval[0]
452 selected_path = selection.get_selected_rows()[1]
453 if clicked_path not in selected_path:
454 selection.unselect_all()
455 selection.select_path(clicked_path)
456 self.emit('show-popup-menu', event.button, event.time)
457
458
459
462
468
469
470 gobject.type_register(ComponentList)
471