예제 #1
0
class CascadersFrame:
    def __init__(self, debugEnabled=False, show=True, host=None):
        """
        Enabling debug does things like disable async so errors are more 
        apparent

        show should be true to show the windows by default
        """
        self.debugEnabled = debugEnabled

        hosts = os.path.join(os.path.dirname(__file__), "data", "hosts")
        self.locator = labmap.Locator(open(hosts))
        self.username = self._getUsername()

        if host is None:
            self.hostname = socket.gethostname().lower()
        else:
            self.hostname = host

        self.model = CascaderModel(self.locator, self.username, self.hostname)
        self.messageDialog = MessageDialog(self.locator, self.model.getCascaderData())

        # slightly more sane method of setting things up that uses depency
        # tracking
        req = RequireFunctions()
        req.add("gui", self.initGui)
        req.add("tray", self.initTray, ["gui"])
        req.add("map", self.initMap, ["gui"])
        req.add("labs", self.initLabs, ["map"])
        req.add("modelcallbacks", self.initModelCallbacks)
        req.add("connection", self.initConnection)
        req.add("signals", self.initSignals, ["gui", "settings"])
        req.add("settings", self.initSettings, ["gui", "connection"])
        req.add("autostart", self.askAutostart, ["gui", "settings"])
        req.run()

        if show:
            self.window.show_all()

    def askAutostart(self):
        if self.settings["asked_autostart"] == False:
            self.settings["asked_autostart"] = True
            message = gtk.MessageDialog(
                None,
                gtk.DIALOG_MODAL,
                gtk.MESSAGE_INFO,
                gtk.BUTTONS_YES_NO,
                ("Do you want to autostart this " "program on login?"),
            )
            resp = message.run()
            if resp == gtk.RESPONSE_YES:
                self.builder.get_object("cbAutostart").set_active(True)
            message.destroy()

    def initMap(self):
        self.map = labmap.Map(self.builder.get_object("tblMap"), self.locator, self.model.getCascaderData())

    def initTray(self):
        icon = os.path.join(os.path.dirname(__file__), "icons", "cascade.ico")
        self.trayIcon = TrayIcon(self, icon)
        self.window.connect("delete-event", lambda w, e: w.hide() or True)

    def initSignals(self):
        """ Signals catch events and shut things down properly """
        signal.signal(signal.SIGINT, self.quit)
        signal.signal(signal.SIGTERM, self.quit)

    def initGui(self):
        self.builder = gtk.Builder()

        dr = os.path.dirname(__file__)
        self.builder.add_from_file(os.path.join(dr, "gui", "main.glade"))

        initTreeView(self.builder.get_object("tvCascList"))
        initTreeView(self.builder.get_object("tvCascSubjects"))

        self.window = self.builder.get_object("wnCascader")
        self.window.connect("destroy", lambda *a: gtk.main_quit())
        self.builder.connect_signals(self)

        pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(dr, "icons", "cascade.ico"))
        self.window.set_icon(pixbuf)

    def initLabs(self):
        """ Sets up the labs drop down box stuff """
        lst = gtk.ListStore(gobject.TYPE_STRING)
        lst.append(["All"])
        for lab in self.locator.getLabs():
            lst.append([lab])
        cb = self.builder.get_object("cbFilterLab")
        cb.set_model(lst)
        cell = gtk.CellRendererText()
        cb.set_active(0)
        cb.pack_start(cell, True)
        cb.add_attribute(cell, "text", 0)

    def initSettings(self):
        self.settings = settings.loadSettings()

        autostart = self.settings["autostart"]
        self.builder.get_object("cbAutostart").set_active(autostart)
        autocascade = self.settings["autocascade"]
        self.builder.get_object("cbAutocascade").set_active(autocascade)

        debug("Got subjects from settings: %s" % str(self.settings["cascSubjects"]))

        self.addSubjects(self.settings["cascSubjects"])

        if self.settings["cascading"] and self.settings["autocascade"]:
            self.startCascading()

    def _getUsername(self):
        try:
            logname = os.environ["LOGNAME"]

            # for debugging only, means multiple clients can be run at once
            if self.debugEnabled == True:
                import random

                logname = str(random.random())
        except KeyError:
            errorDialog(("Couldn't get LOGNAME from the enviroment," " this only runs on Linux at the moment"))
            self.quit()
        return logname

    def initModelCallbacks(self):
        """
        This sets up the service callbacks
        """
        self.model.registerOnCascaderChanged(self.updateCascaderLists)
        self.model.registerOnSubjectChanged(self.updateAllSubjects)
        self.model.registerOnUserAskingForHelp(self.onUserAskingForHelp)

        self.model.registerOnDisconnected(self.onDisconnect)
        self.model.registerOnConnected(self.onLogin)

    def onDisconnect(self):
        status = self.builder.get_object("lbStatus")
        status.set("Connecting...")

    def onLogin(self):
        status = self.builder.get_object("lbStatus")
        status.set("Connected")

    def initConnection(self):
        """
        called in the constructor. also does the setup post connect
        """
        debug("Connecting...")
        self.builder.get_object("lbUsername").set(self.username)

        def loginErr(reason):
            reason = reason.trap(ValueError)
            errorDialog("Failed to login, server reported %s" % reason.getErrorMessage())
            self.quit()

        def catchallLoginErr(reason):
            errorDialog("Failed to login, server reported %s" % reason.getErrorMessage())
            self.quit()

        def connectErrRefused(reason):
            reason.trap(twisted.internet.error.ConnectionRefusedError)
            errorDialog("Failed to connect to the " "server, the connection was refused")
            self.quit()

        def catchallConnectErr(reason):
            errorDialog("Failed to connect to the " "server: %s" % reason.getErrorMessage())
            self.quit()

        def connected(result):
            d = self.model.login()
            d.addErrback(loginErr)
            d.addErrback(catchallLoginErr)

        d = self.model.connect()
        d.addCallback(connected)
        d.addErrback(connectErrRefused)
        d.addErrback(catchallConnectErr)

    # --------------------------------------------------------------------------
    def setupMessagingWindow(self, helpid, toUsername, remoteHost, isUserCasc):
        """
        toUsername - the username of the remote
        remoteHost - the hostname of the remote
        """
        self.messageDialog.addTab(helpid, toUsername, self.hostname, remoteHost, isUserCasc)

        # setup functions to write to the messages from the message dialog to
        # the server
        def onMessageFromServer(fromType, message):
            if fromType == "user":
                fromName = self.username
            elif fromType == "server":
                fromName = "Server"
            self.messageDialog.writeMessage(helpid, fromName, message)

        self.model.registerOnMessgeHandler(helpid, onMessageFromServer)

        def badHelpidError(reason):
            reason.trap(service.BadHelpid)
            msg = "The remote client didn't have a id for this conversation, " "it may be too old. Message not sent"
            self.messageDialog.writeMessage(helpid, "Server", msg)

        def writeError(reason):
            reason.trap("__main__.ClientNotConnected")
            msg = "The connection to the remote client was lost. " "Message not sent"
            self.messageDialog.writeMessage(helpid, "Server", msg)
            return reason

        def writeFunction(message):
            d = self.model.sendMessage(helpid, toUsername, message)
            d.addErrback(badHelpidError)
            d.addErrback(writeError)

        self.messageDialog.registerMessageCallback(helpid, writeFunction)

        self.messageDialog.window.show()

    # --------------------------------------------------------------------------
    # Service callback functions, most of these are just simple wrappers
    # around the cascaders class.

    def onUserAskingForHelp(self, helpid, username, host, subject, description):
        """
        Called from the server to the client cascader to see if help can
        be accepted
        """
        debug("Help wanted by: %s with host %s" % (username, host))

        dialog = AcceptHelpDialog(self.window, username, subject, description)

        # check if user can give help
        if dialog.isAccept():
            debug("Help Accepted")
            self.setupMessagingWindow(helpid, username, host, True)
            return (True, "")

        debug("Help rejected")
        return (False, "")

    # --------------------------------------------------------------------------
    def updateAllSubjects(self, subjects):
        """
        Calling this ensures that the gui reflects the current list of subjects
        """
        debug("Subjects: %s" % subjects)

        cascCb = self.builder.get_object("cbCascSubjectList")
        lst = gtk.ListStore(gobject.TYPE_STRING)
        [lst.append([subject]) for subject in subjects]
        cascCb.set_model(lst)
        cell = gtk.CellRendererText()
        cascCb.set_active(0)
        cascCb.pack_start(cell, True)
        cascCb.add_attribute(cell, "text", 0)

        cb = self.builder.get_object("cbFilterSubject")
        lst = gtk.ListStore(gobject.TYPE_STRING)
        lst.append(["All"])
        [lst.append([subject]) for subject in subjects]
        cb.set_model(lst)
        cell = gtk.CellRendererText()
        cb.set_active(0)
        cb.pack_start(cell, True)
        cb.add_attribute(cell, "text", 0)

    def updateCascaderLists(self, cascaders):
        """
        Cleans the list and updates the list of cascaders avaible. Call
        when filters have been changed
        """

        ls = self.builder.get_object("lsCascList")
        ls.clear()

        cbSubjects = self.builder.get_object("cbFilterSubject")
        filterSub = getComboBoxText(cbSubjects)
        filterSub = [filterSub] if filterSub != "All" else None

        cbLab = self.builder.get_object("cbFilterLab")
        filterLab = getComboBoxText(cbLab)
        filterLab = filterLab if filterLab != "All" else None

        cascaders = list(cascaders.findCascaders(lab=filterLab, subjects=filterSub))
        [ls.append([username]) for username, _ in cascaders]
        debug("Updating cascaders from: %s" % str(cascaders))

    # --------------------------------------------------------------------------
    # GUI events
    def quit(self, *a):
        """
        This quits the application, doing the required shutdown stuff. If some
        thing goes in here, try to assume nothing about the state of the
        application.
        """
        debug("Starting shutdown")

        if self.settings:
            debug("Updating Settings")
            self.settings["cascSubjects"] = list(self.model.cascadingSubjects())
            self.settings["cascading"] = self.model.isCascading()
            self.settings["autostart"] = self.builder.get_object("cbAutostart").get_active()
            self.settings["autocascade"] = self.builder.get_object("cbAutocascade").get_active()

        # quit the gui to try and make everything look snappy (so we don't lock
        # when messing around doing IO. we can't use destory else other
        # things don't work
        if self.window:
            self.window.hide_all()

        if self.model is not None:
            debug("Logging out")
            try:
                gobject.timeout_add(5000, self._finishQuitTimeout)
                l = self.model.logout()
                l.addCallback(self._finishQuit)
                l.addErrback(self._finishQuitErr)
            except Exception as e:
                debug("..Failed %s " % e)
                self._finishQuit()
        else:
            debug("No client, going to second stage shutdown directly")
            self._finishQuit()

    def _finishQuitTimeout(self):
        debug("Logging out timed out")
        self._finishQuit()
        return False  # we should return false here due to using timeout_add

    def _finishQuitErr(self, reason):
        debug("There was an error logging out %s" % str(reason))
        self._finishQuit()

    def _finishQuit(self, result=None):
        debug("In second stage shutdown")
        if self.window:
            self.window.destroy()

        # seems to be a bug, but we need to clean up the threadpool
        if reactor.threadpool is not None:
            reactor.threadpool.stop()
        try:
            reactor.stop()
        except twisted.internet.error.ReactorNotRunning:
            debug("Reactor wasn't running, so couldn't stop it")

        if self.settings:
            settings.saveSettings(self.settings)

        debug("Finished shutdown, goodbye")

    def onStartStopCascading(self, event):
        """ Toggles cascading """
        btn = self.builder.get_object("btStartStopCasc")
        btn.set_sensitive(False)
        if self.model.isCascading():
            debug("Stopping Cascading")
            self.stopCascading()
        else:
            debug("Starting Cascading")
            self.startCascading()

    def stopCascading(self):
        btn = self.builder.get_object("btStartStopCasc")
        self.model.stopCascading().addCallback(lambda *a: btn.set_sensitive(True))
        btn.set_label("Start Cascading")

    def startCascading(self):
        btn = self.builder.get_object("btStartStopCasc")

        # we offer the user to automatically start cascading
        askedAutoCascading = self.settings["asked_autocascade"]
        autoCascade = self.settings["autocascade"] == False
        if askedAutoCascading == False and autoCascade:
            self.settings["asked_autocascade"] = True

            msg = "Do you always want to start cascading on program startup"
            message = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, msg)
            resp = message.run()
            if resp == gtk.RESPONSE_YES:
                self.builder.get_object("cbAutocascade").set_active(True)
            message.destroy()

        self.model.startCascading().addCallback(lambda *a: btn.set_sensitive(True))
        btn.set_label("Stop Cascading")

    def onCascaderClick(self, tv, event):
        if event.button != 1 or event.type != gtk.gdk._2BUTTON_PRESS:
            return
        model, itr = tv.get_selection().get_selected()
        if itr:
            cascaderUsername = model.get_value(itr, 0)
            self.askForHelp(cascaderUsername)

    def askForHelp(self, cascaderUsername):
        (_, (cascHost, cascSubjects)) = self.model.getCascaderData().findCascader(username=cascaderUsername)

        # ask user topic, brief description
        subject = None
        if getComboBoxText(self.builder.get_object("cbFilterSubject")) != "All":
            subject = getComboBoxText(self.builder.get_object("cbFilterSubject"))
        helpDialog = AskForHelp(self.window, cascSubjects, subject)

        if helpDialog.isOk():
            debug("Dialog is ok, asking for help")
            helpid = (self.username, util.generateUnqiueId())

            self.setupMessagingWindow(helpid, cascaderUsername, cascHost, False)

            writeSysMsg = lambda m: self.messageDialog.writeMessage(helpid, "SYSTEM", m)
            writeSysMsg("Waiting for response...")

            def onNotConnected(reason):
                reason.trap(client.ClientNotConnected)
                writeSysMsg("Error: The client was not connected")

            d = self.model.askForHelp(helpid, cascaderUsername, helpDialog.getSubject(), helpDialog.getDescription())
            d.addErrback(onNotConnected)

    def onAddSubject(self, event):
        cb = self.builder.get_object("cbCascSubjectList")
        self.addSubjects([getComboBoxText(cb)])

    def addSubjects(self, subjects):
        ls = self.builder.get_object("lsCascSubjects")
        for subject in subjects:
            if subject not in self.model.cascadeSubjects:
                debug("Adding subject: %s" % subject)
                ls.append([subject])

        self.model.addSubjects(subjects)

    def onRemoveSubject(self, event):
        tv = self.builder.get_object("tvCascSubjects")
        model, itr = tv.get_selection().get_selected()
        if itr is not None:
            subject = model.get_value(itr, 0)
            model.remove(itr)
            self.model.removeSubjects([subject])

    # Filter Stuff
    def onSubjectSelect(self, event):
        self.updateCascaderLists(self.model.getCascaderData())

    def onLabSelect(self, event):
        self.updateCascaderLists(self.model.getCascaderData())

    # -- -----------------------------------------------------------------------

    def onFilterLabChange(self, evt):
        debug("Filter Lab Changed")

        self.updateCascaderLists(self.model.getCascaderData())

        cbLab = self.builder.get_object("cbFilterLab")
        lab = getComboBoxText(cbLab)
        self.updateMap(lab)

    def onFilterSubjectChange(self, evt):
        debug("Filter Subject Changed")
        self.updateCascaderLists(self.model.getCascaderData())

    def updateMap(self, lab):
        cbSubjects = self.builder.get_object("cbFilterSubject")
        filterSub = getComboBoxText(cbSubjects)
        filterSub = [filterSub] if filterSub != "All" else None

        def onHostClick(event, widgit, host):
            """
            Function is passed into the map and called when the user
            clicks on a host
            """
            casc = self.model.getCascaderData().findCascader(host=host, subjects=filterSub)
            if casc is None:
                debug("Clicked on a host (%s) that wasn't " "cascading for the given filter" % host)
                return
            (username, _) = casc
            self.askForHelp(username)

        self.map.applyFilter(lab, myHost=self.hostname, subjects=filterSub, onClick=onHostClick)
