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: 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,
61 COL_CPU) = range(7)
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)
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 = {}
94 self._lastStates = None
95 self._moodPixbufs = self._getMoodPixbufs()
96 self._createUI(treeView)
97
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,
105 str,
106 str,
107 str,
108 object,
109 int,
110 str,
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
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
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
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
202
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
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
227 self._model.foreach(self._removeListenerForeach)
228
229 self.debug('updating components view')
230
231 self._model.clear()
232 self._iters = {}
233
234
235
236
237
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
273
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
294
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
307
308
309
310
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
329
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
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
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
366
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
377 states = self.getSelectedStates()
378
379 if not states:
380 self.debug('no component selected, emitting selection-changed None')
381
382
383
384
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
400
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
415
423
426
432
433
434 gobject.type_register(ComponentList)
435