Ejemplo n.º 1
0
class WorkerListStore(gtk.ListStore):
    implements(flavors.IStateListener)
    gsignal('changed')

    def __init__(self, whs):
        gtk.ListStore.__init__(self, str)
        for x in whs.get('names'):
            i = self.append()
            self.set_value(i, 0, x)
        whs.addListener(self, append=self.stateAppend,
                        remove=self.stateRemove)

    def stateAppend(self, state, key, val):
        if key == 'names':
            i = self.append()
            self.set_value(i, 0, val)
            self.emit('changed')

    def stateRemove(self, state, key, val):
        if key == 'names':
            for r in self:
                if self.get_value(r.iter, 0) == val:
                    self.remove(r.iter)
                    self.emit('changed')
                    return
Ejemplo n.º 2
0
class _SidebarSection(gtk.VBox):
    gsignal('step-chosen', str)

    def __init__(self, title, name):
        gtk.VBox.__init__(self)

        self.set_name(title)
        self._buttons = util.OrderedDict()

        self.title = _SidebarButton(title, 10)
        self.title.show()
        self.title.set_sensitive(False)
        self.pack_start(self.title, False, False)
        self.title.connect('clicked', lambda b: self.emit('step-chosen', name))

    def __repr__(self):
        return '<SidebarSection object %s>' % self.name

    def set_active(self, active):
        if active:
            for button in self._buttons.values():
                button.show()
        else:
            for button in self._buttons.values():
                button.hide()

    def push_header(self):
        self.title.set_sensitive(True)

    def pop_header(self):
        self.title.set_sensitive(False)

    def push_step(self, step_name, step_title):
        if step_name in self._buttons:
            return

        def clicked_cb(b, name):
            self.emit('step-chosen', name)

        button = _SidebarButton(step_title, 20)
        button.connect('clicked', clicked_cb, step_name)
        self.pack_start(button, False, False)
        button.show()
        self._buttons[step_name] = button

    def pop_step(self):
        if not self._buttons:
            return None

        button = self._buttons.popitem()[1]
        if button:
            self.remove(button)
        return button
Ejemplo n.º 3
0
        class Foo(gobject.GObject):
            gsignal('hcf', bool, str)
            gproperty(bool, 'burning', 'If the object is burning', False)

            def __init__(xself):
                gobject.GObject.__init__(xself)
                xself.connect('hcf', xself.on_hcf)
                xself.set_property('burning', False)

            def on_hcf(xself, again_self, x, y):
                self.assert_(isinstance(x, bool))
                self.assert_(isinstance(y, str))
                xself.set_property('burning', True)
Ejemplo n.º 4
0
class DebugMarkerDialog(GladeWindow):
    gladeFile = 'debug-marker.glade'

    gsignal('set-marker', str, int)

    def __init__(self):
        debugLevels = log.getLevelNames()
        GladeWindow.__init__(self)
        pos = 0
        self._debugLevelCode = {}
        for level in debugLevels:
            if level == 'ERROR':
                continue
            self.level_selection.get_model().insert(pos, [level])
            self._debugLevelCode[pos] = log.getLevelInt(level)
            pos = pos + 1

    def _updateOkButtonSensitiveness(self):
        if (self.marker_entry.get_text()
                and self.level_selection.get_active() != -1):
            self.ok_button.set_sensitive(True)
        else:
            self.ok_button.set_sensitive(False)

    def _emitMarker(self):
        level = self._debugLevelCode[self.level_selection.get_active()]
        self.emit('set-marker', self.marker_entry.get_text(), level)

    # Callbacks

    def on_ok_button_clicked_cb(self, button):
        self._emitMarker()
        self.destroy()

    def on_cancel_button_clicked_cb(self, button):
        self.destroy()

    def on_marker_entry_changed_cb(self, entry):
        self._updateOkButtonSensitiveness()

    def on_level_selection_changed_cb(self, combo):
        self._updateOkButtonSensitiveness()
Ejemplo n.º 5
0
class ConnectionsDialog(GladeWindow):
    gladeFile = 'connection-dialog.glade'

    gsignal('have-connection', object)

    def on_connection_activated(self, widget, state):
        self.emit('have-connection', state)

    def on_cancel(self, button):
        self.destroy()

    def on_ok(self, button):
        self.emit('have-connection',
                  self.widgets['connections'].get_selected())

    def on_delete_event(self, dialog, event):
        self.destroy()

    def on_connections_cleared(self, widget):
        self.button_ok.set_sensitive(False)
Ejemplo n.º 6
0
class GladeWidget(gtk.VBox, GladeBacked):
    '''
    Base class for composite widgets backed by glade interface definitions.

    The Window contents will be reparented to ourselves.
    All widgets inside the Window will be available as attributes on the
    object (dashes will be replaced with underscores).

    Example::
      class MyWidget(GladeWidget):
          gladeFile = 'my_gladeFile.glade'
          ...
      gobject.type_register(MyWidget)

    Remember to chain up if you customize __init__().

    '''

    gsignal('validation-changed', bool)

    def __init__(self):
        GladeBacked.__init__(self)
        gtk.VBox.__init__(self)

        for name, widget in self.widgets.items():
            # translate - to _ so we can access them as attributes
            if name.find('-') > -1:
                name = "_".join(name.split('-'))
            setattr(self, name, widget)

        # we reparent the contents of the window to ourselves
        w = self._window.get_child()
        self._window.remove(w)
        self.add(w)
        self._window.destroy()
        self._window = None
Ejemplo n.º 7
0
class View(gobject.GObject):
    gsignal('width-changed', int)
    gsignal('height-changed', int)
    gsignal('framerate-changed', float)
    gsignal('format-changed', int)
    gsignal('pattern-changed', int)

    latency = 100  # latency for timeouts on config changes

    def __init__(self):
        """
        Construct a new videotest View.
        """
        self.__gobject_init__()
        self._gladefile = os.path.join(configure.gladedir, 'videotest.glade')
        self._glade = gtk.glade.XML(self._gladefile, "videotest-widget")
        self._widget = self._glade.get_widget("videotest-widget")
        self._width_timeout = 0
        self._height_timeout = 0
        self._framerate_timeout = 0
        self._format_timeout = 0

    def prepare(self):
        # connect callbacks
        w = self._glade.get_widget("width-button")
        w.connect("value-changed", self.width_changed_cb)
        w = self._glade.get_widget("height-button")
        w.connect("value-changed", self.height_changed_cb)
        w = self._glade.get_widget("framerate-button")
        w.connect("value-changed", self.framerate_changed_cb)
        w = self._glade.get_widget("pattern-combo")
        self._pattern_combo = w
        self._pattern_model = gtk.ListStore(str)
        w.set_model(self._pattern_model)
        w.connect("changed", self.pattern_changed_cb)
        w = self._glade.get_widget("format-combo")
        self._format_combo = w
        self._format_model = gtk.ListStore(str)
        w.set_model(self._format_model)
        w.connect("changed", self.format_changed_cb)

    def get_widget(self):
        return self._widget

    def add_pattern(self, description):
        'add a pattern description to the pattern combobox'
        # FIXME: for now we don't even store enum keys, which we might want
        # to do if they're added in a different order or not always start
        # at 0
        self._pattern_model.append((description, ))

    def set_pattern(self, index):
        self._pattern_combo.set_active(index)

    def add_format(self, description):
        'add a format description to the format combobox'
        # FIXME: for now we don't even store enum keys, which we might want
        # to do if they're added in a different order or not always start
        # at 0
        self._format_model.append((description, ))

    def set_format(self, index):
        self._format_combo.set_active(index)

    ### timeouts

    def view_width_timeout(self, widget):
        width = widget.get_value()
        self.emit('width-changed', width)
        self._width_timeout = 0
        return gtk.FALSE

    def view_height_timeout(self, widget):
        height = widget.get_value()
        self.emit('height-changed', height)
        self._height_timeout = 0
        return gtk.FALSE

    def view_framerate_timeout(self, widget):
        framerate = widget.get_value()
        self.emit('framerate-changed', framerate)
        self._framerate_timeout = 0
        return gtk.FALSE

    def view_format_timeout(self, widget):
        format = widget.get_active()
        self.emit('format-changed', format)
        self._format_timeout = 0
        return gtk.FALSE

    ### callbacks

    def width_changed_cb(self, widget):
        if not self._width_timeout:
            id = gobject.timeout_add(View.latency, self.view_width_timeout,
                                     widget)
            self._width_timeout = id

    def height_changed_cb(self, widget):
        if not self._height_timeout:
            id = gobject.timeout_add(View.latency, self.view_height_timeout,
                                     widget)
            self._height_timeout = id

    def framerate_changed_cb(self, widget):
        if not self._framerate_timeout:
            id = gobject.timeout_add(View.latency, self.view_framerate_timeout,
                                     widget)
            self._framerate_timeout = id

    def format_changed_cb(self, widget):
        if not self._format_timeout:
            id = gobject.timeout_add(View.latency, self.view_format_timeout,
                                     widget)
            self._format_timeout = id

    def pattern_changed_cb(self, widget):
        index = widget.get_active()
        self.emit('pattern-changed', index)
