Пример #1
0
 def __init__( self, seedNodes, reactor ) :
     QMainWindow.__init__( self )
     self.ui = Ui_MainWindow()
     self.ui.setupUi( self )
     self._setupIcons()
     self.seedNodes = seedNodes
     self.reactor = reactor
     self.session = UserSession( seedNodes, reactor )
     self.sm = self.session.sm
     self.ev = Eventer()
     self.profile = None
     self.baseTitle = str(self.windowTitle())
     self.exitAfterOffline = False
     self.reconnector = None
     self.keyInfoDialog = None
     self.addContactDialog = None
     self.sm.insertCallback( self._onStateChange )
     self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
     self.ui.contacts.setWrapping( True )
     self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
             self._onContactsContextMenuRequested )
     self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
             self._onContactDoubleClicked )
     self.contextMenu = QMenu()
     self.contextMenu.addAction( self.ui.actionCheckStatus )
     self.contextMenu.addAction( self.ui.actionRemoveContact )
     self.contextMenu.addAction( self.ui.actionContactInfo )
     self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
     self.actionManager = ActionManager( self.contextMenu )
     self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
     self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
     self.contactInfoDialogs = {}
     self.trayMenu = QMenu( self )
     self.trayMenu.addAction( self.ui.actionExit )
     if HAS_TRAY :
         self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
         self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
         self.trayIcon.show()
     else :
         self.trayIcon = None
     self.editPermissionsDialog = None
     self.autoUpdater = AutoUpdater( self.reactor )
     self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
     self._updateUI()
     self._doCheckSettings()
Пример #2
0
 def __init__( self, seedNodes, reactor ) :
     QMainWindow.__init__( self )
     self.ui = Ui_MainWindow()
     self.ui.setupUi( self )
     self._setupIcons()
     self.seedNodes = seedNodes
     self.reactor = reactor
     self.session = UserSession( seedNodes, reactor )
     self.sm = self.session.sm
     self.ev = Eventer()
     self.profile = None
     self.baseTitle = str(self.windowTitle())
     self.exitAfterOffline = False
     self.reconnector = None
     self.keyInfoDialog = None
     self.addContactDialog = None
     self.sm.insertCallback( self._onStateChange )
     self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
     self.ui.contacts.setWrapping( True )
     self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
             self._onContactsContextMenuRequested )
     self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
             self._onContactDoubleClicked )
     self.contextMenu = QMenu()
     self.contextMenu.addAction( self.ui.actionCheckStatus )
     self.contextMenu.addAction( self.ui.actionRemoveContact )
     self.contextMenu.addAction( self.ui.actionContactInfo )
     self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
     self.actionManager = ActionManager( self.contextMenu )
     self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
     self.appletServer.registerServices()
     self.appletServer.registerActions()
     self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
     self.contactInfoDialogs = {}
     self.trayMenu = QMenu( self )
     self.trayMenu.addAction( self.ui.actionExit )
     if HAS_TRAY :
         self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
         self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
         self.trayIcon.show()
     else :
         self.trayIcon = None
     self.editPermissionsDialog = None
     self.autoUpdater = AutoUpdater( self.reactor )
     self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
     self._updateUI()
     self._doCheckSettings()