예제 #2
0
class CascadersFrame:
    def __init__(self, debugEnabled=False, show=True, host=None):
        '''
        Enabling debug does things like disable async so errors are more 
        apparent

        show should be true to show the windows by default
        '''
        self.debugEnabled = debugEnabled


        hosts = os.path.join(os.path.dirname(__file__), 'data', 'hosts')
        self.locator = labmap.Locator(open(hosts))
        self.username = self._getUsername()


        if host is None:
            self.hostname = socket.gethostname().lower()
        else:
            self.hostname = host

        self.model = CascaderModel(self.locator, self.username, self.hostname)
        self.messageDialog = MessageDialog(self.locator, self.model.getCascaderData())

        #slightly more sane method of setting things up that uses depency
        #tracking
        req = RequireFunctions()
        req.add('gui', self.initGui)
        req.add('tray', self.initTray, ['gui'])
        req.add('map', self.initMap, ['gui'])
        req.add('labs', self.initLabs, ['map'])
        req.add('modelcallbacks', self.initModelCallbacks)
        req.add('connection', self.initConnection)
        req.add('signals', self.initSignals, ['gui', 'settings'])
        req.add('settings', self.initSettings, ['gui', 'connection'])
        req.add('autostart', self.askAutostart, ['gui', 'settings'])
        req.run()

        if show:
            self.window.show_all()

    def askAutostart(self):
        if self.settings['asked_autostart'] == False:
            self.settings['asked_autostart'] = True
            message = gtk.MessageDialog(None,
                                        gtk.DIALOG_MODAL,
                                        gtk.MESSAGE_INFO,
                                        gtk.BUTTONS_YES_NO,
                                        ('Do you want to autostart this '
                                         'program on login?'))
            resp = message.run()
            if resp == gtk.RESPONSE_YES:
                self.builder.get_object('cbAutostart').set_active(True)
            message.destroy()

    def initMap(self):
        self.map = labmap.Map(self.builder.get_object('tblMap'),
                              self.locator,
                              self.model.getCascaderData())

    def initTray(self):
        icon = os.path.join(os.path.dirname(__file__),
                            'icons',
                            'cascade.ico')
        self.trayIcon = TrayIcon(self, icon)
        self.window.connect('delete-event', lambda w, e: w.hide() or True)

    def initSignals(self):
        ''' Signals catch events and shut things down properly '''
        signal.signal(signal.SIGINT, self.quit)
        signal.signal(signal.SIGTERM, self.quit)

    def initGui(self):
        self.builder = gtk.Builder()

        dr = os.path.dirname(__file__)
        self.builder.add_from_file(os.path.join(dr, 'gui', 'main.glade'))

        initTreeView(self.builder.get_object('tvCascList'))
        initTreeView(self.builder.get_object('tvCascSubjects'))

        self.window = self.builder.get_object('wnCascader')
        self.window.connect('destroy', lambda *a: gtk.main_quit())
        self.builder.connect_signals(self)

    def initLabs(self):
        ''' Sets up the labs drop down box stuff '''
        lst = gtk.ListStore(gobject.TYPE_STRING)
        lst.append(['All'])
        for lab in self.locator.getLabs():
            lst.append([lab])
        cb = self.builder.get_object('cbFilterLab')
        cb.set_model(lst)
        cell = gtk.CellRendererText()
        cb.set_active(0)
        cb.pack_start(cell, True)
        cb.add_attribute(cell, 'text', 0)

    def initSettings(self):
        self.settings = settings.loadSettings()

        autostart = self.settings['autostart']
        self.builder.get_object('cbAutostart').set_active(autostart)
        autocascade = self.settings['autocascade']
        self.builder.get_object('cbAutocascade').set_active(autocascade)

        debug('Got subjects from settings: %s' % str(self.settings['cascSubjects']))

        self.addSubjects(self.settings['cascSubjects'])

        if self.settings['cascading'] and self.settings['autocascade']:
            self.startCascading()

    def _getUsername(self):
        try:
            logname = os.environ['LOGNAME']
            
            #for debugging only, means multiple clients can be run at once
            if self.debugEnabled == True:
                import random
                logname = str(random.random())
        except KeyError:
            errorDialog(('Couldn\'t get LOGNAME from the enviroment,'
                         ' this only runs on Linux at the moment'))
            self.quit()
        return logname

    def initModelCallbacks(self):
        '''
        This sets up the service callbacks
        '''
        self.model.registerOnCascaderChanged(self.updateCascaderLists)
        self.model.registerOnSubjectChanged(self.updateAllSubjects)
        self.model.registerOnUserAskingForHelp(self.onUserAskingForHelp)

        self.model.registerOnDisconnected(self.onDisconnect)
        self.model.registerOnConnected(self.onLogin)

    def onDisconnect(self):
        status = self.builder.get_object('lbStatus')
        status.set('Connecting...')

    def onLogin(self):
        status = self.builder.get_object('lbStatus')
        status.set('Connected')

    def initConnection(self):
        '''
        called in the constructor. also does the setup post connect
        '''
        debug('Connecting...')
        self.builder.get_object('lbUsername').set(self.username)

        def loginErr(reason):
            reason = reason.trap(ValueError)
            errorDialog('Failed to login, server reported %s' % reason.getErrorMessage())
            self.quit()

        def connectErr(reason):
            reason.trap(twisted.internet.error.ConnectionRefusedError)
            errorDialog('Failed to connect to the '
                        'server, the connection was refused')
            self.quit()

        def connected(result):
            d = self.model.login()
            d.addErrback(loginErr)

        d = self.model.connect()
        d.addCallback(connected)
        d.addErrback(connectErr)

    #--------------------------------------------------------------------------
    def setupMessagingWindow(self, helpid, toUsername, remoteHost, isUserCasc):
        '''
        toUsername - the username of the remote
        remoteHost - the hostname of the remote
        '''
        self.messageDialog.addTab(helpid, toUsername,
                                  self.hostname, remoteHost, isUserCasc)

        #setup functions to write to the messages from the message dialog to
        #the server
        def onMessageFromServer(fromType, message):
            if fromType == 'user':
                fromName = toUsername 
            elif fromType == 'server':
                fromName = 'Server'
            self.messageDialog.writeMessage(helpid, self.username, message)
            
        self.model.registerOnMessgeHandler(helpid, onMessageFromServer)

        def writeFunction(message):
            try:
                self.model.sendMessage(helpid, toUsername, message)
            except client.NotConnected:
                self.onServerLost()

        self.messageDialog.registerMessageCallback(helpid, writeFunction)

        self.messageDialog.window.show_all()

    #--------------------------------------------------------------------------
    # Service callback functions, most of these are just simple wrappers
    # around the cascaders class.

    def onUserAskingForHelp(self,  helpid, username, host,
                            subject, description):
        '''
        Called from the server to the client cascader to see if help can
        be accepted
        '''
        debug('Help wanted by: %s with host %s' % (username, host))

        dialog = AcceptHelpDialog(self.window, username, subject, description)

        #check if user can give help
        if dialog.isAccept():
            debug('Help Accepted')
            self.setupMessagingWindow(helpid, username, host, True)
            return (True, '')

        debug('Help rejected')
        return (False, '')


    #--------------------------------------------------------------------------
    def updateAllSubjects(self, subjects):
        '''
        Calling this ensures that the gui reflects the current list of subjects
        '''
        debug('Subjects: %s' % subjects)

        cascCb = self.builder.get_object('cbCascSubjectList')
        lst = gtk.ListStore(gobject.TYPE_STRING)
        [lst.append([subject]) for subject in subjects]
        cascCb.set_model(lst)
        cell = gtk.CellRendererText()
        cascCb.set_active(0)
        cascCb.pack_start(cell, True)
        cascCb.add_attribute(cell, 'text', 0)

        cb = self.builder.get_object('cbFilterSubject')
        lst = gtk.ListStore(gobject.TYPE_STRING)
        lst.append(['All'])
        [lst.append([subject]) for subject in subjects]
        cb.set_model(lst)
        cell = gtk.CellRendererText()
        cb.set_active(0)
        cb.pack_start(cell, True)
        cb.add_attribute(cell, 'text', 0)

    def updateCascaderLists(self, cascaders):
        '''
        Cleans the list and updates the list of cascaders avaible. Call
        when filters have been changed
        '''

        ls = self.builder.get_object('lsCascList')
        ls.clear()

        cbSubjects = self.builder.get_object('cbFilterSubject')
        filterSub = getComboBoxText(cbSubjects)
        filterSub = [filterSub] if filterSub != 'All'  else None

        cbLab = self.builder.get_object('cbFilterLab')
        filterLab = getComboBoxText(cbLab)
        filterLab = filterLab if filterLab != 'All' else None

        cascaders = list(cascaders.findCascaders(lab=filterLab,
                                                 subjects=filterSub))
        [ls.append([username]) for username, _ in cascaders]
        debug('Updating cascaders from: %s' % str(cascaders))

    #--------------------------------------------------------------------------
    # GUI events
    def quit(self, *a):
        '''
        This quits the application, doing the required shutdown stuff. If some
        thing goes in here, try to assume nothing about the state of the
        application.
        '''
        debug('Starting shutdown')

        if self.settings:
            debug('Updating Settings')
            self.settings['cascSubjects'] = list(self.model.cascadingSubjects())
            self.settings['cascading'] = self.model.isCascading()
            self.settings['autostart'] = self.builder.get_object('cbAutostart').get_active()
            self.settings['autocascade'] = self.builder.get_object('cbAutocascade').get_active()

        #quit the gui to try and make everything look snappy (so we don't lock
        #when messing around doing IO. we can't use destory else other 
        #things don't work
        if self.window:
            self.window.hide_all() 

        if self.model is not None:
            debug('Logging out')
            try:
                gobject.timeout_add(5000, self._finishQuitTimeout)
                l = self.model.logout()
                l.addCallback(self._finishQuit)
                l.addErrCallback(self._finishQuitErr)
            except Exception:
                self._finishQuit()
        else:
            debug('No client, going to second stage shutdown directly')
            self._finishQuit()

    def _finishQuitTimeout(self):
        debug('Logging out timed out')
        self._finishQuit()
        return False #we should return false here due to using timeout_add

    def _finishQuitErr(self, reason):
        debug('There was an error logging out %s' % str(reason))
        self._finishQuit()

    def _finishQuit(self, result=None):
        debug('In second stage shutdown')
        if self.window:
            self.window.destroy()

        #seems to be a bug, but we need to clean up the threadpool
        if reactor.threadpool is not None:
            reactor.threadpool.stop()
        try:
            reactor.stop()
        except twisted.internet.error.ReactorNotRunning:
            debug('Reactor wasn\'t running, so couldn\'t stop it')

        if self.settings:
            settings.saveSettings(self.settings)

        debug('Finished shutdown, goodbye')

    def onStartStopCascading(self, event):
        ''' Toggles cascading '''
        btn = self.builder.get_object('btStartStopCasc')
        btn.set_sensitive(False)
        if self.model.isCascading():
            debug('Stopping Cascading')
            self.stopCascading()
        else:
            debug('Starting Cascading')
            self.startCascading()

    def stopCascading(self):
        btn = self.builder.get_object('btStartStopCasc')
        self.model.stopCascading().addCallback(lambda *a: btn.set_sensitive(True))
        btn.set_label('Start Cascading')

    def startCascading(self):
        btn = self.builder.get_object('btStartStopCasc')

        #we offer the user to automatically start cascading
        askedAutoCascading = self.settings['asked_autocascade']
        autoCascade = self.settings['autocascade'] == False
        if askedAutoCascading == False and autoCascade:
            self.settings['asked_autocascade'] = True

            message = gtk.MessageDialog(None,
                                        gtk.DIALOG_MODAL,
                                        gtk.MESSAGE_INFO,
                                        gtk.BUTTONS_YES_NO,
                                        "Do you want to enable auto cascading")
            resp = message.run()
            if resp == gtk.RESPONSE_YES:
                self.builder.get_object('cbAutocascade').set_active(True)
            message.destroy()


        self.model.startCascading().addCallback(lambda *a: btn.set_sensitive(True))
        btn.set_label('Stop Cascading')

    def onCascaderClick(self, tv, event):
        if event.button != 1 or event.type != gtk.gdk._2BUTTON_PRESS:
            return
        model, itr = tv.get_selection().get_selected()
        if itr:
            cascaderUsername = model.get_value(itr, 0)
            self.askForHelp(cascaderUsername)

    def askForHelp(self, cascaderUsername):
        (_, (cascHost, cascSubjects)) = self.model.getCascaderData().findCascader(username=cascaderUsername)

        #ask user topic, brief description
        subject = None
        if getComboBoxText(self.builder.get_object('cbFilterSubject')) != 'All':
            subject = getComboBoxText(self.builder.get_object('cbFilterSubject'))
        helpDialog = AskForHelp(self.window, cascSubjects, subject)

        if helpDialog.isOk():
            debug('Dialog is ok, asking for help')
            helpid = (self.username, util.generateUnqiueId())

            self.setupMessagingWindow(helpid, cascaderUsername, cascHost, False) 

            writeSysMsg = lambda m: self.messageDialog.writeMessage(helpid, 'SYSTEM', m)
            writeSysMsg('Waiting for response...')

            def onNotConnected(reason):
                reason.trap(client.ClientNotConnected)
                writeSysMsg('Error: The client was not connected')

            try:
                d = self.model.askForHelp(helpid,
                                          cascaderUsername,
                                          helpDialog.getSubject(),
                                          helpDialog.getDescription())
                d.addErrback(onNotConnected)
            except client.NotConnected:
                self.onServerLost()
    
    def onAddSubject(self, event):
        cb = self.builder.get_object('cbCascSubjectList')
        self.addSubjects([getComboBoxText(cb)])

    def addSubjects(self, subjects):
        ls = self.builder.get_object('lsCascSubjects')
        for subject in subjects:
            if subject not in self.model.cascadeSubjects:
                debug('Adding subject: %s' % subject)
                ls.append([subject])

        self.model.addSubjects(subjects)
    
    def onRemoveSubject(self, event):
        tv = self.builder.get_object('tvCascSubjects')
        model, itr = tv.get_selection().get_selected()
        if itr is not None:
            subject = model.get_value(itr, 0)
            model.remove(itr)
            self.model.removeSubjects([subject])

    # Filter Stuff
    def onSubjectSelect(self, event):
        self.updateCascaderLists(self.model.getCascaderData())
    
    def onLabSelect(self, event):
        self.updateCascaderLists(self.model.getCascaderData())

    #-- -----------------------------------------------------------------------

    def onFilterLabChange(self, evt):
        '''
        This is called before a lot of the things are created fully
        as it is set to its default value. This has to check that things
        fully exist before calling functions on them
        '''
        debug('Filter Lab Changed')

        self.updateCascaderLists(self.model.getCascaderData())

        cbLab = self.builder.get_object('cbFilterLab')
        lab = getComboBoxText(cbLab)
        self.updateMap(lab)

    def onFilterSubjectChange(self, evt):
        debug('Filter Subject Changed')
        self.updateCascaderLists(self.model.getCascaderData())

    def updateMap(self, lab):
        cbSubjects = self.builder.get_object('cbFilterSubject')
        filterSub = getComboBoxText(cbSubjects)
        filterSub = [filterSub] if filterSub != 'All'  else None

        def onHostClick(event, widgit, host):
            casc = self.cascaders.findCascader(host=host, subjects=filterSub)
            if casc is None:
                debug('Clicked on a host (%s) that wasn\'t '
                      'cascading for the given filter' % host)
                return
            (username, _) = casc
            self.askForHelp(username)

        self.map.applyFilter(lab,
                             myHost=self.hostname,
                             subjects=filterSub,
                             onClick=onHostClick)