Ejemplo n.º 8
0
class WorkerList(gtk.HBox):
    gsignal('worker-selected', str)
    _combobox = None
    _label = None

    def __init__(self):
        gtk.HBox.__init__(self)

        self._combobox = gtk.ComboBox()
        self._label = gtk.Label(_('Worker:'))

        self._label.show()
        self.pack_start(self._label, False, False, 0)
        vb = gtk.VBox()
        self.pack_start(vb, False, False, 10)
        vb.show()
        a = gtk.Alignment(0.5, 0.5)
        a.show()
        vb.pack_start(a, True, False, 0)
        cell = gtk.CellRendererText()
        cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE)
        cell.set_property('width', 100)
        self._combobox.pack_start(cell, True)
        self._combobox.add_attribute(cell, 'text', 0)

        def onChanged(cb):
            self.emit('worker-selected', self.getWorker())

        self._combobox.connect('changed', onChanged)
        self._combobox.show()
        # GTK 2.4
        try:
            self._combobox.set_property('focus-on-click', False)
            self._combobox.set_property('has-frame', False)
        except TypeError:
            pass
        a.add(self._combobox)

    def setWorkerHeavenState(self, whs):
        self._combobox.set_model(WorkerListStore(whs))
        self.selectWorker(None)

        def onModelChanged(model):
            if not self.getWorker():
                # need to select a new worker
                self.selectWorker(None) # will emit if worker selected
                if not self.getWorker():
                    # no workers present!
                    self.emit('worker-selected', None)

        self._combobox.get_model().connect('changed', onModelChanged)

    def selectWorker(self, worker):
        # worker == none means select first worker
        for r in self._combobox.get_model():
            if not worker or r.model.get_value(r.iter, 0) == worker:
                self._combobox.set_active_iter(r.iter)
                return

        if worker:
            # FIXME: let's not print, have correct logging
            print 'warning: worker %s not available' % worker

    def getWorker(self):
        i = self._combobox.get_active_iter()
        if i:
            return self._combobox.get_model().get_value(i, 0)

        return None

    def notifySelected(self):
        self.emit('worker-selected', self.getWorker())
Ejemplo n.º 9
0
class StartNew(WizardStep):
    name = 'start_new'
    title = _('Start a New Manager and Worker')
    text = _("""This will start a new manager and worker for you.

The manager and worker will run under your user account.
The manager will only accept connections from the local machine.
This mode is only useful for testing Flumotion.
""")
    start_worker_check = None
    next_pages = ['start_new_error', 'start_new_success']
    gsignal('finished', str)

    _timeout_id = None

    # WizardStep

    def setup(self, state, available_pages):
        self.button_next.grab_focus()

    def on_next(self, state):
        self.label_starting.show()
        self.progressbar_starting.set_fraction(0.0)
        self.progressbar_starting.show()

        def pulse():
            self.progressbar_starting.pulse()
            return True

        self._timeout_id = gobject.timeout_add(200, pulse)

        self._startManager(state)
        self.button_prev.set_sensitive(False)
        return '*signaled*'

    # Private

    def _startManager(self, state):
        port = tryPort()
        managerSpawner = LocalManagerSpawner(port)
        managerSpawner.connect('error', self._on_spawner_error, state)
        managerSpawner.connect('description-changed',
                               self._on_spawner_description_changed)
        managerSpawner.connect('finished', self._on_spawner_finished, state)
        managerSpawner.start()

    def _on_spawner_error(self, spawner, failure, msg, args, state):
        # error should trigger going to next page with an overview
        state.update({
            'command': ' '.join(args),
            'error': msg,
            'failure': failure,
        })
        self._finished('start_new_error')

    def _on_spawner_description_changed(self, spawner, description):
        self.label_starting.set_text(description)

    def _on_spawner_finished(self, spawner, state):
        # because of the ugly call-by-reference passing of state,
        # we have to update the existing dict, not re-bind with state =
        state['connectionInfo'] = parsePBConnectionInfo('localhost',
                                                        username='******',
                                                        password='******',
                                                        port=spawner.getPort(),
                                                        use_ssl=True)
        state.update(
            dict(confDir=spawner.getConfDir(),
                 logDir=spawner.getLogDir(),
                 runDir=spawner.getRunDir()))
        state['managerSpawner'] = spawner
        self._finished('start_new_success')

    def _finished(self, result):
        # result: start_new_error or start_new_success
        self.label_starting.hide()
        self.progressbar_starting.hide()
        gobject.source_remove(self._timeout_id)
        self.emit('finished', result)