Пример #3
0
class MainWindow( QMainWindow ) :
    OFFLINE = UserSession.OFFLINE
    CONNECTING = UserSession.CONNECTING
    ONLINE = UserSession.ONLINE
    DISCONNECTING = UserSession.DISCONNECTING

    def __init__( self, seedNodes, reactor ) :
        QMainWindow.__init__( self )
        self.ui = Ui_MainWindow()
        self.ui.setupUi( self )
        self._setupIcons()
        self.seedNodes = seedNodes
        self.reactor = reactor
        self.session = UserSession( seedNodes, reactor )
        self.sm = self.session.sm
        self.ev = Eventer()
        self.profile = None
        self.baseTitle = str(self.windowTitle())
        self.exitAfterOffline = False
        self.reconnector = None
        self.keyInfoDialog = None
        self.addContactDialog = None
        self.sm.insertCallback( self._onStateChange )
        self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
        self.ui.contacts.setWrapping( True )
        self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
                self._onContactsContextMenuRequested )
        self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
                self._onContactDoubleClicked )
        self.contextMenu = QMenu()
        self.contextMenu.addAction( self.ui.actionCheckStatus )
        self.contextMenu.addAction( self.ui.actionRemoveContact )
        self.contextMenu.addAction( self.ui.actionContactInfo )
        self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
        self.actionManager = ActionManager( self.contextMenu )
        self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
        self.appletServer.registerServices()
        self.appletServer.registerActions()
        self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
        self.contactInfoDialogs = {}
        self.trayMenu = QMenu( self )
        self.trayMenu.addAction( self.ui.actionExit )
        if HAS_TRAY :
            self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
            self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
            self.trayIcon.show()
        else :
            self.trayIcon = None
        self.editPermissionsDialog = None
        self.autoUpdater = AutoUpdater( self.reactor )
        self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
        self._updateUI()
        self._doCheckSettings()

    def _setupIcons( self ) :
        def makeIcon( name ) :
            icon = QIcon()
            for res in '16 22 32'.split() :
                icon.addFile( ':/images/%s%s.png' % (name,res) )
            return icon
        actionIcons = (
                'CreateKey register',
                'GoOnline connect',
                'GoOffline disconnect',
                'Exit exit',
                'AddContact user_add',
                'RefreshStatus refresh',
                'CheckStatus refresh',
                'ContactInfo contact_info',
                'RemoveContact user_remove',
                'EditPermissions edit_permissions',
                'KeyInfo key_info' )
        for s in actionIcons :
            name,icon = s.split()
            action = getattr( self.ui, 'action%s'%name )
            action.setIcon( makeIcon(icon) )

    def _updateUI( self ) :
        state = self.sm.current()
        online = (state == self.ONLINE)
        offline = (state == self.OFFLINE)
        connecting = (state == self.CONNECTING)
        disconnecting = (state == self.DISCONNECTING)
        self.ui.actionCreateKey.setEnabled( offline )
        self.ui.actionGoOnline.setEnabled( offline )
        self.ui.actionGoOffline.setEnabled( online )
        self.ui.actionKeyInfo.setEnabled( online )
        self.ui.actionExit.setEnabled( True )
        self.ui.actionAddContact.setEnabled( online )
        self.ui.actionRefreshStatus.setEnabled( online )
        self.ui.actionCheckStatus.setEnabled( online )
        self.ui.actionContactInfo.setEnabled( online )
        self.ui.actionRemoveContact.setEnabled( online )
        self.ui.actionEditPermissions.setEnabled( online )
        self.ui.actionAboutCSpace.setEnabled( True )
        if online : msg = 'Online'
        if offline : msg = 'Offline'
        if connecting : msg = 'Going Online...'
        if disconnecting : msg = 'Going Offline...'
        if not offline :
            msg = '%s - %s' % (self.profile.name,msg)
        self.setWindowTitle( ' - '.join([msg,self.baseTitle]) )
        self.ui.createKeyButton.setEnabled( offline )
        self.ui.createKeyButton1.setEnabled( offline )
        self.ui.goOnlineButton.setEnabled( offline )
        self.ui.connectCancelButton.setEnabled( connecting or disconnecting )
        if online :
            self.ui.stack.setCurrentWidget( self.ui.contactsPage )
        elif offline :
            if self.reconnector :
                assert self.reconnector.reconnecting
                self.ui.connectCancelButton.setEnabled( True )
                self.ui.stack.setCurrentWidget( self.ui.connectingPage )
                self.ui.connectStatus.setText('<b>%s<br/>Reconnecting in %d second(s)...</b>'
                        % (self.reconnector.errorMsg, self.reconnector.timeLeft) )
            elif listProfiles() :
                self.ui.stack.setCurrentWidget( self.ui.offlinePage )
            else :
                self.ui.stack.setCurrentWidget( self.ui.offlineNoUsersPage )
        elif connecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Connecting...</b>' )
        elif disconnecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Disconnecting...</b>' )

    def _onStateChange( self ) :
        if self.sm.previous() == self.ONLINE :
            self.statusChecker.shutdown()
            self.appletServer.clearConnections()
            for dlg in self.contactInfoDialogs.values() :
                dlg.close()
            assert not self.contactInfoDialogs
            if self.editPermissionsDialog :
                self.editPermissionsDialog.close()
            assert not self.editPermissionsDialog
            self.ui.contacts.clear()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_offline.png') )
                self.trayIcon.setToolTip( 'CSpace' )
        if self.sm.current() == self.OFFLINE :
            self.profile = None
            if self.reconnector :
                if self.sm.previous() == self.CONNECTING :
                    msg = 'Connect failed.'
                else :
                    msg = 'Disconnected.'
                self.reconnector.startReconnectTimer( msg )
            if self.exitAfterOffline :
                self.reactor.callLater( 0, self.ui.actionExit.trigger )
        elif self.sm.current() == self.ONLINE :
            self.ui.contacts.clear()
            for contactName in self.profile.getContactNames() :
                contact = self.profile.getContactByName( contactName )
                self._addContactItem( contact )
            self.statusChecker.initialize()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_online.png') )
                self.trayIcon.setToolTip( 'CSpace - %s' % self.profile.name )
        self._updateUI()

    def _doCheckSettings( self ) :
        st = localSettings()
        windowWidth = st.getInt( 'Settings/WindowWidth', 0 )
        windowHeight = st.getInt( 'Settings/WindowHeight', 0 )
        if (windowWidth > 0) and (windowHeight > 0) :
            windowWidth = max( windowWidth, 100 )
            windowHeight = max( windowHeight, 100 )
            self.resize( QSize(windowWidth,windowHeight).expandedTo(self.minimumSizeHint()) )
        profiles = listProfiles()
        entries = [entry for userName,keyId,entry in profiles]
        if st.getInt('Settings/RememberKey',0) :
            entry = st.getString('Settings/SavedProfile')
            password = st.getString('Settings/SavedPassword')
            if entry and password and (entry in entries) :
                profile = loadProfile( entry, password )
                if profile :
                    self._doGoOnline( profile )

    def _sortContacts( self ) :
        self.ui.contacts.sortItems()

    def _addContactItem( self, contact ) :
        contactItem = QListWidgetItem( QIcon(':/images/user_offline.png'),
                contact.name, self.ui.contacts )
        self._sortContacts()
        self.statusChecker.check( contactItem )

    def _cancelReconnect( self ) :
        if self.reconnector :
            self.reconnector.shutdown()
            self.reconnector = None
            self._updateUI()

    @pyqtSignature( '' )
    def on_connectCancelButton_clicked( self ) :
        self._cancelReconnect()
        self.exitAfterOffline = False
        self.session.shutdown()

    @pyqtSignature( '' )
    def on_actionCreateKey_triggered( self ) :
        self._cancelReconnect()
        dlg = CreateKeyDialog( self, self.reactor )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        userName = dlg.userName
        password = dlg.password
        rsaKey = dlg.rsaKey
        keyId = dlg.keyId
        profile = createProfile( rsaKey, password, userName, keyId )
        dlg = CreateKeyDoneDialog( self, keyId )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        st = localSettings()
        if dlg.rememberKey :
            st.setInt( 'Settings/RememberKey', 1 )
            st.setString( 'Settings/SavedProfile', profile.storeEntry )
            st.setString( 'Settings/SavedPassword', password )
        else :
            st.setInt( 'Settings/RememberKey', 0 )
            st.remove( 'Settings/SavedProfile' )
            st.remove( 'Settings/SavedPassword' )
        self._updateUI()
        self._doGoOnline( profile )

    @pyqtSignature( '' )
    def on_actionGoOnline_triggered( self ) :
        self._cancelReconnect()
        dlg = GoOnlineDialog( self )
        if not dlg.profiles :
            QMessageBox.warning( self, 'No Private Keys',
                    'No Private Keys have been created. Please create your Private Key first.' )
            self.ui.actionCreateKey.trigger()
            return
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        self._doGoOnline( dlg.profile )

    def _doGoOnline( self, profile ) :
        self.profile = profile
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    def _onReconnectTimer( self ) :
        assert self.reconnector
        if self.reconnector.timeLeft > 0 :
            self._updateUI()
            return
        self.profile = self.reconnector.profile
        self._cancelReconnect()
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    @pyqtSignature( '' )
    def on_actionGoOffline_triggered( self ) :
        self._cancelReconnect()
        self.session.goOffline()

    @pyqtSignature( '' )
    def on_actionKeyInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.keyInfoDialog :
            self.keyInfoDialog.show()
            self.keyInfoDialog.activateWindow()
            return
        def onDialogClose() :
            self.keyInfoDialog = None
            self.sm.removeCallback( callbackId )
        def onStateChange() :
            self.keyInfoDialog.close()
            assert not self.keyInfoDialog
        dlg = KeyInfoDialog( self, self.profile, onDialogClose )
        dlg.show()
        self.keyInfoDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionExit_triggered( self ) :
        self._cancelReconnect()
        if self.sm.current() == self.ONLINE :
            self.exitAfterOffline = True
            self.session.goOffline()
        else :
            self.exitAfterOffline = False
            self.appletServer.shutdown()
            self.session.shutdown()
            if self.trayIcon is not None :
                self.trayIcon.hide()
            st = localSettings()
            st.setInt( 'Settings/WindowWidth', self.width() )
            st.setInt( 'Settings/WindowHeight', self.height() )
            qApp.exit( 0 )

    @pyqtSignature( '' )
    def on_actionAddContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.addContactDialog :
            self.addContactDialog.show()
            self.addContactDialog.activateWindow()
            return
        def onDialogClose() :
            self.addContactDialog = None
            self.sm.removeCallback( callbackId )
        def onAddContact( contact ) :
            self.profile.addContact( contact )
            self._addContactItem( contact )
            saveProfileContacts( self.profile )
        def onStateChange() :
            self.addContactDialog.close()
            assert not self.addContactDialog
        dlg = AddContactDialog( self, self.reactor, self.profile,
                onAddContact, onDialogClose )
        dlg.show()
        self.addContactDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionRefreshStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        for i in range(self.ui.contacts.count()) :
            item = self.ui.contacts.item( i )
            self.statusChecker.check( item )

    def _onContactsContextMenuRequested( self, pos ) :
        item = self.ui.contacts.itemAt( pos )
        if item is None : return
        curItem = self.ui.contacts.currentItem()
        if item is not curItem : return
        self.contextMenu.popup( self.ui.contacts.mapToGlobal(pos) )

    def _onContactDoubleClicked( self, item ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execDefaultAction( str(item.text()) )

    def _onContextMenuAction( self, action ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execAction( action, str(item.text()) )

    @pyqtSignature( '' )
    def on_actionCheckStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.statusChecker.check( item )

    @pyqtSignature( '' )
    def on_actionRemoveContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        contact = self.profile.getContactByName( str(item.text()) )
        self.ev.trigger( 'contact.remove', item, contact )
        self.ui.contacts.takeItem( self.ui.contacts.row(item) )
        self.profile.removeContact( contact )
        saveProfileContacts( self.profile )
        permissions = self.session.getPermissions()
        permissions.removeContact( contact.name )
        if permissions.isModified() :
            permissions.savePermissions()

    @pyqtSignature( '' )
    def on_actionContactInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        dlg = self.contactInfoDialogs.get( item )
        if dlg is None :
            contact = self.profile.getContactByName( str(item.text()) )
            # FIXME: why is obj needed?
            obj = Dummy()
            def onUpdateName( newName ) :
                oldName = contact.name
                self.profile.changeContactName( oldName, newName )
                saveProfileContacts( self.profile )
                item.setText( newName )
                self._sortContacts()
                permissions = self.session.getPermissions()
                permissions.changeContactName( oldName, newName )
                if permissions.isModified() :
                    permissions.savePermissions()
            def onClose() :
                if obj.callbackId is not None :
                    self.ev.remove( obj.callbackId )
                del self.contactInfoDialogs[item]
            def onContactRemoved( removedItem, contact ) :
                if removedItem is item :
                    dlg.close()
            dlg = ContactInfoDialog( self, contact, self.profile,
                    onUpdateName, onClose )
            self.contactInfoDialogs[item] = dlg
            obj.callbackId = self.ev.register( 'contact.remove',
                    onContactRemoved )
        dlg.show()
        dlg.activateWindow()

    @pyqtSignature( '' )
    def on_actionEditPermissions_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.editPermissionsDialog :
            self.editPermissionsDialog.show()
            self.editPermissionsDialog.activateWindow()
            return
        def onClose() :
            self.editPermissionsDialog = None
        dlg = PermissionsDialog( self, self.session.getPermissions(),
                onClose )
        dlg.show()
        self.editPermissionsDialog = dlg

    @pyqtSignature( '' )
    def on_actionAboutCSpace_triggered( self ) :
        buildNumber = currentBuildNumber()
        listenPortMsg = ''
        if self.sm.current() == self.ONLINE :
            listenPortMsg = 'Applet Server Port: %d\n' % \
                    self.appletServer.getListenPort()
        QMessageBox.information( self, 'About CSpace',
                'CSpace Peer-to-Peer Communication Platform\n'+
                'Build %d\n' % buildNumber +
                listenPortMsg +
                '(c) Tachyon Technologies 2006',
                QMessageBox.Ok )

    def _onTrayClicked( self, pos, button ) :
        if (self.isMinimized()) or (not self.isVisible()) :
            self.showNormal()
        self.activateWindow()

    def _onUpdateDownloaded( self, updateFileName, updateFileData ) :
        def onInstall() :
            installerFile = self.autoUpdater.saveInstaller(
                    updateFileName, updateFileData )
            global execFileAfterExit
            execFileAfterExit = installerFile
            self.ui.actionExit.trigger()
            self.notifyWindow.close()
        self.notifyWindow = UpdateNotifyWindow( self.reactor,
                onInstall )

    def closeEvent( self, ev ) :
        self.showMinimized()
        if self.trayIcon is not None :
            self.hide()
        ev.ignore()
Пример #4
0
class MainWindow( QMainWindow ) :
    OFFLINE = UserSession.OFFLINE
    CONNECTING = UserSession.CONNECTING
    ONLINE = UserSession.ONLINE
    DISCONNECTING = UserSession.DISCONNECTING

    def __init__( self, seedNodes, reactor ) :
        QMainWindow.__init__( self )
        self.ui = Ui_MainWindow()
        self.ui.setupUi( self )
        self._setupIcons()
        self.seedNodes = seedNodes
        self.reactor = reactor
        self.session = UserSession( seedNodes, reactor )
        self.sm = self.session.sm
        self.ev = Eventer()
        self.profile = None
        self.baseTitle = str(self.windowTitle())
        self.exitAfterOffline = False
        self.reconnector = None
        self.keyInfoDialog = None
        self.addContactDialog = None
        self.sm.insertCallback( self._onStateChange )
        self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
        self.ui.contacts.setWrapping( True )
        self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
                self._onContactsContextMenuRequested )
        self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
                self._onContactDoubleClicked )
        self.contextMenu = QMenu()
        self.contextMenu.addAction( self.ui.actionCheckStatus )
        self.contextMenu.addAction( self.ui.actionRemoveContact )
        self.contextMenu.addAction( self.ui.actionContactInfo )
        self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
        self.actionManager = ActionManager( self.contextMenu )
        self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
        self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
        self.contactInfoDialogs = {}
        self.trayMenu = QMenu( self )
        self.trayMenu.addAction( self.ui.actionExit )
        if HAS_TRAY :
            self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
            self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
            self.trayIcon.show()
        else :
            self.trayIcon = None
        self.editPermissionsDialog = None
        self.autoUpdater = AutoUpdater( self.reactor )
        self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
        self._updateUI()
        self._doCheckSettings()

    def _setupIcons( self ) :
        def makeIcon( name ) :
            icon = QIcon()
            for res in '16 22 32'.split() :
                icon.addFile( ':/images/%s%s.png' % (name,res) )
            return icon
        actionIcons = (
                'CreateKey register',
                'GoOnline connect',
                'GoOffline disconnect',
                'Exit exit',
                'AddContact user_add',
                'RefreshStatus refresh',
                'CheckStatus refresh',
                'ContactInfo contact_info',
                'RemoveContact user_remove',
                'EditPermissions edit_permissions',
                'KeyInfo key_info' )
        for s in actionIcons :
            name,icon = s.split()
            action = getattr( self.ui, 'action%s'%name )
            action.setIcon( makeIcon(icon) )

    def _updateUI( self ) :
        state = self.sm.current()
        online = (state == self.ONLINE)
        offline = (state == self.OFFLINE)
        connecting = (state == self.CONNECTING)
        disconnecting = (state == self.DISCONNECTING)
        self.ui.actionCreateKey.setEnabled( offline )
        self.ui.actionGoOnline.setEnabled( offline )
        self.ui.actionGoOffline.setEnabled( online )
        self.ui.actionKeyInfo.setEnabled( online )
        self.ui.actionExit.setEnabled( True )
        self.ui.actionAddContact.setEnabled( online )
        self.ui.actionRefreshStatus.setEnabled( online )
        self.ui.actionCheckStatus.setEnabled( online )
        self.ui.actionContactInfo.setEnabled( online )
        self.ui.actionRemoveContact.setEnabled( online )
        self.ui.actionEditPermissions.setEnabled( online )
        self.ui.actionAboutCSpace.setEnabled( True )
        if online : msg = 'Online'
        if offline : msg = 'Offline'
        if connecting : msg = 'Going Online...'
        if disconnecting : msg = 'Going Offline...'
        if not offline :
            msg = '%s - %s' % (self.profile.name,msg)
        self.setWindowTitle( ' - '.join([msg,self.baseTitle]) )
        self.ui.createKeyButton.setEnabled( offline )
        self.ui.createKeyButton1.setEnabled( offline )
        self.ui.goOnlineButton.setEnabled( offline )
        self.ui.connectCancelButton.setEnabled( connecting or disconnecting )
        if online :
            self.ui.stack.setCurrentWidget( self.ui.contactsPage )
        elif offline :
            if self.reconnector :
                assert self.reconnector.reconnecting
                self.ui.connectCancelButton.setEnabled( True )
                self.ui.stack.setCurrentWidget( self.ui.connectingPage )
                self.ui.connectStatus.setText('<b>%s<br/>Reconnecting in %d second(s)...</b>'
                        % (self.reconnector.errorMsg, self.reconnector.timeLeft) )
            elif listProfiles() :
                self.ui.stack.setCurrentWidget( self.ui.offlinePage )
            else :
                self.ui.stack.setCurrentWidget( self.ui.offlineNoUsersPage )
        elif connecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Connecting...</b>' )
        elif disconnecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Disconnecting...</b>' )

    def _onStateChange( self ) :
        if self.sm.previous() == self.ONLINE :
            self.statusChecker.shutdown()
            self.appletServer.clearConnections()
            for dlg in self.contactInfoDialogs.values() :
                dlg.close()
            assert not self.contactInfoDialogs
            if self.editPermissionsDialog :
                self.editPermissionsDialog.close()
            assert not self.editPermissionsDialog
            self.ui.contacts.clear()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_offline.png') )
                self.trayIcon.setToolTip( 'CSpace' )
        if self.sm.current() == self.OFFLINE :
            self.profile = None
            if self.reconnector :
                if self.sm.previous() == self.CONNECTING :
                    msg = 'Connect failed.'
                else :
                    msg = 'Disconnected.'
                self.reconnector.startReconnectTimer( msg )
            if self.exitAfterOffline :
                self.reactor.callLater( 0, self.ui.actionExit.trigger )
        elif self.sm.current() == self.ONLINE :
            self.ui.contacts.clear()
            for contactName in self.profile.getContactNames() :
                contact = self.profile.getContactByName( contactName )
                self._addContactItem( contact )
            self.statusChecker.initialize()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_online.png') )
                self.trayIcon.setToolTip( 'CSpace - %s' % self.profile.name )
        self._updateUI()

    def _doCheckSettings( self ) :
        st = localSettings()
        windowWidth = st.getInt( 'Settings/WindowWidth', 0 )
        windowHeight = st.getInt( 'Settings/WindowHeight', 0 )
        if (windowWidth > 0) and (windowHeight > 0) :
            windowWidth = max( windowWidth, 100 )
            windowHeight = max( windowHeight, 100 )
            self.resize( QSize(windowWidth,windowHeight).expandedTo(self.minimumSizeHint()) )
        profiles = listProfiles()
        entries = [entry for userName,keyId,entry in profiles]
        if st.getInt('Settings/RememberKey',0) :
            entry = st.getString('Settings/SavedProfile')
            password = st.getString('Settings/SavedPassword')
            if entry and password and (entry in entries) :
                profile = loadProfile( entry, password )
                if profile :
                    self._doGoOnline( profile )

    def _sortContacts( self ) :
        self.ui.contacts.sortItems()

    def _addContactItem( self, contact ) :
        contactItem = QListWidgetItem( QIcon(':/images/user_offline.png'),
                contact.name, self.ui.contacts )
        self._sortContacts()
        self.statusChecker.check( contactItem )

    def _cancelReconnect( self ) :
        if self.reconnector :
            self.reconnector.shutdown()
            self.reconnector = None
            self._updateUI()

    @pyqtSignature( '' )
    def on_connectCancelButton_clicked( self ) :
        self._cancelReconnect()
        self.exitAfterOffline = False
        self.session.shutdown()

    @pyqtSignature( '' )
    def on_actionCreateKey_triggered( self ) :
        self._cancelReconnect()
        dlg = CreateKeyDialog( self, self.reactor )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        userName = dlg.userName
        password = dlg.password
        rsaKey = dlg.rsaKey
        keyId = dlg.keyId
        profile = createProfile( rsaKey, password, userName, keyId )
        dlg = CreateKeyDoneDialog( self, keyId )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        st = localSettings()
        if dlg.rememberKey :
            st.setInt( 'Settings/RememberKey', 1 )
            st.setString( 'Settings/SavedProfile', profile.storeEntry )
            st.setString( 'Settings/SavedPassword', password )
        else :
            st.setInt( 'Settings/RememberKey', 0 )
            st.remove( 'Settings/SavedProfile' )
            st.remove( 'Settings/SavedPassword' )
        self._updateUI()
        self._doGoOnline( profile )

    @pyqtSignature( '' )
    def on_actionGoOnline_triggered( self ) :
        self._cancelReconnect()
        dlg = GoOnlineDialog( self )
        if not dlg.profiles :
            QMessageBox.warning( self, 'No Private Keys',
                    'No Private Keys have been created. Please create your Private Key first.' )
            self.ui.actionCreateKey.trigger()
            return
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        self._doGoOnline( dlg.profile )

    def _doGoOnline( self, profile ) :
        self.profile = profile
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    def _onReconnectTimer( self ) :
        assert self.reconnector
        if self.reconnector.timeLeft > 0 :
            self._updateUI()
            return
        self.profile = self.reconnector.profile
        self._cancelReconnect()
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    @pyqtSignature( '' )
    def on_actionGoOffline_triggered( self ) :
        self._cancelReconnect()
        self.session.goOffline()

    @pyqtSignature( '' )
    def on_actionKeyInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.keyInfoDialog :
            self.keyInfoDialog.show()
            self.keyInfoDialog.activateWindow()
            return
        def onDialogClose() :
            self.keyInfoDialog = None
            self.sm.removeCallback( callbackId )
        def onStateChange() :
            self.keyInfoDialog.close()
            assert not self.keyInfoDialog
        dlg = KeyInfoDialog( self, self.profile, onDialogClose )
        dlg.show()
        self.keyInfoDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionExit_triggered( self ) :
        self._cancelReconnect()
        if self.sm.current() == self.ONLINE :
            self.exitAfterOffline = True
            self.session.goOffline()
        else :
            self.exitAfterOffline = False
            self.appletServer.shutdown()
            self.session.shutdown()
            if self.trayIcon is not None :
                self.trayIcon.hide()
            st = localSettings()
            st.setInt( 'Settings/WindowWidth', self.width() )
            st.setInt( 'Settings/WindowHeight', self.height() )
            qApp.exit( 0 )

    @pyqtSignature( '' )
    def on_actionAddContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.addContactDialog :
            self.addContactDialog.show()
            self.addContactDialog.activateWindow()
            return
        def onDialogClose() :
            self.addContactDialog = None
            self.sm.removeCallback( callbackId )
        def onAddContact( contact ) :
            self.profile.addContact( contact )
            self._addContactItem( contact )
            saveProfileContacts( self.profile )
        def onStateChange() :
            self.addContactDialog.close()
            assert not self.addContactDialog
        dlg = AddContactDialog( self, self.reactor, self.profile,
                onAddContact, onDialogClose )
        dlg.show()
        self.addContactDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionRefreshStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        for i in range(self.ui.contacts.count()) :
            item = self.ui.contacts.item( i )
            self.statusChecker.check( item )

    def _onContactsContextMenuRequested( self, pos ) :
        item = self.ui.contacts.itemAt( pos )
        if item is None : return
        curItem = self.ui.contacts.currentItem()
        if item is not curItem : return
        self.contextMenu.popup( self.ui.contacts.mapToGlobal(pos) )

    def _onContactDoubleClicked( self, item ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execDefaultAction( str(item.text()) )

    def _onContextMenuAction( self, action ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execAction( action, str(item.text()) )

    @pyqtSignature( '' )
    def on_actionCheckStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.statusChecker.check( item )

    @pyqtSignature( '' )
    def on_actionRemoveContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        contact = self.profile.getContactByName( str(item.text()) )
        self.ev.trigger( 'contact.remove', item, contact )
        self.ui.contacts.takeItem( self.ui.contacts.row(item) )
        self.profile.removeContact( contact )
        saveProfileContacts( self.profile )
        permissions = self.session.getPermissions()
        permissions.removeContact( contact.name )
        if permissions.isModified() :
            permissions.savePermissions()

    @pyqtSignature( '' )
    def on_actionContactInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        dlg = self.contactInfoDialogs.get( item )
        if dlg is None :
            contact = self.profile.getContactByName( str(item.text()) )
            # FIXME: why is obj needed?
            obj = Dummy()
            def onUpdateName( newName ) :
                oldName = contact.name
                self.profile.changeContactName( oldName, newName )
                saveProfileContacts( self.profile )
                item.setText( newName )
                self._sortContacts()
                permissions = self.session.getPermissions()
                permissions.changeContactName( oldName, newName )
                if permissions.isModified() :
                    permissions.savePermissions()
            def onClose() :
                if obj.callbackId is not None :
                    self.ev.remove( obj.callbackId )
                del self.contactInfoDialogs[item]
            def onContactRemoved( removedItem, contact ) :
                if removedItem is item :
                    dlg.close()
            dlg = ContactInfoDialog( self, contact, self.profile,
                    onUpdateName, onClose )
            self.contactInfoDialogs[item] = dlg
            obj.callbackId = self.ev.register( 'contact.remove',
                    onContactRemoved )
        dlg.show()
        dlg.activateWindow()

    @pyqtSignature( '' )
    def on_actionEditPermissions_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.editPermissionsDialog :
            self.editPermissionsDialog.show()
            self.editPermissionsDialog.activateWindow()
            return
        def onClose() :
            self.editPermissionsDialog = None
        dlg = PermissionsDialog( self, self.session.getPermissions(),
                onClose )
        dlg.show()
        self.editPermissionsDialog = dlg

    @pyqtSignature( '' )
    def on_actionAboutCSpace_triggered( self ) :
        buildNumber = currentBuildNumber()
        listenPortMsg = ''
        if self.sm.current() == self.ONLINE :
            listenPortMsg = 'Applet Server Port: %d\n' % \
                    self.appletServer.getListenPort()
        QMessageBox.information( self, 'About CSpace',
                'CSpace Peer-to-Peer Communication Platform\n'+
                'Build %d\n' % buildNumber +
                listenPortMsg +
                '(c) Tachyon Technologies 2006',
                QMessageBox.Ok )

    def _onTrayClicked( self, pos, button ) :
        if (self.isMinimized()) or (not self.isVisible()) :
            self.showNormal()
        self.activateWindow()

    def _onUpdateDownloaded( self, updateFileName, updateFileData ) :
        def onInstall() :
            installerFile = self.autoUpdater.saveInstaller(
                    updateFileName, updateFileData )
            global execFileAfterExit
            execFileAfterExit = installerFile
            self.ui.actionExit.trigger()
            self.notifyWindow.close()
        self.notifyWindow = UpdateNotifyWindow( self.reactor,
                onInstall )

    def closeEvent( self, ev ) :
        self.showMinimized()
        if self.trayIcon is not None :
            self.hide()
        ev.ignore()