Ejemplo n.º 10
0
class ComponentList(log.Loggable, gobject.GObject):
    """
    I present a view on the list of components logged in to the manager.
    """

    implements(flavors.IStateListener)

    logCategory = 'components'

    gsignal('selection-changed', object)  # state-or-None
    gsignal('show-popup-menu', int, int)  # button, click time

    gproperty(bool, 'can-start-any', 'True if any component can be started',
              False)
    gproperty(bool, 'can-stop-any', 'True if any component can be stopped',
              False)

    def __init__(self, treeView):
        """
        @param treeView: the gtk.TreeView to put the view in.
        """
        gobject.GObject.__init__(self)
        self.set_property('can-start-any', False)
        self.set_property('can-stop-any', False)

        self._iters = {}  # componentState -> model iter
        self._lastStates = None
        self._model = None
        self._workers = []
        self._view = None
        self._moodPixbufs = self._getMoodPixbufs()
        self._createUI(treeView)

    def _createUI(self, treeView):
        treeView.connect('button-press-event',
                         self._view_button_press_event_cb)
        treeView.set_headers_visible(True)

        treeModel = gtk.ListStore(
            gtk.gdk.Pixbuf,  # mood
            str,  # name
            str,  # worker
            str,  # pid
            gtk.gdk.Pixbuf,  # message level
            object,  # state
            int,  # mood-value
            str,  # tooltip
            str,  # color
            bool,  # colorize
        )
        treeView.set_model(treeModel)

        treeSelection = treeView.get_selection()
        treeSelection.set_mode(gtk.SELECTION_MULTIPLE)
        treeSelection.connect('changed', self._view_cursor_changed_cb)

        # put in all the columns
        col = gtk.TreeViewColumn('', gtk.CellRendererPixbuf(), pixbuf=COL_MOOD)
        col.set_sort_column_id(COL_MOOD_VALUE)
        treeView.append_column(col)

        col = gtk.TreeViewColumn(_('Component'),
                                 gtk.CellRendererText(),
                                 text=COL_NAME,
                                 foreground=COL_FG,
                                 foreground_set=COL_SAD)
        col.set_sort_column_id(COL_NAME)
        treeView.append_column(col)

        col = gtk.TreeViewColumn(_('Worker'),
                                 gtk.CellRendererText(),
                                 markup=COL_WORKER,
                                 foreground=COL_FG,
                                 foreground_set=COL_SAD)
        col.set_sort_column_id(COL_WORKER)
        treeView.append_column(col)

        t = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_('PID'),
                                 t,
                                 markup=COL_PID,
                                 foreground=COL_FG,
                                 foreground_set=COL_SAD)
        col.set_sort_column_id(COL_PID)
        treeView.append_column(col)

        col = gtk.TreeViewColumn('', gtk.CellRendererPixbuf(), pixbuf=COL_MSG)
        treeView.append_column(col)

        if gtk.pygtk_version >= (2, 12):
            treeView.set_tooltip_column(COL_TOOLTIP)

        if hasattr(gtk.TreeView, 'set_rubber_banding'):
            treeView.set_rubber_banding(False)

        self._model = treeModel
        self._view = treeView

    def getSelectedNames(self):
        """
        Get the names of the currently selected components, or None if none
        are selected.

        @rtype: list of str or None
        """
        return self._getSelected(COL_NAME)

    def getSelectedStates(self):
        """
        Get the states of the currently selected components, or None if none
        are selected.

        @rtype: list of L{flumotion.common.component.AdminComponentState}
                or None
        """
        return self._getSelected(COL_STATE)

    def getComponentNames(self):
        """
        Fetches a list of all component names.

        @returns: component names
        @rtype:   list of str
        """
        names = []
        for row in self._model:
            names.append(row[COL_NAME])
        return names

    def getComponentStates(self):
        """
        Fetches a list of all component states

        @returns: component states
        @rtype:   list of L{AdminComponentState}
        """
        names = []
        for row in self._model:
            names.append(row[COL_STATE])
        return names

    def canDelete(self):
        """
        Get whether the selected components can be deleted.

        Returns True if all components are sleeping.

        Also returns False if no components are selected.

        @rtype: bool
        """
        states = self.getSelectedStates()
        if not states:
            return False
        canDelete = True
        for state in states:
            moodname = moods.get(state.get('mood')).name
            canDelete = canDelete and moodname == 'sleeping'
        return canDelete

    def canStart(self):
        """
        Get whether the selected components can be started.

        Returns True if all components are sleeping and their worked has
        logged in.

        Also returns False if no components are selected.

        @rtype: bool
        """
        # additionally to canDelete, the worker needs to be logged intoo
        if not self.canDelete():
            return False

        canStart = True
        states = self.getSelectedStates()
        for state in states:
            workerName = state.get('workerRequested')
            canStart = canStart and workerName in self._workers

        return canStart

    def canStop(self):
        """
        Get whether the selected components can be stopped.

        Returns True if none of the components are sleeping.

        Also returns False if no components are selected.

        @rtype: bool
        """
        states = self.getSelectedStates()
        if not states:
            return False
        canStop = True
        for state in states:
            moodname = moods.get(state.get('mood')).name
            canStop = canStop and moodname != 'sleeping'
        return canStop

    def clearAndRebuild(self, components, componentNameToSelect=None):
        """
        Update the components view by removing all old components and
        showing the new ones.

        @param components: dictionary of name ->
                           L{flumotion.common.component.AdminComponentState}
        @param componentNameToSelect: name of the component to select or None
        """
        # remove all Listeners
        self._model.foreach(self._removeListenerForeach)

        self.debug('updating components view')
        # clear and rebuild
        self._view.get_selection().unselect_all()
        self._model.clear()
        self._iters = {}

        components = sorted(components.values(),
                            cmp=cmpComponentType,
                            key=operator.itemgetter('type'))

        for component in components:
            self.appendComponent(component, componentNameToSelect)

        self.debug('updated components view')

    def appendComponent(self, component, componentNameToSelect):
        self.debug('adding component %r to listview' % component)
        component.addListener(self,
                              set_=self.stateSet,
                              append=self.stateSet,
                              remove=self.stateSet)

        titer = self._model.append()
        self._iters[component] = titer

        mood = component.get('mood')
        self.debug('component has mood %r' % mood)
        messages = component.get('messages')
        self.debug('component has messages %r' % messages)
        self._setMsgLevel(titer, messages)

        if mood != None:
            self._setMoodValue(titer, mood)

        self._model.set(titer, COL_FG, SAD_COLOR)
        self._model.set(titer, COL_STATE, component)
        componentName = getComponentLabel(component)
        self._model.set(titer, COL_NAME, componentName)

        pid = component.get('pid')
        if not pid and component.hasKey('lastKnownPid'):
            if component.get('lastKnownPid'):
                pid = "<i>%d</i>" % component.get('lastKnownPid')
        self._model.set(titer, COL_PID, (pid and str(pid)) or '')

        self._updateWorker(titer, component)
        selection = self._view.get_selection()
        if (componentNameToSelect is not None
                and componentName == componentNameToSelect
                and not selection.get_selected_rows()[1]):
            selection.select_iter(titer)

        self._updateStartStop()

    def removeComponent(self, component):
        self.debug('removing component %r to listview' % component)

        titer = self._iters[component]
        self._model.remove(titer)
        del self._iters[component]

        self._updateStartStop()

    # IStateListener implementation

    def stateSet(self, state, key, value):
        if not isinstance(state, planet.AdminComponentState):
            self.warning('Got state change for unknown object %r' % state)
            return

        titer = self._iters[state]
        self.log('stateSet: state %r, key %s, value %r' % (state, key, value))

        if key == 'mood':
            self.debug('stateSet: mood of %r changed to %r' % (state, value))

            if value == moods.sleeping.value:
                self.debug('sleeping, removing local messages on %r' % state)
                for message in state.get('messages', []):
                    state.observe_remove('messages', message)

            self._setMoodValue(titer, value)
            self._updateWorker(titer, state)
        elif key == 'name':
            if value:
                self._model.set(titer, COL_NAME, value)
        elif key == 'workerName':
            self._updateWorker(titer, state)
        elif key == 'pid':
            self._model.set(titer, COL_PID, (value and str(value) or ''))
        elif key == 'messages':
            self._setMsgLevel(titer, state.get('messages'))

    # Private

    def _setMsgLevel(self, titer, messages):
        icon = None

        if messages:
            messages = sorted(messages, cmp=lambda x, y: x.level - y.level)
            level = messages[0].level
            st = _stock_icons.get(level, gtk.STOCK_MISSING_IMAGE)
            w = gtk.Invisible()
            icon = w.render_icon(st, gtk.ICON_SIZE_MENU)

        self._model.set(titer, COL_MSG, icon)

    def _updateStartStop(self):
        oldstop = self.get_property('can-stop-any')
        oldstart = self.get_property('can-start-any')
        moodnames = [moods.get(x[COL_MOOD_VALUE]).name for x in self._model]
        canStop = bool([x for x in moodnames if (x != 'sleeping')])
        canStart = bool([x for x in moodnames if (x == 'sleeping')])
        if oldstop != canStop:
            self.set_property('can-stop-any', canStop)
        if oldstart != canStart:
            self.set_property('can-start-any', canStart)

    def workerAppend(self, name):
        self._workers.append(name)

    def workerRemove(self, name):
        self._workers.remove(name)
        for state, titer in self._iters.items():
            self._updateWorker(titer, state)

    def _updateWorker(self, titer, componentState):
        # update the worker name:
        # - italic if workerName and workerRequested are not running
        # - normal if running

        workerName = componentState.get('workerName')
        workerRequested = componentState.get('workerRequested')
        if not workerName and not workerRequested:
            #FIXME: Should we raise an error here?
            #       It's an impossible situation.
            workerName = _("[any worker]")

        markup = workerName or workerRequested
        if markup not in self._workers:
            self._model.set(titer, COL_TOOLTIP,
                            _("<b>Worker %s is not connected</b>") % markup)
            markup = "<i>%s</i>" % markup
        self._model.set(titer, COL_WORKER, markup)

    def _removeListenerForeach(self, model, path, titer):
        # remove the listener for each state object
        state = model.get(titer, COL_STATE)[0]
        state.removeListener(self)

    def _setMoodValue(self, titer, value):
        """
        Set the mood value on the given component name.

        @type  value: int
        """
        self._model.set(titer, COL_MOOD, self._moodPixbufs[value])
        self._model.set(titer, COL_MOOD_VALUE, value)
        self._model.set(titer, COL_SAD, moods.sad.value == value)
        mood = moods.get(value)
        self._model.set(
            titer, COL_TOOLTIP,
            _("<b>Component is %s</b>") % (MOODS_INFO[mood].lower(), ))

        self._updateStartStop()

    def _getSelected(self, col_name):
        # returns None if no components are selected, a list otherwise
        selection = self._view.get_selection()
        if not selection:
            return None
        model, selected_tree_rows = selection.get_selected_rows()
        selected = []
        for tree_row in selected_tree_rows:
            component_state = model[tree_row][col_name]
            selected.append(component_state)
        return selected

    def _getMoodPixbufs(self):
        # load all pixbufs for the moods
        pixbufs = {}
        for i in range(0, len(moods)):
            name = moods.get(i).name
            pixbufs[i] = gtk.gdk.pixbuf_new_from_file_at_size(
                os.path.join(configure.imagedir, 'mood-%s.png' % name), 24, 24)

        return pixbufs

    def _selectionChanged(self):
        states = self.getSelectedStates()

        if not states:
            self.debug(
                'no component selected, emitting selection-changed None')
            # Emit this in an idle, since popups will not be shown
            # before this has completed, and it might possibly take a long
            # time to finish all the callbacks connected to selection-changed
            # This is not the proper fix, but makes the popups show up faster
            gobject.idle_add(self.emit, 'selection-changed', [])
            return

        if states == self._lastStates:
            self.debug('no new components selected, no emitting signal')
            return

        self.debug('components selected, emitting selection-changed')
        self.emit('selection-changed', states)
        self._lastStates = states

    def _showPopupMenu(self, event):
        selection = self._view.get_selection()
        retval = self._view.get_path_at_pos(int(event.x), int(event.y))
        if retval is None:
            selection.unselect_all()
            return
        clicked_path = retval[0]
        selected_path = selection.get_selected_rows()[1]
        if clicked_path not in selected_path:
            selection.unselect_all()
            selection.select_path(clicked_path)
        self.emit('show-popup-menu', event.button, event.time)

    # Callbacks

    def _view_cursor_changed_cb(self, *args):
        self._selectionChanged()

    def _view_button_press_event_cb(self, treeview, event):
        if event.button == 3:
            self._showPopupMenu(event)
            return True
        return False
Ejemplo n.º 11
0
class ConfigurationAssistant(SectionWizard):
    """This is the main configuration assistant class,
    it is responsible for::
    - executing tasks which will block the ui
    - showing a worker list in the UI
    - communicating with the manager, fetching bundles
      and registry information
    - running check defined by a step in a worker, for instance
      querying for hardware devices and their capabilities
    It extends SectionWizard which provides the basic user interface, such
    as sidebar, buttons, title bar and basic step navigation.
    """
    gsignal('finished', str)

    def __init__(self, parent=None):
        SectionWizard.__init__(self, parent)
        self.connect('help-clicked', self._on_assistant__help_clicked)
        # Set the name of the toplevel window, this is used by the
        # ui unittest framework
        self.window1.set_name('ConfigurationAssistant')
        self.message_area.disableTimestamps()

        self._cursorWatch = gdk.Cursor(gdk.WATCH)
        self._tasks = []
        self._adminModel = None
        self._workerHeavenState = None
        self._lastWorker = 0  # combo id last worker from step to step
        self._stepWorkers = {}
        self._scenario = None
        self._existingComponentNames = []
        self._porters = []
        self._mountPoints = []
        self._consumers = {}

        self._workerList = WorkerList()
        self.top_vbox.pack_start(self._workerList, False, False)
        self._workerList.connect('worker-selected',
                                 self.on_combobox_worker_changed)

    # SectionWizard

    def completed(self):
        saver = AssistantSaver()
        saver.setFlowName('default')
        saver.setExistingComponentNames(self._existingComponentNames)
        self._scenario.save(self, saver)

        xml = saver.getXML()
        self.emit('finished', xml)

    def destroy(self):
        SectionWizard.destroy(self)
        self._adminModel = None

    def beforeShowStep(self, step):
        if isinstance(step, WorkerWizardStep):
            self._workerList.show()
            if step.worker:
                self._workerList.selectWorker(step.worker)
            self._workerList.notifySelected()
        else:
            self._workerList.hide()

        self._fetchDescription(step)
        self._setupWorker(step, self._workerList.getWorker())

    def prepareNextStep(self, step):
        self._setupWorker(step, self._workerList.getWorker())
        SectionWizard.prepareNextStep(self, step)

    def blockNext(self, block):
        # Do not block/unblock if we have tasks running
        if self._tasks:
            return
        SectionWizard.blockNext(self, block)

    # Public API

    # FIXME: Remove this and make fgc create a new scenario

    def addInitialSteps(self):
        """Add the step sections of the wizard, can be
        overridden in a subclass
        """
        # These two steps are independent of the scenarios, they
        # should always be added.
        self.addStepSection(WelcomeStep)
        self.addStepSection(ScenarioStep)

    def setScenario(self, scenario):
        """Sets the current scenario of the assistant.
        Normally called by ScenarioStep to tell the assistant the
        current scenario just after creating it.
        @param scenario: the scenario of the assistant
        @type scenario: a L{flumotion.admin.assistant.scenarios.Scenario}
          subclass
        """
        self._scenario = scenario

    def getScenario(self):
        """Fetches the currently set scenario of the assistant.
        @returns scenario: the scenario of the assistant
        @rtype: a L{flumotion.admin.assistant.scenarios.Scenario} subclass
        """
        return self._scenario

    def setWorkerHeavenState(self, workerHeavenState):
        """
        Sets the worker heaven state of the assistant
        @param workerHeavenState: the worker heaven state
        @type workerHeavenState: L{WorkerComponentUIState}
        """
        self._workerHeavenState = workerHeavenState
        self._workerList.setWorkerHeavenState(workerHeavenState)

    def setAdminModel(self, adminModel):
        """
        Sets the admin model of the assistant
        @param adminModel: the admin model
        @type adminModel: L{AdminModel}
        """
        self._adminModel = adminModel
        self._adminModel.connect('connected', self.on_admin_connected_cb)
        self._adminModel.connect('disconnected', self.on_admin_disconnected_cb)

    def setHTTPPorters(self, porters):
        """
        Sets the list of currently configured porters so
        we can reuse them for future streamers.

        @param porters: list of porters
        @type porters : list of L{flumotion.admin.assistant.models.Porter}
        """

        self._porters = porters

    def getHTTPPorters(self):
        """
        Obtains the list of the currently configured porters.

        @rtype : list of L{flumotion.admin.assistant.models.Porter}
        """
        return self._porters

    def addMountPoint(self, worker, port, mount_point, consumer=None):
        """
        Marks a mount point as used on the given worker and port.
        If a consumer name is provided it means we are changing the
        mount point for that consumer and that we should keep track of
        it for further modifications.

        @param worker   : The worker where the mount_point is configured.
        @type  worker   : str
        @param port     : The port where the streamer should be listening.
        @type  port     : int
        @param mount_point : The mount point where the data will be served.
        @type  mount_point : str
        @param consumer : The consumer that is changing its mountpoint.
        @type  consumer : str

        @returns : True if the mount point is not used and has been
                   inserted correctly, False otherwise.
        @rtype   : boolean
        """
        if not worker or not port or not mount_point:
            return False

        if consumer in self._consumers:
            oldData = self._consumers[consumer]
            if oldData in self._mountPoints:
                self._mountPoints.remove(oldData)

        data = (worker, port, mount_point)

        if data in self._mountPoints:
            return False

        self._mountPoints.append(data)

        if consumer:
            self._consumers[consumer] = data

        return True

    def getAdminModel(self):
        """Gets the admin model of the assistant
        @returns adminModel: the admin model
        @rtype adminModel: L{AdminModel}
        """
        return self._adminModel

    def waitForTask(self, taskName):
        """Instruct the assistant that we're waiting for a task
        to be finished. This changes the cursor and prevents
        the user from continuing moving forward.
        Each call to this method should have another call
        to taskFinished() when the task is actually done.
        @param taskName: name of the name
        @type taskName: string
        """
        self.info("waiting for task %s" % (taskName, ))
        if not self._tasks:
            if self.window1.window is not None:
                self.window1.window.set_cursor(self._cursorWatch)
            self.blockNext(True)
        self._tasks.append(taskName)

    def taskFinished(self, blockNext=False):
        """Instruct the assistant that a task was finished.
        @param blockNext: if we should still next when done
        @type blockNext: boolean
        """
        if not self._tasks:
            raise AssertionError(
                "Stray call to taskFinished(), forgot to call waitForTask()?")

        taskName = self._tasks.pop()
        self.info("task %s has now finished" % (taskName, ))
        if not self._tasks:
            self.window1.window.set_cursor(None)
            self.blockNext(blockNext)

    def pendingTask(self):
        """Returns true if there are any pending tasks
        @returns: if there are pending tasks
        @rtype: bool
        """
        return bool(self._tasks)

    def checkElements(self, workerName, *elementNames):
        """Check if the given list of GStreamer elements exist on the
        given worker.
        @param workerName: name of the worker to check on
        @type workerName: string
        @param elementNames: names of the elements to check
        @type elementNames: list of strings
        @returns: a deferred returning a tuple of the missing elements
        @rtype: L{twisted.internet.defer.Deferred}
        """
        if not self._adminModel:
            self.debug('No admin connected, not checking presence of elements')
            return

        asked = python.set(elementNames)

        def _checkElementsCallback(existing, workerName):
            existing = python.set(existing)
            self.taskFinished()
            return tuple(asked.difference(existing))

        self.waitForTask('check elements %r' % (elementNames, ))
        d = self._adminModel.checkElements(workerName, elementNames)
        d.addCallback(_checkElementsCallback, workerName)
        return d

    def requireElements(self, workerName, *elementNames):
        """Require that the given list of GStreamer elements exists on the
        given worker. If the elements do not exist, an error message is
        posted and the next button remains blocked.
        @param workerName: name of the worker to check on
        @type workerName: string
        @param elementNames: names of the elements to check
        @type elementNames: list of strings
        @returns: element name
        @rtype: deferred -> list of strings
        """
        if not self._adminModel:
            self.debug('No admin connected, not checking presence of elements')
            return

        self.debug('requiring elements %r' % (elementNames, ))
        f = ngettext(
            "Checking the existence of GStreamer element '%s' "
            "on %s worker.",
            "Checking the existence of GStreamer elements '%s' "
            "on %s worker.", len(elementNames))
        msg = messages.Info(T_(f, "', '".join(elementNames), workerName),
                            mid='require-elements')

        self.add_msg(msg)

        def gotMissingElements(elements, workerName):
            self.clear_msg('require-elements')

            if elements:
                self.warning('elements %r do not exist' % (elements, ))
                f = ngettext(
                    "Worker '%s' is missing GStreamer element '%s'.",
                    "Worker '%s' is missing GStreamer elements '%s'.",
                    len(elements))
                message = messages.Error(
                    T_(f, workerName, "', '".join(elements)))
                message.add(
                    T_(
                        N_("\n"
                           "Please install the necessary GStreamer plug-ins that "
                           "provide these elements and restart the worker.")))
                message.add(
                    T_(
                        N_("\n\n"
                           "You will not be able to go forward using this worker."
                           )))
                message.id = 'element' + '-'.join(elementNames)
                documentation.messageAddGStreamerInstall(message)
                self.add_msg(message)
            self.taskFinished(bool(elements))
            return elements

        self.waitForTask('require elements %r' % (elementNames, ))
        d = self.checkElements(workerName, *elementNames)
        d.addCallback(gotMissingElements, workerName)

        return d

    def checkImport(self, workerName, moduleName):
        """Check if the given module can be imported.
        @param workerName:  name of the worker to check on
        @type workerName: string
        @param moduleName:  name of the module to import
        @type moduleName: string
        @returns: a deferred firing None or Failure.
        @rtype: L{twisted.internet.defer.Deferred}
        """
        if not self._adminModel:
            self.debug('No admin connected, not checking presence of elements')
            return

        d = self._adminModel.checkImport(workerName, moduleName)
        return d

    def requireImport(self,
                      workerName,
                      moduleName,
                      projectName=None,
                      projectURL=None):
        """Require that the given module can be imported on the given worker.
        If the module cannot be imported, an error message is
        posted and the next button remains blocked.
        @param workerName:  name of the worker to check on
        @type workerName: string
        @param moduleName:  name of the module to import
        @type moduleName: string
        @param projectName: name of the module to import
        @type projectName: string
        @param projectURL:  URL of the project
        @type projectURL: string
        @returns: a deferred firing None or Failure
        @rtype: L{twisted.internet.defer.Deferred}
        """
        if not self._adminModel:
            self.debug('No admin connected, not checking presence of elements')
            return

        self.debug('requiring module %s' % moduleName)

        def _checkImportErrback(failure):
            self.warning('could not import %s', moduleName)
            message = messages.Error(
                T_(N_("Worker '%s' cannot import module '%s'."), workerName,
                   moduleName))
            if projectName:
                message.add(
                    T_(N_("\n"
                          "This module is part of '%s'."), projectName))
            if projectURL:
                message.add(
                    T_(N_("\n"
                          "The project's homepage is %s"), projectURL))
            message.add(
                T_(
                    N_("\n\n"
                       "You will not be able to go forward using this worker.")
                ))
            message.id = 'module-%s' % moduleName
            documentation.messageAddPythonInstall(message, moduleName)
            self.add_msg(message)
            self.taskFinished(blockNext=True)
            return False

        d = self.checkImport(workerName, moduleName)
        d.addErrback(_checkImportErrback)
        return d

    # FIXME: maybe add id here for return messages ?

    def runInWorker(self, workerName, moduleName, functionName, *args,
                    **kwargs):
        """
        Run the given function and arguments on the selected worker.
        The given function should return a L{messages.Result}.

        @param workerName:   name of the worker to run the function in
        @type  workerName:   string
        @param moduleName:   name of the module where the function is found
        @type  moduleName:   string
        @param functionName: name of the function to run
        @type  functionName: string

        @returns: a deferred firing the Result's value.
        @rtype: L{twisted.internet.defer.Deferred}
        """
        self.debug('runInWorker(moduleName=%r, functionName=%r)' %
                   (moduleName, functionName))
        admin = self._adminModel
        if not admin:
            self.warning('skipping runInWorker, no admin')
            return defer.fail(errors.FlumotionError('no admin'))

        if not workerName:
            self.warning('skipping runInWorker, no worker')
            return defer.fail(errors.FlumotionError('no worker'))

        def callback(result):
            self.debug('runInWorker callbacked a result')
            self.clear_msg(functionName)

            if not isinstance(result, messages.Result):
                msg = messages.Error(T_(
                    N_("Internal error: could not run check code on worker.")),
                                     debug=(
                                         'function %r returned a non-Result %r'
                                         % (functionName, result)))
                self.add_msg(msg)
                self.taskFinished(True)
                raise errors.RemoteRunError(functionName, 'Internal error.')

            for m in result.messages:
                self.debug('showing msg %r' % m)
                self.add_msg(m)

            if result.failed:
                self.debug('... that failed')
                self.taskFinished(True)
                raise errors.RemoteRunFailure(functionName, 'Result failed')
            self.debug('... that succeeded')
            self.taskFinished()
            return result.value

        def errback(failure):
            self.debug('runInWorker errbacked, showing error msg')
            if failure.check(errors.RemoteRunError):
                debug = failure.value
            else:
                debug = "Failure while running %s.%s:\n%s" % (
                    moduleName, functionName, failure.getTraceback())

            msg = messages.Error(T_(
                N_("Internal error: could not run check code on worker.")),
                                 debug=debug)
            self.add_msg(msg)
            self.taskFinished(True)
            raise errors.RemoteRunError(functionName, 'Internal error.')

        self.waitForTask('run in worker: %s.%s(%r, %r)' %
                         (moduleName, functionName, args, kwargs))
        d = admin.workerRun(workerName, moduleName, functionName, *args,
                            **kwargs)
        d.addErrback(errback)
        d.addCallback(callback)
        return d

    def getWizardEntry(self, componentType):
        """Fetches a assistant bundle from a specific kind of component
        @param componentType: the component type to get the assistant entry
          bundle from.
        @type componentType: string
        @returns: a deferred returning either::
          - factory of the component
          - noBundle error: if the component lacks a assistant bundle
        @rtype: L{twisted.internet.defer.Deferred}
        """
        self.waitForTask('get assistant entry %s' % (componentType, ))
        self.clear_msg('assistant-bundle')
        d = self._adminModel.callRemote('getEntryByType', componentType,
                                        'wizard')
        d.addCallback(self._gotEntryPoint)
        return d

    def getWizardScenario(self, scenarioType):
        """
        Fetches a scenario bundle from a specific kind of component.

        @param scenarioType: the scenario type to get the assistant entry
          bundle from.
        @type scenarioType: string
        @returns: a deferred returning either::
          - factory of the component
          - noBundle error: if the component lacks a assistant bundle
        @rtype: L{twisted.internet.defer.Deferred}
        """
        self.waitForTask('get assistant entry %s' % (scenarioType, ))
        self.clear_msg('assistant-bundle')
        d = self._adminModel.callRemote('getScenarioByType', scenarioType,
                                        'wizard')
        d.addCallback(self._gotEntryPoint)
        return d

    def getWizardPlugEntry(self, plugType):
        """Fetches a assistant bundle from a specific kind of plug
        @param plugType: the plug type to get the assistant entry
          bundle from.
        @type plugType: string
        @returns: a deferred returning either::
          - factory of the plug
          - noBundle error: if the plug lacks a assistant bundle
        @rtype: L{twisted.internet.defer.Deferred}
        """
        self.waitForTask('get assistant plug %s' % (plugType, ))
        self.clear_msg('assistant-bundle')
        d = self._adminModel.callRemote('getPlugEntry', plugType, 'wizard')
        d.addCallback(self._gotEntryPoint)
        return d

    def getWizardEntries(self, wizardTypes=None, provides=None, accepts=None):
        """Queries the manager for a list of assistant entries matching the
        query.
        @param wizardTypes: list of component types to fetch, is usually
                            something like ['video-producer'] or
                            ['audio-encoder']
        @type  wizardTypes: list of str
        @param provides:    formats provided, eg ['jpeg', 'speex']
        @type  provides:    list of str
        @param accepts:     formats accepted, eg ['theora']
        @type  accepts:     list of str

        @returns: a deferred firing a list
                  of L{flumotion.common.componentui.WizardEntryState}
        @rtype: L{twisted.internet.defer.Deferred}
        """
        self.debug('querying wizard entries (wizardTypes=%r,provides=%r'
                   ',accepts=%r)' % (wizardTypes, provides, accepts))
        return self._adminModel.getWizardEntries(wizardTypes=wizardTypes,
                                                 provides=provides,
                                                 accepts=accepts)

    def setExistingComponentNames(self, componentNames):
        """Tells the assistant about the existing components available, so
        we can resolve naming conflicts when saving the configuration
        @param componentNames: existing component names
        @type componentNames: list of strings
        """
        self._existingComponentNames = componentNames

    def workerChangedForStep(self, step, workerName):
        """Tell a step that its worker changed.
        @param step: step which worker changed for
        @type step: a L{WorkerWizardStep} subclass
        @param workerName: name of the worker
        @type workerName: string
        """
        if self._stepWorkers.get(step) == workerName:
            return

        self.debug('calling %r.workerChanged' % step)
        step.workerChanged(workerName)
        self._stepWorkers[step] = workerName

    # Private

    def _gotEntryPoint(self, (filename, procname)):
        # The manager always returns / as a path separator, replace them with
        # the separator since the rest of our infrastructure depends on that.
        filename = filename.replace('/', os.path.sep)
        modname = pathToModuleName(filename)
        d = self._adminModel.getBundledFunction(modname, procname)
        self.clear_msg('assistant-bundle')
        self.taskFinished()
        return d
Ejemplo n.º 12
0
class _WizardSidebar(gtk.EventBox, log.Loggable):
    gsignal('step-chosen', str)

    logCategory = 'wizard'

    def __init__(self, wizard):
        # FIXME: Remove this reference
        self._wizard = wizard

        # FIXME: Join these three into one
        self._sections = []
        self._sections2 = []
        self._sectionsByName = {}

        self._active = -1
        self._currentSection = 0
        self._currentStep = None
        self._stack = _WalkableStack()
        self._steps = {}
        self._top = -1

        gtk.EventBox.__init__(self)
        self.connect_after('realize', self.after_realize)
        self.set_size_request(160, -1)

        self.vbox = gtk.VBox()
        self.vbox.set_border_width(5)
        self.vbox.show()
        self.add(self.vbox)

    # Public API

    def appendSection(self, title, name):
        """Adds a new section to the sidebar
        @param title: title of the section
        @param name: name of the section
        """

        def clicked_cb(b, name):
            self.emit('step-chosen', name)

        section = _SidebarSection(title, name)
        section.connect('step-chosen', clicked_cb)
        section.show()
        section.set_active(False)
        self.vbox.pack_start(section, False, False)
        self._sections.append(section)
        self._sectionsByName[name] = section

    def removeSection(self, name):
        """Removes a section by name
        @param name: name of the section
        """
        section = self._sectionsByName.pop(name)
        self._sections.remove(section)
        self.vbox.remove(section)

    def jumpTo(self, section_name):
        for i, section in enumerate(self._sections):
            if section.name == section_name:
                self._set_active(i)
                return
        raise AssertionError()

    def push(self, section_name, step_name, step_title):
        active_section = self._sections[self._active]
        if active_section.name == section_name:
            # same section
            active_section.push_step(step_name, step_title)
        else:
            # new section
            self._set_active(self._active + 1)
            self._top += 1
            self._sections[self._active].push_header()

    def pop(self):
        if self._top < 0 or self._top >= len(self._sections):
            return False

        top_section = self._sections[self._top]
        if not top_section.pop_step():
            top_section.pop_header()
            self._top -= 1
            if self._top < 0:
                return False
            if self._top < self._active:
                self._set_active(self._top)
        return True

    def cleanFutureSteps(self):
        oldSections = self._sections2[self._currentSection+1:][:]
        for i, oldSection in enumerate(oldSections):
            self.removeSection(oldSection.name)
            self._sections2.remove(oldSection)
        cleared = self._stack.clearToCurrent()
        for step in cleared:
            self._steps.pop(step.name)
        self._top = self._active

    def addStepSection(self, section):
        self.appendSection(section.section, section.name)
        self._sections2.append(section)

    def getStep(self, stepname):
        for step in self.getSteps():
            if step.get_name() == stepname:
                return step
        else:
            raise KeyError(stepname)

    def getCurrentStep(self):
        return self._currentStep

    def getSteps(self):
        return self._steps.values()

    def hasStep(self, stepName):
        for step in self.getSteps():
            if step.get_name() == stepName:
                return True
        return False

    def getVisitedSteps(self):
        for step in self.getSteps():
            if step.visited:
                yield step

    def pushSteps(self):
        sectionClass = self._sections2[self._currentSection]
        if isinstance(sectionClass, (type, types.ClassType)):
            section = sectionClass(self._wizard)
        else:
            section = sectionClass

        self.push(section.section, None, section.section)
        self._stack.push(section)
        self._setStep(section)

    def canGoBack(self):
        return self._stack.pos != 0

    def prepareNextStep(self, step):
        if hasattr(step, 'lastStep'):
            self._wizard.finish(completed=True)
            return

        stepNext = step.getNext()
        if isinstance(stepNext, WizardStep):
            nextStep = stepNext
        elif isinstance(stepNext, defer.Deferred):
            d = stepNext

            def getStep(step):
                if step is None:
                    step = self._getNextStep()
                if step is None:
                    return
                self._showNextStep(step)

            def manageBundleError(error):
                exceptionHandler(error.type,
                                  error.value,
                                  error.getTracebackObject())
                self._wizard.taskFinished(False)
            d.addCallback(getStep)
            d.addErrback(manageBundleError)
            return
        elif stepNext is None:
            nextStep = self._getNextStep()
            if nextStep is None:
                return
        else:
            raise AssertionError(stepNext)

        self._showNextStep(nextStep)

    def jumpToStep(self, name):
        # If we're jumping to the same step don't do anything to
        # avoid unnecessary ui flashes
        if self.getStep(name) == self.getCurrentStep():
            return
        self._stack.skipTo(lambda x: x.name == name)
        step = self._stack.current()
        self._wizard.sidebar.jumpTo(step.section)
        self._currentSection = self._getSectionByName(step.section)
        self._setStep(step)

    def showPreviousStep(self):
        step = self._stack.back()
        self._currentSection = self._getSectionByName(step.section)
        self._setStep(step)
        self._wizard.updateButtons(hasNext=True)
        self.jumpTo(step.section)
        hasNext = not hasattr(step, 'lastStep')
        self._wizard.updateButtons(hasNext)

    # Private

    def _set_active(self, i):
        if self._active >= 0:
            self._sections[self._active].set_active(False)
        self._active = i
        if self._active >= 0:
            self._sections[self._active].set_active(True)

        l = len(self._sections)
        for i, section in enumerate(self._sections):
            if i <= self._active:
                pos = i
                pack_type = gtk.PACK_START
            else:
                pos = l - i
                pack_type = gtk.PACK_END
            self.vbox.child_set_property(section, 'pack_type', pack_type)
            self.vbox.reorder_child(section, pos)

    def _showNextStep(self, step):
        while not self._stack.push(step):
            s = self._stack.pop()
            s.visited = False
            self.pop()

        hasNext = not hasattr(step, 'lastStep')
        if not step.visited and hasNext:
            self.push(step.section,
                      step.name,
                      step.sidebarName)
        else:
            self.jumpTo(step.section)

        step.visited = True
        self._setStep(step)

        self._wizard.updateButtons(hasNext)

    def _setStep(self, step):
        self._steps[step.name] = step
        self._currentStep = step

        self._wizard.blockNext(False)
        self._wizard.packStep(step)

        self._wizard.beforeShowStep(step)

        self.debug('showing step %r' % step)
        step.show()
        step.activated()

    def _getSectionByName(self, section_name):
        for sectionClass in self._sections2:
            if sectionClass.section == section_name:
                return self._sections2.index(sectionClass)

    def _getNextStep(self):
        if self._currentSection + 1 == len(self._sections2):
            self._wizard.finish(completed=True)
            return

        self._currentSection += 1
        nextStepClass = self._sections2[self._currentSection]
        if isinstance(nextStepClass, WizardStep):
            return nextStepClass

        return nextStepClass(self._wizard)

    # Callbacks

    def after_realize(self, eventbox):
        # have to get the style from the theme, but it's not really
        # there until we're realized
        style = self.get_style()
        self.modify_bg(gtk.STATE_NORMAL, style.bg[gtk.STATE_SELECTED])
Ejemplo n.º 13
0
class Connections(GladeWidget):
    gladeFile = 'connections.glade'

    gsignal('have-connection', bool)
    gsignal('connection-activated', object)
    gsignal('connections-cleared')

    def __init__(self):
        GladeWidget.__init__(self)

        self.connections.set_columns([
            Column("host",
                   title=_("Hostname"),
                   searchable=True,
                   ellipsize=ELLIPSIZE_MIDDLE,
                   expand=True,
                   width=100),
            Column("manager",
                   title=_("Manager"),
                   searchable=True,
                   ellipsize=ELLIPSIZE_END,
                   expand=True,
                   width=50),
            Column("timestamp",
                   title=_("Last used"),
                   sorted=True,
                   order=gtk.SORT_DESCENDING,
                   format_func=format_timestamp),
        ])
        self.connections.add_list(getRecentConnections())
        self.connections.get_treeview().set_search_equal_func(
            self._searchEqual)
        self.connections.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.connections.set_property('selection-mode', gtk.SELECTION_SINGLE)
        self.connections.set_size_request(-1, 160)

        self._updateButtons()

    def _updateButtons(self):
        canClear = hasRecentConnections()
        self.button_clear.set_sensitive(canClear)
        self.button_clear_all.set_sensitive(canClear)
        if not canClear:
            self.emit('connections-cleared')

    def _searchEqual(self, model, column, key, iter):
        connection = model.get(iter, 0)[0]
        if key in connection.manager:
            return False

        # True means doesn't match
        return True

    def _clear_all(self):
        for conn in self.connections:
            os.unlink(conn.filename)
        self.connections.clear()

    def _clear(self, conn):
        self.connections.remove(conn)
        os.unlink(conn.filename)

    # Public API

    def grab_focus(self):
        if len(self.connections):
            self.connections.select(self.connections[0])
        self.connections.grab_focus()

    def get_selected(self):
        return self.connections.get_selected()

    def update(self, connection):
        os.utime(connection.filename, None)

    # Callbacks

    def on_button_clear_clicked(self, button):
        conn = self.connections.get_selected()
        if conn:
            self._clear(conn)
        self._updateButtons()

    def on_button_clear_all_clicked(self, button):
        self._clear_all()
        self._updateButtons()

    def _on_connections_row_activated(self, connections, connection):
        self.emit('connection-activated', connection)

    def _on_connections_selection_changed(self, connections, connection):
        self.emit('have-connection', bool(connection))
Ejemplo n.º 14
0
class SimpleWizard(GladeWindow):
    """
    A simple generic wizard.

    This wizard runs its own GObject main loop.
    The wizard is run with the run() method.

    Example::
      w = Wizard('foo', 'first-step', FirstStep)
      w.show()
      w.run() => {'bank-account': 'foo'}
    """
    # should by filled by subclasses
    name = None
    steps = None

    # private
    # well really, if they're private, why not prefix them with _
    gladeFile = 'admin-wizard.glade'

    gsignal('finished')

    def __init__(self, initial_page, parent=None):
        """
        @param initial_page: name of the WizardStep to start on
        @type  initial_page: str
        @param parent: parent window
        @type  window: gtk.Window subclass or None
        """
        GladeWindow.__init__(self, parent=parent)

        self.page = None
        self.page_stack = []
        self.pages = {}
        self.state = {}

        # these should be filled by subclasses
        assert self.name
        assert self.steps

        # HACK warning, it would be better to pass in the wizard
        # to each step
        self.window1.wizard = self

        # instantiate steps
        for cls in self.steps:
            page = cls(self, self.name + '-')
            self.pages[cls.name] = page
            page.show()

        self._setup_ui()
        self.set_page(initial_page)

    def _setup_ui(self):
        w = self.widgets

        iconfile = os.path.join(configure.imagedir, 'flumotion.png')
        self.window.set_icon_from_file(iconfile)
        w['image_icon'].set_from_file(iconfile)

        # have to get the style from the theme, but it's not really there until
        # it's realized
        w['label_title'].realize()
        style = w['label_title'].get_style()

        title_bg = style.bg[gtk.STATE_SELECTED]
        title_fg = style.fg[gtk.STATE_SELECTED]
        w['eventbox_top'].modify_bg(gtk.STATE_NORMAL, title_bg)
        w['eventbox_top'].modify_bg(gtk.STATE_INSENSITIVE, title_bg)
        w['label_title'].modify_fg(gtk.STATE_NORMAL, title_fg)
        w['label_title'].modify_fg(gtk.STATE_INSENSITIVE, title_fg)
        normal_bg = style.bg[gtk.STATE_NORMAL]
        w['textview_text'].modify_base(gtk.STATE_INSENSITIVE, normal_bg)
        w['textview_text'].modify_bg(gtk.STATE_INSENSITIVE, normal_bg)
        w['eventbox_content'].modify_base(gtk.STATE_INSENSITIVE, normal_bg)
        w['eventbox_content'].modify_bg(gtk.STATE_INSENSITIVE, normal_bg)

    def set_page(self, name):
        try:
            page = self.pages[name]
        except KeyError:
            raise AssertionError('No page named %s in %r' % (name, self.pages))

        w = self.widgets
        page.button_next = w['button_next']
        page.button_prev = w['button_prev']

        available_pages = [
            p for p in page.next_pages if self.pages[p].is_available()
        ]

        w['button_prev'].set_sensitive(bool(self.page_stack))

        self.page = page
        for x in w['page_bin'].get_children():
            w['page_bin'].remove(x)
        w['page_bin'].add(page)
        w['label_title'].set_markup('<big><b>%s</b></big>' % page.title)
        w['textview_text'].get_buffer().set_text(page.text)
        w['button_next'].set_sensitive(True)
        page.setup(self.state, available_pages)

    def on_delete_event(self, *window):
        self.state = None
        self.emit('finished')

    def next(self):
        self.on_next(self.widgets['button_next'])

    def on_next(self, button):
        button.set_sensitive(False)
        next_page = self.page.on_next(self.state)

        if not next_page:
            # the input is incorrect
            pass
        elif next_page == '*finished*':
            button.set_sensitive(True)
            self.emit('finished')
        elif next_page == '*signaled*':
            # page wants to do more stuff and will signal us next_page
            # when done

            def on_finished(page, next_page):
                button.set_sensitive(True)
                self.page.disconnect(self._finished_id)
                if next_page == '*finished*':
                    self.emit('finished')
                else:
                    self.page_stack.append(self.page)
                    self.set_page(next_page)

            self._finished_id = self.page.connect('finished', on_finished)
        else:
            # work around a gtk+ bug #56070
            button.hide()
            button.show()

            button.set_sensitive(True)
            self.page_stack.append(self.page)
            self.set_page(next_page)

    def on_prev(self, button):
        page = self.page_stack.pop()
        self.button_next.set_label(gtk.STOCK_GO_FORWARD)
        self.set_page(page.name)

    def set_sensitive(self, is_sensitive):
        self.window.set_sensitive(is_sensitive)

    def run(self):
        """
        Run in a recursive main loop. Will block until the user finishes
        or closes the wizard.
        """
        loop = gobject.MainLoop()
        d = self.runAsync()

        def async_done(_):
            loop.quit()

        d.addCallbacks(async_done, async_done)
        loop.run()
        return self.state

    def stop(self):
        self.window.destroy()

    def runAsync(self):
        """
        Show the wizard. Returns a deferred that fires when the user has
        closed the wizard, either via completing the process or has
        cancelled somehow.

        @returns: a deferred that will fire the state dict accumulated
        by the pages, or None on cancel
        """
        assert self.window
        self.set_sensitive(True)
        self.show()
        from twisted.internet import defer
        d = defer.Deferred()

        def finished(x):
            self.disconnect(i)
            self.hide()
            if self.state:
                d.callback(self.state)
            else:
                d.errback(WizardCancelled())

        i = self.connect('finished', finished)
        return d
Ejemplo n.º 15
0
class Controller(gobject.GObject):
    """
    Controller for a video test producer, used to generate a video test feed.
    The controller's model produces a raw video test feed using videotestsrc.
    """
    # FIXME: decide on a good name for prepared that says "you can do stuff with me
    # now"
    gsignal('prepared')

    def __init__(self):
        """
        Create a new video test controller.
        The spyglass controller needs to be prepared.
        """
        self.__gobject_init__()
        self.view = View()
        self.model = Model()

    ### public methods

    def prepare(self):
        """
        Prepares the controller.
        Returns immediately.
        Emits 'prepared' signal when it is done preparing.
        """
        # the view's prepare is synchronous for now
        self.view.prepare()

        # add possible patterns
        self.view.add_pattern("SMPTE")
        self.view.add_pattern("Snow")
        self.view.add_pattern("0% Black")
        self.view.set_pattern(1)

        # add possible formats
        self.view.add_format("RGB")
        self.view.add_format("YUY2")
        self.view.add_format("I420")
        self.view.set_format(0)

        self.view.connect('width-changed', self.view_width_changed_cb)
        self.view.connect('height-changed', self.view_height_changed_cb)
        self.view.connect('framerate-changed', self.view_framerate_changed_cb)
        self.view.connect('format-changed', self.view_format_changed_cb)
        self.view.connect('pattern-changed', self.view_pattern_changed_cb)
        # the model doesn't currently have a prepare

    ### callbacks

    def view_width_changed_cb(self, view, width):
        _debug("width changed to %d" % width)
        self.model.set_width(width)

    def view_height_changed_cb(self, view, height):
        _debug("height changed to %d" % height)
        self.model.set_height(height)

    def view_framerate_changed_cb(self, view, framerate):
        _debug("framerate changed to %f" % framerate)
        self.model.set_framerate(framerate)

    def view_format_changed_cb(self, view, format):
        _debug("format changed to %f" % format)
        self.model.set_format(format)

    def view_pattern_changed_cb(self, view, index):
        _debug("pattern changed to index %d" % index)
        self.model.set_pattern(index)
Ejemplo n.º 16
0
class SectionWizard(GladeWindow, log.Loggable):
    """I am a section wizard which consists of the following elements::
    - header: showing step title and icon
    - sidebar should a list of step hierarchal sections
    - step area: step dependent area
    - footer: buttons; help, back, forward/finish.
    """
    gsignal('destroy')
    gsignal('help-clicked', str, str, str) # section, anchor, version

    logCategory = 'wizard'

    gladeFile = 'sectionwizard.glade'

    def __init__(self, parent_window=None):
        self._useMain = True

        GladeWindow.__init__(self, parent_window)
        for k, v in self.widgets.items():
            setattr(self, k, v)
        self.window.set_icon_from_file(os.path.join(configure.imagedir,
                                                    'flumotion.png'))
        self.window.connect_after('realize', self.on_window_realize)
        self.window.connect('destroy', self.on_window_destroy)

        self.sidebar = _WizardSidebar(self)
        self.sidebar.connect('step-chosen', self.on_sidebar_step_chosen)
        self.sidebar.set_size_request(160, -1)
        self.hbox_main.pack_start(self.sidebar, False, False)
        self.hbox_main.reorder_child(self.sidebar, 0)
        self.sidebar.show()

    def __nonzero__(self):
        return True

    def __len__(self):
        return len(self.getSteps())

    # Override this in subclass

    def completed(self):
        pass

    def beforeShowStep(self, step):
        pass

    # Public API

    def cleanFutureSteps(self):
        """Removes all the steps in front of the current one"""
        self.sidebar.cleanFutureSteps()

    def addStepSection(self, section):
        """Adds a new step section
        @param section: section to add
        @type section: a WizardStep subclass
        """
        self.sidebar.addStepSection(section)

    def getSteps(self):
        """Returns a sequence of all steps
        @returns: sequence of visited steps.
        @rtype: sequence of L{WizardStep}
        """
        return self.sidebar.getSteps()

    def getStep(self, stepname):
        """Fetches a step. KeyError is raised when the step is not found.
        @param stepname: name of the step to fetch
        @type stepname: str
        @returns: a L{WizardStep} instance or raises KeyError
        """
        return self.sidebar.getStep(stepname)

    def hasStep(self, stepName):
        """Find out if a step with name stepName exists
        @returns: if the stepName exists
        """
        return self.sidebar.hasStep(stepName)

    def getVisitedSteps(self):
        """Returns a sequence of steps which has been visited.
        Visited means that the state of the step should be considered
        when finishing the wizard.
        @returns: sequence of visited steps.
        @rtype: sequence of L{WizardStep}
        """
        return self.sidebar.getVisitedSteps()

    def getCurrentStep(self):
        """Get the current step
        @returns: the current step
        @rtype: L{WizardStep}
        """
        return self.sidebar.getCurrentStep()

    def hide(self):
        self.window.hide()

    def clear_msg(self, id):
        self.message_area.clearMessage(id)

    def add_msg(self, msg):
        self.message_area.addMessage(msg)

    def clear_all_msg(self):
        self.message_area.clear()

    def goNext(self):
        """Show the next step, this is called when
        the next button is clicked
        """
        self.sidebar.prepareNextStep(self.sidebar.getCurrentStep())

    def setStepDescription(self, description):
        """Sets the steps description.
        This is usually a sentence describing a component.
        @param description: the description
        @type description: string
        """
        self.label_description.set_markup(
            '<i>%s</i>' % _escape(description or ''))

    def blockNext(self, block):
        self.button_next.set_sensitive(not block)
        # work around a gtk+ bug #56070
        if not block:
            self.button_next.hide()
            self.button_next.show()

    def run(self, main=True):
        self._useMain = main
        self.sidebar.pushSteps()

        self.window.present()
        self.window.grab_focus()

        if self._useMain:
            try:
                gtk.main()
            except KeyboardInterrupt:
                pass

    def packStep(self, step):
        # Remove previous step
        map(self.content_area.remove, self.content_area.get_children())
        self.message_area.clear()

        # Add current
        self.content_area.pack_start(step, True, True, 0)
        self._setStepIcon(step.icon)
        self._setStepTitle(step.title)
        self.updateButtons(hasNext=True)

    def finish(self, main=True, completed=True):
        if completed:
            self.completed()

        if self._useMain:
            try:
                gtk.main_quit()
            except RuntimeError:
                pass

    def updateButtons(self, hasNext):
        # hasNext: whether or not there is a next step

        step = self.getCurrentStep()
        canGoBack = self.sidebar.canGoBack()
        hasHelp = bool(step.docSection)

        self.button_help.set_sensitive(hasHelp)
        self.button_prev.set_sensitive(canGoBack)

        if hasNext:
            self.button_next.set_label(gtk.STOCK_GO_FORWARD)
        else:
            self.button_next.set_label(_('_Finish'))
        self.button_next.grab_focus()

    # Private

    def _setStepIcon(self, icon):
        icon_filename = os.path.join(configure.imagedir, 'wizard', icon)
        assert os.path.exists(icon_filename)
        self.image_icon.set_from_file(icon_filename)

    def _setStepTitle(self, title):
        self.label_title.set_markup(
            '<span size="x-large">%s</span>' % _escape(title or ''))

    def _helpClicked(self):
        step = self.getCurrentStep()
        self.emit('help-clicked', step.docSection, step.docAnchor,
                  step.docVersion)

    # Callbacks

    def on_window_realize(self, window):
        # have to get the style from the theme, but it's not really
        # there until it's attached
        style = self.eventbox_top.get_style()
        bg = style.bg[gtk.STATE_SELECTED]
        fg = style.fg[gtk.STATE_SELECTED]
        self.eventbox_top.modify_bg(gtk.STATE_NORMAL, bg)
        self.hbuttonbox2.modify_bg(gtk.STATE_NORMAL, bg)
        self.label_title.modify_fg(gtk.STATE_NORMAL, fg)
        self.label_description.modify_fg(gtk.STATE_NORMAL, fg)

    def on_window_destroy(self, window):
        self.emit('destroy')

    def on_window_delete_event(self, wizard, event):
        self.finish(self._useMain, completed=False)

    def on_button_help_clicked(self, button):
        self._helpClicked()

    def on_button_prev_clicked(self, button):
        self.sidebar.showPreviousStep()

    def on_button_next_clicked(self, button):
        self.goNext()

    def on_sidebar_step_chosen(self, sidebar, name):
        self.sidebar.jumpToStep(name)
Ejemplo n.º 17
0
class MessagesView(gtk.VBox):
    """
    I am a widget that can show messages.
    """
    # I am a vbox with first row the label and icons,
    # second row a separator
    # and third row a text view
    gsignal('resize-event', bool)

    def __init__(self):
        gtk.VBox.__init__(self)

        self._disableTimestamps = False
        self.active_button = None

        self._createUI()
        self.clear()

        self._translator = Translator()
        localedir = os.path.join(configure.localedatadir, 'locale')
        # FIXME: add locales as messages from domains come in
        self._translator.addLocaleDir(configure.PACKAGE, localedir)

    def _createUI(self):
        h1 = gtk.HBox()
        self.pack_start(h1, False, False, 0)

        self.hline = gtk.HSeparator()
        h1.pack_start(self.hline, True, True, 3)
        # button box holding the message icons at the top right
        h2 = gtk.HBox()
        h1.pack_end(h2, False, False, 0)
        self.buttonbox = h2

        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self.pack_start(sw, True, True, 0)
        self.sw = sw

        # text view shows the messages, plus debug information
        # FIXME: needs to be hyperlinkable in the future
        tv = gtk.TextView()
        tv.set_wrap_mode(gtk.WRAP_WORD)
        tv.set_left_margin(6)
        tv.set_right_margin(6)
        tv.set_accepts_tab(False)
        tv.set_cursor_visible(False)
        tv.set_editable(False)
        #tv.set_sensitive(False)
        # connect signals to act on the hyperlink
        tv.connect('event-after', self._after_textview__event)
        tv.connect('motion-notify-event',
                   self._on_textview___motion_notify_event)
        sw.add(tv)
        self.textview = tv

        self.show_all()

    def clear(self):
        """
        Remove all messages and hide myself.
        """
        for child in self.buttonbox.get_children():
            self.clearMessage(child.message.id)
        self.hide()

    def addMessage(self, m):
        """
        Add a message to me.
        @type  m: L{flumotion.common.messages.Message}
        """
        # clear all previously added messages with the same id.  This allows
        # us to replace for example a "probing" message with the
        # result message
        self.clearMessage(m.id)

        # add a message button to show this message
        b = MessageButton(m)
        b.sigid = b.connect('toggled', self._on_message_button__toggled, m)
        b.show()
        self.buttonbox.pack_start(b, False, False, 0)

        firstButton = self._sortMessages()

        self.show()
        if not self.active_button:
            b.set_active(True)
        elif b == firstButton:
            b.set_active(True)

    def clearMessage(self, id):
        """
        Clear all messages with the given id.
        Will bring the remaining most important message to the front,
        or hide the view completely if no messages are left.
        """
        for button in self.buttonbox.get_children():
            if button.message.id != id:
                continue

            self.buttonbox.remove(button)
            button.disconnect(button.sigid)
            button.sigid = 0
            if not self.buttonbox.get_children():
                self.active_button = None
                self.hide()
            elif self.active_button == button:
                self.active_button = self.buttonbox.get_children()[0]
                self.active_button.set_active(True)
            break

    def disableTimestamps(self):
        """Disable timestamps for this MessageView,
        it will make it easier to understand the error messages and
        make it suitable for end users.
        """
        self._disableTimestamps = True

    # Private

    def _addMessageToBuffer(self, message):
        # FIXME: it would be good to have a "Debug" button when
        # applicable, instead of always showing the text
        text = self._translator.translate(message)

        textbuffer = gtk.TextBuffer()
        textbuffer.set_text(text)
        self.textview.set_buffer(textbuffer)

        # if we have help information, add it to the end of the text view
        description = message.getDescription()
        if description:
            textbuffer.insert(textbuffer.get_end_iter(), ' ')
            titer = textbuffer.get_end_iter()
            # we set the 'link' data field on tags to identify them
            translated = self._translator.translateTranslatable(description)
            tag = textbuffer.create_tag(translated)
            tag.set_property('underline', pango.UNDERLINE_SINGLE)
            tag.set_property('foreground', 'blue')
            tag.set_data('link', getMessageWebLink(message))
            textbuffer.insert_with_tags_by_name(titer, translated,
                                                tag.get_property('name'))

        timestamp = message.getTimeStamp()
        if timestamp and not self._disableTimestamps:
            text = _("\nPosted on %s.\n") % time.strftime(
                "%c", time.localtime(timestamp))
            textbuffer.insert(textbuffer.get_end_iter(), text)

        if message.debug:
            text = "\n\n" + _("Debug information:\n") + message.debug + '\n'
            textbuffer.insert(textbuffer.get_end_iter(), text)

    def _sortMessages(self):
        # Sort all messages first by (reverse of) level, then priority
        children = [(-w.message.level, w.message.priority, w)
                    for w in self.buttonbox.get_children()]
        children.sort()
        children.reverse()
        children = [(i, children[i][2]) for i in range(len(children))]
        for child in children:
            self.buttonbox.reorder_child(child[1], child[0])

        # the first button, e.g. highest priority
        return children[0][1]

    # Callbacks

    def _on_message_button__toggled(self, button, message):
        # on toggling the button, show the message
        if not button.get_active():
            if self.active_button == button:
                self.sw.hide()
                self.hline.hide()
                button.set_active(False)
                self.active_button = None
                self.emit('resize-event', True)
            return
        old_active = self.active_button
        self.active_button = button
        if old_active and old_active != button:
            old_active.set_active(False)

        self._addMessageToBuffer(message)
        self.show_all()
        self.emit('resize-event', False)

    # when the mouse cursor moves, set the cursor image accordingly

    def _on_textview___motion_notify_event(self, textview, event):
        x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
                                                int(event.x), int(event.y))
        tags = textview.get_iter_at_location(x, y).get_tags()
        # without this call, further motion notify events don't get
        # triggered
        textview.window.get_pointer()

        # if any of the tags is a link, show a hand
        cursor = None
        for tag in tags:
            if tag.get_data('link'):
                cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
                break
        textview.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor)
        return False

    def _after_textview__event(self, textview, event):
        if event.type != gtk.gdk.BUTTON_RELEASE:
            return False
        if event.button != 1:
            return False

        textbuffer = textview.get_buffer()
        # we shouldn't follow a link if the user has selected something
        bounds = textbuffer.get_selection_bounds()
        if bounds:
            [start, end] = bounds
            if start.get_offset() != end.get_offset():
                return False

        x, y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
                                                int(event.x), int(event.y))
        iter = textview.get_iter_at_location(x, y)

        for tag in iter.get_tags():
            link = tag.get_data('link')
            if link:
                import webbrowser
                log.debug('messageview', 'opening %s' % link)
                webbrowser.open(link)
                break

        return False
Ejemplo n.º 18
0
class FluTrayIcon(log.Loggable, gobject.GObject):
    """
    I represent a tray icon in GNOME's notification area for the Admin UI.

    If I cannot create a tray icon, I will still be usable but not do anything.
    """

    implements(flavors.IStateListener)

    logCategory = 'trayui'

    gsignal("quit")

    def __init__(self, window):
        """
        @type window: L{flumotion.admin.gtk.client.Window}
        """
        gobject.GObject.__init__(self)

        self._components = None
        self._window = window
        self._tray_image = None
        self._tray_icon = self._create_trayicon()
        self._set_icon_from_filename(_DEFAULT_ICON)

    # Public methods

    def update(self, components):
        """
        Update the components list

        @param components: dictionary of name ->
                           L{flumotion.common.component.AdminComponentState}
        """
        if not self._tray_icon:
            return

        self.debug('updating component in trayicon view')

        # get a dictionary of components
        self._components = components
        for component in components.values():
            try:
                component.addListener(self, set_=self.stateSet)
            except KeyError:
                pass

        self._update_mood()

    def stateSet(self, state, key, value):
        if not isinstance(state, planet.AdminComponentState):
            self.warning('Got state change for unknown object %r' % state)
            return

        if key == 'mood':
            # one of the components has changed mood
            self._update_mood()
        elif key == 'message':
            # one of the components has sent a message
            self.debug("message: %s" % value)
            if self._tray_icon and hasattr(self._tray_icon, 'send_message'):
                self._tray_icon.send_message(1000, value)

    def set_tooltip(self, tooltip):
        """
        @param tooltip: Text to show when the mouse is over the tray icon.
        @type  tooltip: str
        """
        if self._tray_icon:
            self._tray_icon.set_tooltip(tooltip)

    # Private methods

    def _create_trayicon(self):
        """
        Create the icon
        """
        icon = gtk.StatusIcon()
        icon.connect('popup-menu', self._on_trayicon__popup_menu)
        icon.connect('activate', self._on_trayicon__activated)

        return icon

    def _set_icon_from_filename(self, filename):
        if not self._tray_icon:
            return

        self._tray_icon.set_from_file(filename)

    # FIXME: looks like cutnpaste from a similar function, squash duplication

    def _update_mood(self):
        """
        This method goes through all the components to
        determine and set the overall mood.
        """
        # get a dictionary of components
        names = self._components.keys()

        if names:
            # get overall mood of components
            overallmood = moods.happy.value
            for compName in names:
                component = self._components[compName]
                mood = component.get('mood')
                self.debug("component %s has mood %d" % (compName, mood))
                if mood > overallmood:
                    overallmood = mood

            moodname = moods.get(overallmood).name
            self.debug("overall mood: %s %d" % (moodname, overallmood))
            filename = os.path.join(configure.imagedir,
                                    'mood-%s.png' % moodname)
            self._set_icon_from_filename(filename)
        else:
            # if no components, show fluendo logo
            self._set_icon_from_filename(_DEFAULT_ICON)

    # Callbacks

    def _on_trayicon__popup_menu(self, *p):
        """
        Called when we click the tray icon with the second mouse's button.
        Shows a popup menu with the quit option.
        """
        menu = gtk.Menu()
        quitButton = gtk.ImageMenuItem(gtk.STOCK_QUIT)
        quitButton.connect('activate', self._on_quit__activate)
        menu.add(quitButton)
        menu.popup(None, None, None, 3, gtk.get_current_event_time())
        menu.show_all()

    def _on_trayicon__activated(self, *p):
        """
        Called when we click the tray icon with the first mouse's button.
        Shows or hides the main window.
        """
        if self._window.get_property('visible'):
            self._window.hide()
        else:
            self._window.show()

    def _on_quit__activate(self, menu):
        """
        Called when we click the quit  option on the popup menu.
        """
        self.emit('quit')