Exemplo n.º 1
0
class Tray():

    def __init__(self, parent):
        
        icon = AppIcon.getAppIcon()
        if icon:
            self.tray = QSystemTrayIcon(icon)
        else:
            self.tray = QSystemTrayIcon()
        self.parent = parent
        
        self.tray.setToolTip("Droid Navi")
        
        # Menu
        self.menu = QMenu()
        self.menu.addAction("Show Connected List", partial(self.maximize))
        self.menu.addAction("Options", partial(self.parent.openSettings))
        self.menu.addAction("Exit", partial(self.parent.close))
        self.tray.setContextMenu(self.menu)
        
        # Connect handlers
        self.tray.activated.connect(self.activated)
    
    def getTray(self):
        return self.tray
     
    def display(self, show):
        ''' Toggle showing the tray '''
        
        if show:
            self.tray.show()
        else:
            self.tray.hide()
    
    @pyqtSlot()
    def maximize(self):
        ''' Show the main window and hide tray icon '''
        
        self.display(False)
        self.parent.maximizeFromTray()
    
    @pyqtSlot()
    def activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.maximize()
Exemplo n.º 2
0
class Tray():
    def __init__(self, parent):

        icon = AppIcon.getAppIcon()
        if icon:
            self.tray = QSystemTrayIcon(icon)
        else:
            self.tray = QSystemTrayIcon()
        self.parent = parent

        self.tray.setToolTip("Droid Navi")

        # Menu
        self.menu = QMenu()
        self.menu.addAction("Show Connected List", partial(self.maximize))
        self.menu.addAction("Options", partial(self.parent.openSettings))
        self.menu.addAction("Exit", partial(self.parent.close))
        self.tray.setContextMenu(self.menu)

        # Connect handlers
        self.tray.activated.connect(self.activated)

    def getTray(self):
        return self.tray

    def display(self, show):
        ''' Toggle showing the tray '''

        if show:
            self.tray.show()
        else:
            self.tray.hide()

    @pyqtSlot()
    def maximize(self):
        ''' Show the main window and hide tray icon '''

        self.display(False)
        self.parent.maximizeFromTray()

    @pyqtSlot()
    def activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.maximize()
Exemplo n.º 3
0
class sysBaloon(QMainWindow):
    def baloon(self, t, m, tm=50000):
        self.trayicon = QSystemTrayIcon(self)
        if self.trayicon.supportsMessages():
            icona = QIcon('py.ico')
            self.trayicon.setIcon(icona)
            self.trayicon.show()
            self.trayicon.showMessage(t, m, msecs=tm)
            time.sleep(10)
            self.trayicon.hide()
        else:
            print "This Function isn't supported."
            choose = raw_input("Would you enable it? Y/N \n --> ")
            if choose == "Y":
                shell = os.popen('enable_baloon.reg')
                print "Run again this program"
            elif choose == "N":
                print "You don't use this program without baloon enabled."
            else:
                print "You have insert wrong char."
Exemplo n.º 4
0
class MainWindow(QMainWindow):
    groups = dict()
    typeQListWidgetHeader = 1000
    showHostsInGroups = False
    currentGroupName = None  # used to simple detect currently selected group to show menu

    def __init__(self):
        super(MainWindow, self).__init__()
        self.config = Config()
        self.db = Database(self.config.getConnectionString())

        cryptoKey = self.getCryptoKey()
        self.hosts = Hosts(self.db, cryptoKey)

        # menu used for each host
        self.hostMenu = QMenu()
        self.editAction = QAction(QIcon(':/ico/edit.svg'), "Edit",
                                  self.hostMenu)
        self.editAction.triggered.connect(self.editHost)
        self.hostMenu.addAction(self.editAction)

        # menu used for headers of groups
        self.groupsHeaderMenu = QMenu()
        self.editGroupAction = QAction(QIcon(':/ico/edit.svg'), "Edit group",
                                       self.groupsHeaderMenu)
        self.editGroupAction.triggered.connect(self.editGroup)
        self.deleteGroupAction = QAction(QIcon(':/ico/remove.svg'),
                                         "Delete group", self.groupsHeaderMenu)
        self.deleteGroupAction.triggered.connect(self.deleteGroup)
        self.groupsHeaderMenu.addAction(self.editGroupAction)
        self.groupsHeaderMenu.addAction(self.deleteGroupAction)

        self.duplicateAction = QAction(QIcon(':/ico/copy.svg'), "Duplicate",
                                       self.hostMenu)
        self.duplicateAction.triggered.connect(self.duplicateHost)
        self.hostMenu.addAction(self.duplicateAction)

        # todo: confirm for delete action
        self.deleteAction = QAction(QIcon(':/ico/remove.svg'), "Delete",
                                    self.hostMenu)
        self.deleteAction.triggered.connect(self.deleteHost)
        self.hostMenu.addAction(self.deleteAction)

        self.connectFramelessMenu = actions.generateScreenChoseMenu(
            self.hostMenu, self.connectFrameless, ':/ico/frameless.svg',
            "Connect frameless")
        self.hostMenu.addMenu(self.connectFramelessMenu)

        self.assignGroupAction = QAction("Assign group", self.hostMenu)
        self.assignGroupAction.triggered.connect(self.assignGroup)
        self.hostMenu.addAction(self.assignGroupAction)

        # setup main window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # when top level changed, we changing dock title bar
        self.dockWidgetTileBar = DockWidgetTitleBar()
        self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)
        self.ui.hostsDock.topLevelChanged.connect(self.dockLevelChanged)

        # set global menu
        self.globalMenu = QMenu()
        self.globalMenu.addAction(QIcon(':/ico/add.svg'), 'Add host',
                                  self.addHost)

        # groups menu
        self.groupsMenu = QMenu("Groups")
        self.groupsMenu.aboutToShow.connect(self.setGroupsMenu)
        self.globalMenu.addMenu(self.groupsMenu)

        # disable menu indicator
        self.ui.menu.setStyleSheet(
            "QPushButton::menu-indicator {image: none;}")
        self.positionMenu = QMenu("Dock position")
        self.positionMenu.addAction(
            "Left", lambda: self.setDockPosition(Qt.LeftDockWidgetArea))
        self.positionMenu.addAction(
            "Right", lambda: self.setDockPosition(Qt.RightDockWidgetArea))
        self.positionMenu.addAction("Float", self.setDockFloat)
        self.globalMenu.addMenu(self.positionMenu)
        self.globalMenu.addAction('Change tray icon visibility',
                                  self.changeTrayIconVisibility)
        self.globalMenu.addAction('Settings', self.showSettings)
        self.globalMenu.addAction('Quit', self.close)
        self.ui.menu.setMenu(self.globalMenu)

        # set events on hosts list
        self.ui.hostsList.itemDoubleClicked.connect(self.slotConnectHost)
        self.ui.hostsList.itemClicked.connect(self.slotShowHost)
        self.ui.hostsList.customContextMenuRequested.connect(
            self.slotShowHostContextMenu)

        # set tab widget
        self.tabWidget = MyTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.tabWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tabWidget.customContextMenuRequested.connect(
            self.showCentralWidgetContextMenu)

        # set tray icon
        self.tray = QSystemTrayIcon(QIcon(":/ico/myrdp.svg"))
        self.tray.activated.connect(self.trayActivated)

        self.trayMenu = QMenu()
        self.trayMenu.addAction("Hide tray icon",
                                self.changeTrayIconVisibility)
        self.connectHostMenuTray = ConnectHostMenu(self.hosts)
        self.connectHostMenuTray.triggered.connect(
            self.connectHostFromTrayMenu)
        self.trayMenu.addMenu(self.connectHostMenuTray)
        self.trayMenu.addAction("Quit", self.close)

        self.tray.setContextMenu(self.trayMenu)
        self.restoreSettings()
        # host list
        self.ui.filter.textChanged.connect(self.setHostList)
        self.setHostList()

    def getCryptoKey(self, passphrase=None):
        try:
            return self.config.getPrivateKey(passphrase)
        except ValueError:
            passwordDialog = PasswordDialog()
            retCode = passwordDialog.exec_()
            if retCode == QtGui.QDialog.Accepted:
                return self.getCryptoKey(passwordDialog.getPassword())
            else:
                raise SystemError("Password required")

    def showSettings(self):
        settingsWidget = self.findChild(QWidget, "settings")
        if settingsWidget is None:
            self.settingsWidget = SettingsPage()
            self.settingsWidget.setObjectName("settings")
            self.tabWidget.insertTab(0, self.settingsWidget,
                                     QIcon(":/ico/settings.svg"), 'Settings')

        index = self.tabWidget.indexOf(self.settingsWidget)
        self.tabWidget.setCurrentIndex(index)

    def connectHostFromMenu(self, action):
        self.connectHost(unicode(action.text()))

    def connectHostFromTrayMenu(self, action):
        tabPage = self.connectHost(unicode(action.text()))
        if not self.isVisible():
            self.tabWidget.setDetached(True, tabPage)

    def trayActivated(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.activateWindow()

    def changeTrayIconVisibility(self):
        if self.tray.isVisible():
            self.tray.hide()
            if not self.isVisible():
                self.show()
        else:
            self.tray.show()

    def refreshGroups(self):
        groupList = self.hosts.getGroupsList()
        for group in groupList:
            if group not in self.groups:
                # add new groups as visible
                self.groups[group] = True

        # remove not existing groups
        keysToDelete = set(self.groups.keys()) - set(groupList)
        for key in keysToDelete:
            self.groups.pop(key)

    def assignGroup(self):
        groups = self.hosts.getGroupsList()
        assignGroupDialog = AssignGroupDialog(groups)
        groupToAssign = assignGroupDialog.assign()
        if groupToAssign is not False:  # None could be used to unassign the group
            groupToAssign = None if groupToAssign.isEmpty() else unicode(
                groupToAssign)
            for hostName in self.getSelectedHosts():
                self.hosts.assignGroup(hostName, groupToAssign)
            self.db.tryCommit()
            self.setHostList()

    def setGroupsMenu(self):
        self.groupsMenu.clear()
        addGroupAction = self.groupsMenu.addAction('Add group')
        addGroupAction.triggered.connect(self.addGroup)

        deleteGroupAction = self.groupsMenu.addAction('Delete group')
        deleteGroupAction.triggered.connect(self.showDeleteGroupDialog)

        showHostsInGroupsAction = self.groupsMenu.addAction(
            'Show host list in groups')
        showHostsInGroupsAction.triggered.connect(self.changeHostListView)
        showHostsInGroupsAction.setCheckable(True)
        showHostsInGroupsAction.setChecked(self.showHostsInGroups)

        self.groupsMenu.addSeparator()
        for group, checked in self.groups.items():
            action = QAction(group, self.groupsMenu)
            action.setCheckable(True)
            action.setChecked(checked)
            action.triggered.connect(self.groupsVisibilityChanged)
            self.groupsMenu.addAction(action)

    def addGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.add()
        self._processHostSubmit(resp)

    def groupsVisibilityChanged(self, checked):
        currentGroup = unicode(self.sender().text())
        self.groups[currentGroup] = checked
        self.setHostList()

    def setDockPosition(self, dockWidgetArea):
        if self.ui.hostsDock.isFloating():
            self.ui.hostsDock.setFloating(False)
        self.addDockWidget(dockWidgetArea, self.ui.hostsDock)

    def setDockFloat(self):
        if self.ui.hostsDock.isFloating():
            return
        # default title bar must be set before is float because sometimes window make strange crash
        self.ui.hostsDock.setTitleBarWidget(None)
        self.ui.hostsDock.setFloating(True)

    def dockLevelChanged(self, isFloating):
        if isFloating:
            # changing title bar widget if is not none, probably true will be only once on start with saved float state
            if self.ui.hostsDock.titleBarWidget():
                self.ui.hostsDock.setTitleBarWidget(None)
        else:
            self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)

    def showFramelessWidget(self):
        self.t.show()
        self.t.setGeometry(self.frameGeometry())

    def getCurrentHostListItemName(self):
        return self.ui.hostsList.currentItem().text()

    def getSelectedHosts(self):
        return [host.text() for host in self.ui.hostsList.selectedItems()]

    def findHostItemByName(self, name):
        result = self.ui.hostsList.findItems(name, Qt.MatchExactly)
        resultLen = len(result)
        if resultLen != 1:  # should be only one host
            logger.error("Host not found. Got %d results" % resultLen)
        return result[0]

    def showCentralWidgetContextMenu(self, pos):
        menu = QMenu()
        title = self.ui.hostsDock.windowTitle()

        hostsDockAction = menu.addAction(title)
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.ui.hostsDock.isVisible())
        hostsDockAction.triggered.connect(self.changeHostsDockWidgetVisibility)

        hostsDockAction = menu.addAction("Tray icon")
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.tray.isVisible())
        hostsDockAction.triggered.connect(self.changeTrayIconVisibility)

        connectHostMenuTray = ConnectHostMenu(self.hosts, "Connect")
        connectHostMenuTray.triggered.connect(self.connectHostFromMenu)
        menu.addMenu(connectHostMenuTray)

        menu.exec_(self.tabWidget.mapToGlobal(pos))

    def changeHostListView(self, checked):
        self.showHostsInGroups = checked
        self.setHostList()

    def changeHostsDockWidgetVisibility(self):
        isVisible = self.ui.hostsDock.isVisible()
        self.ui.hostsDock.setVisible(not isVisible)

    def isHostListHeader(self, item):
        if not item or item.type() == self.typeQListWidgetHeader:
            return True
        return False

    def slotShowHostContextMenu(self, pos):
        def changeMenusVisibility(isEnabled):
            self.connectFramelessMenu.setEnabled(isEnabled)
            self.editAction.setEnabled(isEnabled)
            self.duplicateAction.setEnabled(isEnabled)

        # ignore context menu for group headers
        item = self.ui.hostsList.itemAt(pos)

        if self.isHostListHeader(item):
            item = self.ui.hostsList.itemAt(pos)
            widgetItem = self.ui.hostsList.itemWidget(item)
            if widgetItem:
                self.currentGroupName = widgetItem.text()  # yea I'm so dirty
                if self.currentGroupName != unassignedGroupName:
                    self.groupsHeaderMenu.exec_(
                        self.ui.hostsList.mapToGlobal(pos))
            return

        if len(self.ui.hostsList.selectedItems()) == 1:  # single menu
            changeMenusVisibility(True)
        else:
            changeMenusVisibility(False)

        self.hostMenu.exec_(self.ui.hostsList.mapToGlobal(pos))

    def _processHostSubmit(self, resp):
        if resp["code"]:
            self.setHostList()
        hostName = resp.get("name")
        if hostName:
            hostItem = self.findHostItemByName(hostName)
            self.slotConnectHost(hostItem)

    def addHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        self._processHostSubmit(hostDialog.add())

    def editHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.edit(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def editGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.edit(self.currentGroupName)
        self._processHostSubmit(resp)

    def deleteGroup(self):
        retCode = self.showOkCancelMessageBox(
            "Do you want to remove selected group? All assigned hosts "
            "to this group will be unassigned.", "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        self.hosts.deleteGroup(self.currentGroupName)
        self.setHostList()

    def showDeleteGroupDialog(self):
        deleteGroupDialog = DeleteGroupDialog(self.hosts)
        deleteGroupDialog.deleteGroup()
        self.setHostList()

    def duplicateHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.duplicate(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def deleteHost(self):
        retCode = self.showOkCancelMessageBox(
            "Do you want to remove selected hosts?", "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        for host in self.getSelectedHosts():
            self.hosts.delete(host)
        self.setHostList()

    def connectFrameless(self, screenIndex=None):
        self.connectHost(self.getCurrentHostListItemName(),
                         frameless=True,
                         screenIndex=screenIndex)

    # Fix to release keyboard from QX11EmbedContainer, when we leave widget through wm border
    def leaveEvent(self, event):
        keyG = QWidget.keyboardGrabber()
        if keyG is not None:
            keyG.releaseKeyboard()
        event.accept()  # needed?

    def setHostList(self):
        """ set hosts list in list view """
        self.ui.hostsList.clear()
        self.refreshGroups()
        hostFilter = self.ui.filter.text()
        if self.showHostsInGroups:
            self.showHostListInGroups(hostFilter)
        else:
            self.showHostList(hostFilter)

    def showHostList(self, hostFilter):
        groupFilter = [
            group for group, visibility in self.groups.items() if visibility
        ]
        hosts = self.hosts.getHostsListByHostNameAndGroup(
            hostFilter, groupFilter)
        self.ui.hostsList.addItems(hosts)

    def showHostListInGroups(self, hostFilter):
        hosts = self.hosts.getGroupedHostNames(hostFilter)
        for group, hostsList in hosts.items():
            if self.groups.get(group, True):
                if group is None:
                    group = unassignedGroupName
                groupHeader = QtGui.QListWidgetItem(
                    type=self.typeQListWidgetHeader)
                groupLabel = QtGui.QLabel(unicode(group))
                groupLabel.setProperty('class', 'group-title')
                self.ui.hostsList.addItem(groupHeader)
                self.ui.hostsList.setItemWidget(groupHeader, groupLabel)
                self.ui.hostsList.addItems(hostsList)

    def slotShowHost(self, item):
        # on one click we activating tab and showing options
        self.tabWidget.activateTab(item)

    def slotConnectHost(self, item):
        if self.isHostListHeader(item):
            return
        self.connectHost(unicode(item.text()))

    def connectHost(self, hostId, frameless=False, screenIndex=None):
        hostId = unicode(hostId)  # sometimes hostId comes as QString
        tabPage = self.tabWidget.createTab(hostId)
        tabPage.reconnectionNeeded.connect(self.connectHost)

        if frameless:
            self.tabWidget.detachFrameless(tabPage, screenIndex)

        try:
            execCmd, opts = self.getCmd(tabPage, hostId)
        except LookupError:
            logger.error(u"Host {} not found.".format(hostId))
            return

        ProcessManager.start(hostId, tabPage, execCmd, opts)
        return tabPage

    def getCmd(self, tabPage, hostName):
        host = self.hosts.get(hostName)

        # set tabPage widget
        width, height = tabPage.setSizeAndGetCurrent()
        # 1et widget winId to embed rdesktop
        winId = tabPage.x11.winId()

        # set remote desktop client, at this time works only with freerdp
        remoteClientType, remoteClientOptions = self.config.getRdpClient()
        remoteClient = ClientFactory(remoteClientType, **remoteClientOptions)
        remoteClient.setWindowParameters(winId, width, height)
        remoteClient.setUserAndPassword(host.user, host.password)
        remoteClient.setAddress(host.address)
        return remoteClient.getComposedCommand()

    def saveSettings(self):
        self.config.setValue("geometry", self.saveGeometry())
        self.config.setValue("windowState", self.saveState())
        self.config.setValue('trayIconVisibility', self.tray.isVisible())
        self.config.setValue('mainWindowVisibility', self.isVisible())
        self.config.setValue('groups', self.groups)
        self.config.setValue('showHostsInGroups', self.showHostsInGroups)

    def restoreSettings(self):
        try:
            self.restoreGeometry(
                self.config.getValue("geometry").toByteArray())
            self.restoreState(
                self.config.getValue("windowState").toByteArray())
        except Exception:
            logger.debug("No settings to restore")

        # restore tray icon state
        trayIconVisibility = self.config.getValue('trayIconVisibility',
                                                  "true").toBool()
        self.tray.setVisible(trayIconVisibility)

        self.showHostsInGroups = self.config.getValue('showHostsInGroups',
                                                      'false').toBool()

        if self.tray.isVisible():
            mainWindowVisibility = self.config.getValue(
                'mainWindowVisibility', "true").toBool()
            self.setVisible(mainWindowVisibility)
        else:  # it tray icon is not visible, always show main window
            self.show()

        self.groups = {
            unicode(k): v
            for k, v in self.config.getValue('groups',
                                             {}).toPyObject().items()
        }

    def closeEvent(self, event):
        if not ProcessManager.hasActiveProcess:
            self.saveSettings()
            QCoreApplication.exit()
            return

        ret = self.showOkCancelMessageBox("Are you sure do you want to quit?",
                                          "Exit confirmation")
        if ret == QMessageBox.Cancel:
            event.ignore()
            return

        self.saveSettings()
        ProcessManager.killemall()
        event.accept()
        QCoreApplication.exit()

    def showOkCancelMessageBox(self, messageBoxText, windowTitle):
        msgBox = QMessageBox(self, text=messageBoxText)
        msgBox.setWindowTitle(windowTitle)
        msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        msgBox.setIcon(QMessageBox.Question)
        return msgBox.exec_()
Exemplo n.º 5
0
class GlobalSysTray(object):
    def __init__(self, parent, name, icon):
        object.__init__(self)

        self._app    = None
        self._parent = parent
        self._gtk_running = False
        self._quit_added  = False

        self.act_indexes  = []
        self.sep_indexes  = []
        self.menu_indexes = []

        if TrayEngine == "KDE":
            self.menu = KMenu(parent)
            self.menu.setTitle(name)
            self.tray = KStatusNotifierItem()
            self.tray.setAssociatedWidget(parent)
            self.tray.setCategory(KStatusNotifierItem.ApplicationStatus)
            self.tray.setContextMenu(self.menu)
            self.tray.setIconByPixmap(getIcon(icon))
            self.tray.setTitle(name)
            self.tray.setToolTipTitle(" ")
            self.tray.setToolTipIconByPixmap(getIcon(icon))
            # Double-click is managed by KDE

        elif TrayEngine == "AppIndicator":
            self.menu = Gtk.Menu()
            self.tray = AppIndicator.Indicator.new(name, icon, AppIndicator.IndicatorCategory.APPLICATION_STATUS)
            self.tray.set_menu(self.menu)
            # Double-click is not possible with App-Indicators

        elif TrayEngine == "Qt":
            self.menu = QMenu(parent)
            self.tray = QSystemTrayIcon(getIcon(icon))
            self.tray.setContextMenu(self.menu)
            self.tray.setParent(parent)
            self.tray.connect(self.tray, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.qt_systray_clicked)

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

    def addAction(self, act_name_id, act_name_string, is_check=False):
        if TrayEngine == "KDE":
            act_widget = KAction(act_name_string, self.menu)
            act_widget.setCheckable(is_check)
            self.menu.addAction(act_widget)

        elif TrayEngine == "AppIndicator":
            if is_check:
                act_widget = Gtk.CheckMenuItem(act_name_string)
            else:
                act_widget = Gtk.ImageMenuItem(act_name_string)
                act_widget.set_image(None)
            act_widget.show()
            self.menu.append(act_widget)

        elif TrayEngine == "Qt":
            act_widget = QAction(act_name_string, self.menu)
            act_widget.setCheckable(is_check)
            self.menu.addAction(act_widget)

        else:
            act_widget = None

        act_obj = [None, None, None, None]
        act_obj[iActNameId] = act_name_id
        act_obj[iActWidget] = act_widget

        self.act_indexes.append(act_obj)

    def addSeparator(self, sep_name_id):
        if TrayEngine == "KDE":
            sep_widget = self.menu.addSeparator()

        elif TrayEngine == "AppIndicator":
            sep_widget = Gtk.SeparatorMenuItem()
            sep_widget.show()
            self.menu.append(sep_widget)

        elif TrayEngine == "Qt":
            sep_widget = self.menu.addSeparator()

        else:
            sep_widget = None

        sep_obj = [None, None, None]
        sep_obj[iSepNameId] = sep_name_id
        sep_obj[iSepWidget] = sep_widget

        self.sep_indexes.append(sep_obj)

    def addMenu(self, menu_name_id, menu_name_string):
        if TrayEngine == "KDE":
            menu_widget = KMenu(menu_name_string, self.menu)
            self.menu.addMenu(menu_widget)

        elif TrayEngine == "AppIndicator":
            menu_widget = Gtk.MenuItem(menu_name_string)
            menu_parent = Gtk.Menu()
            menu_widget.set_submenu(menu_parent)
            menu_widget.show()
            self.menu.append(menu_widget)

        elif TrayEngine == "Qt":
            menu_widget = QMenu(menu_name_string, self.menu)
            self.menu.addMenu(menu_widget)

        else:
            menu_widget = None

        menu_obj = [None, None, None]
        menu_obj[iMenuNameId] = menu_name_id
        menu_obj[iMenuWidget] = menu_widget

        self.menu_indexes.append(menu_obj)

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

    def addMenuAction(self, menu_name_id, act_name_id, act_name_string, is_check=False):
        i = self.get_menu_index(menu_name_id)
        if i < 0: return

        menu_widget = self.menu_indexes[i][iMenuWidget]

        if TrayEngine == "KDE":
            act_widget = KAction(act_name_string, menu_widget)
            act_widget.setCheckable(is_check)
            menu_widget.addAction(act_widget)

        elif TrayEngine == "AppIndicator":
            menu_widget = menu_widget.get_submenu()
            if is_check:
                act_widget = Gtk.CheckMenuItem(act_name_string)
            else:
                act_widget = Gtk.ImageMenuItem(act_name_string)
                act_widget.set_image(None)
            act_widget.show()
            menu_widget.append(act_widget)

        elif TrayEngine == "Qt":
            act_widget = QAction(act_name_string, menu_widget)
            act_widget.setCheckable(is_check)
            menu_widget.addAction(act_widget)

        else:
            act_widget = None

        act_obj = [None, None, None, None]
        act_obj[iActNameId] = act_name_id
        act_obj[iActWidget] = act_widget
        act_obj[iActParentMenuId] = menu_name_id

        self.act_indexes.append(act_obj)

    def addMenuSeparator(self, menu_name_id, sep_name_id):
        i = self.get_menu_index(menu_name_id)
        if i < 0: return

        menu_widget = self.menu_indexes[i][iMenuWidget]

        if TrayEngine == "KDE":
            sep_widget = menu_widget.addSeparator()

        elif TrayEngine == "AppIndicator":
            menu_widget = menu_widget.get_submenu()
            sep_widget = Gtk.SeparatorMenuItem()
            sep_widget.show()
            menu_widget.append(sep_widget)

        elif TrayEngine == "Qt":
            sep_widget = menu_widget.addSeparator()

        else:
            sep_widget = None

        sep_obj = [None, None, None]
        sep_obj[iSepNameId] = sep_name_id
        sep_obj[iSepWidget] = sep_widget
        sep_obj[iSepParentMenuId] = menu_name_id

        self.sep_indexes.append(sep_obj)

    #def addSubMenu(self, menu_name_id, new_menu_name_id, new_menu_name_string):
        #menu_index = self.get_menu_index(menu_name_id)
        #if menu_index < 0: return
        #menu_widget = self.menu_indexes[menu_index][1]
        ##if TrayEngine == "KDE":
            ##new_menu_widget = KMenu(new_menu_name_string, self.menu)
            ##menu_widget.addMenu(new_menu_widget)
        ##elif TrayEngine == "AppIndicator":
            ##new_menu_widget = Gtk.MenuItem(new_menu_name_string)
            ##new_menu_widget.show()
            ##menu_widget.get_submenu().append(new_menu_widget)
            ##parent_menu_widget = Gtk.Menu()
            ##new_menu_widget.set_submenu(parent_menu_widget)
        ##else:
        #if (1):
            #new_menu_widget = QMenu(new_menu_name_string, self.menu)
            #menu_widget.addMenu(new_menu_widget)
        #self.menu_indexes.append([new_menu_name_id, new_menu_widget, menu_name_id])

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

    def connect(self, act_name_id, act_func):
        i = self.get_act_index(act_name_id)
        if i < 0: return

        act_widget = self.act_indexes[i][iActWidget]

        if TrayEngine == "KDE":
            self.tray.connect(act_widget, SIGNAL("triggered()"), act_func)

        elif TrayEngine == "AppIndicator":
            act_widget.connect("activate", self.gtk_call_func, act_name_id)

        elif TrayEngine == "Qt":
            self.tray.connect(act_widget, SIGNAL("triggered()"), act_func)

        self.act_indexes[i][iActFunc] = act_func

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

    #def setActionChecked(self, act_name_id, yesno):
        #index = self.get_act_index(act_name_id)
        #if index < 0: return
        #act_widget = self.act_indexes[index][1]
        ##if TrayEngine == "KDE":
            ##act_widget.setChecked(yesno)
        ##elif TrayEngine == "AppIndicator":
            ##if type(act_widget) != Gtk.CheckMenuItem:
                ##return # Cannot continue
            ##act_widget.set_active(yesno)
        ##else:
        #if (1):
            #act_widget.setChecked(yesno)

    def setActionEnabled(self, act_name_id, yesno):
        i = self.get_act_index(act_name_id)
        if i < 0: return

        act_widget = self.act_indexes[i][iActWidget]

        if TrayEngine == "KDE":
            act_widget.setEnabled(yesno)

        elif TrayEngine == "AppIndicator":
            act_widget.set_sensitive(yesno)

        elif TrayEngine == "Qt":
            act_widget.setEnabled(yesno)

    def setActionIcon(self, act_name_id, icon):
        i = self.get_act_index(act_name_id)
        if i < 0: return

        act_widget = self.act_indexes[i][iActWidget]

        if TrayEngine == "KDE":
            act_widget.setIcon(KIcon(icon))

        elif TrayEngine == "AppIndicator":
            if not isinstance(act_widget, Gtk.ImageMenuItem):
                # Cannot use icons here
                return

            act_widget.set_image(Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU))
            #act_widget.set_always_show_image(True)

        elif TrayEngine == "Qt":
            act_widget.setIcon(getIcon(icon))

    def setActionText(self, act_name_id, text):
        i = self.get_act_index(act_name_id)
        if i < 0: return

        act_widget = self.act_indexes[i][iActWidget]

        if TrayEngine == "KDE":
            act_widget.setText(text)

        elif TrayEngine == "AppIndicator":
            if isinstance(act_widget, Gtk.ImageMenuItem):
                # Fix icon reset
                last_icon = act_widget.get_image()
                act_widget.set_label(text)
                act_widget.set_image(last_icon)
            else:
                act_widget.set_label(text)

        elif TrayEngine == "Qt":
            act_widget.setText(text)

    def setIcon(self, icon):
        if TrayEngine == "KDE":
            self.tray.setIconByPixmap(getIcon(icon))
            #self.tray.setToolTipIconByPixmap(getIcon(icon))

        elif TrayEngine == "AppIndicator":
            self.tray.set_icon(icon)

        elif TrayEngine == "Qt":
            self.tray.setIcon(getIcon(icon))

    def setToolTip(self, text):
        if TrayEngine == "KDE":
            self.tray.setToolTipSubTitle(text)

        elif TrayEngine == "AppIndicator":
            # ToolTips are disabled in App-Indicators by design
            pass

        elif TrayEngine == "Qt":
            self.tray.setToolTip(text)

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

    #def removeAction(self, act_name_id):
        #index = self.get_act_index(act_name_id)
        #if index < 0: return
        #act_widget = self.act_indexes[index][1]
        #parent_menu_widget = self.get_parent_menu_widget(self.act_indexes[index][2])
        ##if TrayEngine == "KDE":
            ##parent_menu_widget.removeAction(act_widget)
        ##elif TrayEngine == "AppIndicator":
            ##act_widget.hide()
            ##parent_menu_widget.remove(act_widget)
        ##else:
        #if (1):
            #parent_menu_widget.removeAction(act_widget)
        #self.act_indexes.pop(index)

    #def removeSeparator(self, sep_name_id):
        #index = self.get_sep_index(sep_name_id)
        #if index < 0: return
        #sep_widget = self.sep_indexes[index][1]
        #parent_menu_widget = self.get_parent_menu_widget(self.sep_indexes[index][2])
        ##if TrayEngine == "KDE":
            ##parent_menu_widget.removeAction(sep_widget)
        ##elif TrayEngine == "AppIndicator":
            ##sep_widget.hide()
            ##parent_menu_widget.remove(sep_widget)
        ##else:
        #if (1):
            #parent_menu_widget.removeAction(sep_widget)
        #self.sep_indexes.pop(index)

    #def removeMenu(self, menu_name_id):
        #index = self.get_menu_index(menu_name_id)
        #if index < 0: return
        #menu_widget = self.menu_indexes[index][1]
        #parent_menu_widget = self.get_parent_menu_widget(self.menu_indexes[index][2])
        ##if TrayEngine == "KDE":
            ##parent_menu_widget.removeAction(menu_widget.menuAction())
        ##elif TrayEngine == "AppIndicator":
            ##menu_widget.hide()
            ##parent_menu_widget.remove(menu_widget.get_submenu())
        ##else:
        #if (1):
            #parent_menu_widget.removeAction(menu_widget.menuAction())
        #self.remove_actions_by_menu_name_id(menu_name_id)
        #self.remove_separators_by_menu_name_id(menu_name_id)
        #self.remove_submenus_by_menu_name_id(menu_name_id)

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

    #def clearAll(self):
        ##if TrayEngine == "KDE":
            ##self.menu.clear()
        ##elif TrayEngine == "AppIndicator":
            ##for child in self.menu.get_children():
                ##self.menu.remove(child)
        ##else:
        #if (1):
            #self.menu.clear()

        #self.act_indexes = []
        #self.sep_indexes = []
        #self.menu_indexes = []

    #def clearMenu(self, menu_name_id):
        #menu_index = self.get_menu_index(menu_name_id)
        #if menu_index < 0: return
        #menu_widget = self.menu_indexes[menu_index][1]
        ##if TrayEngine == "KDE":
            ##menu_widget.clear()
        ##elif TrayEngine == "AppIndicator":
            ##for child in menu_widget.get_submenu().get_children():
                ##menu_widget.get_submenu().remove(child)
        ##else:
        #if (1):
            #menu_widget.clear()
        #list_of_submenus = [menu_name_id]
        #for x in range(0, 10): # 10x level deep, should cover all cases...
            #for this_menu_name_id, menu_widget, parent_menu_id in self.menu_indexes:
                #if parent_menu_id in list_of_submenus and this_menu_name_id not in list_of_submenus:
                    #list_of_submenus.append(this_menu_name_id)
        #for this_menu_name_id in list_of_submenus:
            #self.remove_actions_by_menu_name_id(this_menu_name_id)
            #self.remove_separators_by_menu_name_id(this_menu_name_id)
            #self.remove_submenus_by_menu_name_id(this_menu_name_id)

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

    def getTrayEngine(self):
        return TrayEngine

    def isTrayAvailable(self):
        if TrayEngine in ("KDE", "Qt"):
            return QSystemTrayIcon.isSystemTrayAvailable()
        elif TrayEngine == "AppIndicator":
            # Ubuntu/Unity always has a systray
            return True
        else:
            return False

    def handleQtCloseEvent(self, event):
        if self.isTrayAvailable() and self._parent.isVisible():
            event.accept()
            self.__hideShowCall()
            return

        self.close()
        QMainWindow.closeEvent(self._parent, event)

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

    def show(self):
        if not self._quit_added:
            self._quit_added = True

            if TrayEngine != "KDE":
                self.addSeparator("_quit")
                self.addAction("show", self._parent.tr("Minimize"))
                self.addAction("quit", self._parent.tr("Quit"))
                self.setActionIcon("quit", "application-exit")
                self.connect("show", self.__hideShowCall)
                self.connect("quit", self.__quitCall)

        if TrayEngine == "KDE":
            self.tray.setStatus(KStatusNotifierItem.Active)
        elif TrayEngine == "AppIndicator":
            self.tray.set_status(AppIndicator.IndicatorStatus.ACTIVE)
        elif TrayEngine == "Qt":
            self.tray.show()

    def hide(self):
        if TrayEngine == "KDE":
            self.tray.setStatus(KStatusNotifierItem.Passive)
        elif TrayEngine == "AppIndicator":
            self.tray.set_status(AppIndicator.IndicatorStatus.PASSIVE)
        elif TrayEngine == "Qt":
            self.tray.hide()

    def close(self):
        if TrayEngine == "KDE":
            self.menu.close()
        elif TrayEngine == "AppIndicator":
            if self._gtk_running:
                self._gtk_running = False
                Gtk.main_quit()
        elif TrayEngine == "Qt":
            self.menu.close()

    def exec_(self, app):
        self._app = app
        if TrayEngine == "AppIndicator":
            self._gtk_running = True
            return Gtk.main()
        else:
            return app.exec_()

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

    def get_act_index(self, act_name_id):
        for i in range(len(self.act_indexes)):
            if self.act_indexes[i][iActNameId] == act_name_id:
                return i
        else:
            print("systray.py - Failed to get action index for %s" % act_name_id)
            return -1

    def get_sep_index(self, sep_name_id):
        for i in range(len(self.sep_indexes)):
            if self.sep_indexes[i][iSepNameId] == sep_name_id:
                return i
        else:
            print("systray.py - Failed to get separator index for %s" % sep_name_id)
            return -1

    def get_menu_index(self, menu_name_id):
        for i in range(len(self.menu_indexes)):
            if self.menu_indexes[i][iMenuNameId] == menu_name_id:
                return i
        else:
            print("systray.py - Failed to get menu index for %s" % menu_name_id)
            return -1

    #def get_parent_menu_widget(self, parent_menu_id):
        #if parent_menu_id != None:
            #menu_index = self.get_menu_index(parent_menu_id)
            #if menu_index >= 0:
                #return self.menu_indexes[menu_index][1]
            #else:
                #print("systray.py::Failed to get parent Menu widget for", parent_menu_id)
                #return None
        #else:
            #return self.menu

    #def remove_actions_by_menu_name_id(self, menu_name_id):
        #h = 0
        #for i in range(len(self.act_indexes)):
            #act_name_id, act_widget, parent_menu_id, act_func = self.act_indexes[i - h]
            #if parent_menu_id == menu_name_id:
                #self.act_indexes.pop(i - h)
                #h += 1

    #def remove_separators_by_menu_name_id(self, menu_name_id):
        #h = 0
        #for i in range(len(self.sep_indexes)):
            #sep_name_id, sep_widget, parent_menu_id = self.sep_indexes[i - h]
            #if parent_menu_id == menu_name_id:
                #self.sep_indexes.pop(i - h)
                #h += 1

    #def remove_submenus_by_menu_name_id(self, submenu_name_id):
        #h = 0
        #for i in range(len(self.menu_indexes)):
            #menu_name_id, menu_widget, parent_menu_id = self.menu_indexes[i - h]
            #if parent_menu_id == submenu_name_id:
                #self.menu_indexes.pop(i - h)
                #h += 1

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

    def gtk_call_func(self, gtkmenu, act_name_id):
        i = self.get_act_index(act_name_id)
        if i < 0: return None

        return self.act_indexes[i][iActFunc]

    def qt_systray_clicked(self, reason):
        if reason in (QSystemTrayIcon.DoubleClick, QSystemTrayIcon.Trigger):
            self.__hideShowCall()

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

    def __hideShowCall(self):
        if self._parent.isVisible():
            self.setActionText("show", self._parent.tr("Restore"))
            self._parent.hide()

            if self._app:
                self._app.setQuitOnLastWindowClosed(False)

        else:
            self.setActionText("show", self._parent.tr("Minimize"))

            if self._parent.isMaximized():
                self._parent.showMaximized()
            else:
                self._parent.showNormal()

            if self._app:
                self._app.setQuitOnLastWindowClosed(True)

            QTimer.singleShot(500, self.__raiseWindow)

    def __quitCall(self):
        if self._app:
            self._app.setQuitOnLastWindowClosed(True)

        self._parent.hide()
        self._parent.close()

    def __raiseWindow(self):
        self._parent.activateWindow()
        self._parent.raise_()
Exemplo n.º 6
0
class MainWindow(base_class, ui_class):
    implements(IObserver)

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.saved_account_state = None

        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='SIPApplicationWillStart')
        notification_center.add_observer(self, name='SIPApplicationDidStart')
        notification_center.add_observer(self, name='SIPAccountGotMessageSummary')
        notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
        notification_center.add_observer(self, name='BlinkSessionNewOutgoing')
        notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing')
        notification_center.add_observer(self, name='FileTransferNewIncoming')
        notification_center.add_observer(self, name='FileTransferNewOutgoing')
        notification_center.add_observer(self, sender=AccountManager())

        icon_manager = IconManager()

        self.pending_watcher_dialogs = []

        self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in xrange(0, 11)]
        self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png')))

        with Resources.directory:
            self.setupUi()

        self.setWindowTitle('Blink')
        self.setWindowIconText('Blink')

        geometry = QSettings().value("main_window/geometry")
        if geometry:
            self.restoreGeometry(geometry)

        self.default_icon_path = Resources.get('icons/default-avatar.png')
        self.default_icon = QIcon(self.default_icon_path)
        self.last_icon_directory = Path('~').normalized
        self.set_user_icon(icon_manager.get('avatar'))

        self.active_sessions_label.hide()
        self.enable_call_buttons(False)
        self.conference_button.setEnabled(False)
        self.hangup_all_button.setEnabled(False)
        self.sip_server_settings_action.setEnabled(False)
        self.search_for_people_action.setEnabled(False)
        self.history_on_server_action.setEnabled(False)
        self.main_view.setCurrentWidget(self.contacts_panel)
        self.contacts_view.setCurrentWidget(self.contact_list_panel)
        self.search_view.setCurrentWidget(self.search_list_panel)

        # System tray
        if QSystemTrayIcon.isSystemTrayAvailable() and not os.getenv('XDG_CURRENT_DESKTOP', '').lower().startswith('unity'):
            self.system_tray_icon = QSystemTrayIcon(QIcon(Resources.get('icons/blink.png')), self)
            self.system_tray_icon.activated.connect(self._SH_SystemTrayIconActivated)
            menu = QMenu(self)
            menu.addAction(QAction("Show", self, triggered=self._AH_SystemTrayShowWindow))
            menu.addAction(QAction(QIcon(Resources.get('icons/application-exit.png')), "Quit", self, triggered=self._AH_QuitActionTriggered))
            self.system_tray_icon.setContextMenu(menu)
            self.system_tray_icon.show()
        else:
            self.system_tray_icon = None

        # Accounts
        self.account_model = AccountModel(self)
        self.enabled_account_model = ActiveAccountModel(self.account_model, self)
        self.server_tools_account_model = ServerToolsAccountModel(self.account_model, self)
        self.identity.setModel(self.enabled_account_model)

        # Contacts
        self.contact_model = ContactModel(self)
        self.contact_search_model = ContactSearchModel(self.contact_model, self)
        self.contact_list.setModel(self.contact_model)
        self.search_list.setModel(self.contact_search_model)

        # Sessions (audio)
        self.session_model = AudioSessionModel(self)
        self.session_list.setModel(self.session_model)
        self.session_list.selectionModel().selectionChanged.connect(self._SH_SessionListSelectionChanged)

        # History
        self.history_manager = HistoryManager()

        # Windows, dialogs and panels
        self.about_panel = AboutPanel(self)
        self.conference_dialog = ConferenceDialog(self)
        self.contact_editor_dialog = ContactEditorDialog(self)
        self.google_contacts_dialog = GoogleContactsDialog(self)
        self.filetransfer_window = FileTransferWindow()
        self.preferences_window = PreferencesWindow(self.account_model, None)
        self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)

        # Signals
        self.account_state.stateChanged.connect(self._SH_AccountStateChanged)
        self.account_state.clicked.connect(self._SH_AccountStateClicked)
        self.activity_note.editingFinished.connect(self._SH_ActivityNoteEditingFinished)
        self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.audio_call_button.clicked.connect(self._SH_AudioCallButtonClicked)
        self.video_call_button.clicked.connect(self._SH_VideoCallButtonClicked)
        self.chat_session_button.clicked.connect(self._SH_ChatSessionButtonClicked)
        self.back_to_contacts_button.clicked.connect(self.search_box.clear) # this can be set in designer -Dan
        self.conference_button.makeConference.connect(self._SH_MakeConference)
        self.conference_button.breakConference.connect(self._SH_BreakConference)

        self.contact_list.selectionModel().selectionChanged.connect(self._SH_ContactListSelectionChanged)
        self.contact_model.itemsAdded.connect(self._SH_ContactModelAddedItems)
        self.contact_model.itemsRemoved.connect(self._SH_ContactModelRemovedItems)

        self.display_name.editingFinished.connect(self._SH_DisplayNameEditingFinished)
        self.hangup_all_button.clicked.connect(self._SH_HangupAllButtonClicked)

        self.identity.activated[int].connect(self._SH_IdentityChanged)
        self.identity.currentIndexChanged[int].connect(self._SH_IdentityCurrentIndexChanged)

        self.mute_button.clicked.connect(self._SH_MuteButtonClicked)

        self.search_box.textChanged.connect(self._SH_SearchBoxTextChanged)
        self.search_box.returnPressed.connect(self._SH_SearchBoxReturnPressed)
        self.search_box.shortcut.activated.connect(self.search_box.setFocus)

        self.search_list.selectionModel().selectionChanged.connect(self._SH_SearchListSelectionChanged)

        self.server_tools_account_model.rowsInserted.connect(self._SH_ServerToolsAccountModelChanged)
        self.server_tools_account_model.rowsRemoved.connect(self._SH_ServerToolsAccountModelChanged)

        self.session_model.sessionAdded.connect(self._SH_AudioSessionModelAddedSession)
        self.session_model.sessionRemoved.connect(self._SH_AudioSessionModelRemovedSession)
        self.session_model.structureChanged.connect(self._SH_AudioSessionModelChangedStructure)

        self.silent_button.clicked.connect(self._SH_SilentButtonClicked)
        self.switch_view_button.viewChanged.connect(self._SH_SwitchViewButtonChangedView)

        # Blink menu actions
        self.about_action.triggered.connect(self.about_panel.show)
        self.add_account_action.triggered.connect(self.preferences_window.show_add_account_dialog)
        self.manage_accounts_action.triggered.connect(self.preferences_window.show_for_accounts)
        self.help_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/help-qt.phtml')))
        self.preferences_action.triggered.connect(self.preferences_window.show)
        self.auto_accept_chat_action.triggered.connect(self._AH_AutoAcceptChatActionTriggered)
        self.received_messages_sound_action.triggered.connect(self._AH_ReceivedMessagesSoundActionTriggered)
        self.answering_machine_action.triggered.connect(self._AH_EnableAnsweringMachineActionTriggered)
        self.release_notes_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/changelog-qt.phtml')))
        self.quit_action.triggered.connect(self._AH_QuitActionTriggered)

        # Call menu actions
        self.redial_action.triggered.connect(self._AH_RedialActionTriggered)
        self.join_conference_action.triggered.connect(self.conference_dialog.show)
        self.history_menu.aboutToShow.connect(self._SH_HistoryMenuAboutToShow)
        self.history_menu.triggered.connect(self._AH_HistoryMenuTriggered)
        self.output_devices_group.triggered.connect(self._AH_AudioOutputDeviceChanged)
        self.input_devices_group.triggered.connect(self._AH_AudioInputDeviceChanged)
        self.alert_devices_group.triggered.connect(self._AH_AudioAlertDeviceChanged)
        self.video_devices_group.triggered.connect(self._AH_VideoDeviceChanged)
        self.mute_action.triggered.connect(self._SH_MuteButtonClicked)
        self.silent_action.triggered.connect(self._SH_SilentButtonClicked)

        # Tools menu actions
        self.sip_server_settings_action.triggered.connect(self._AH_SIPServerSettings)
        self.search_for_people_action.triggered.connect(self._AH_SearchForPeople)
        self.history_on_server_action.triggered.connect(self._AH_HistoryOnServer)

        # Window menu actions
        self.chat_window_action.triggered.connect(self._AH_ChatWindowActionTriggered)
        self.transfers_window_action.triggered.connect(self._AH_TransfersWindowActionTriggered)
        self.logs_window_action.triggered.connect(self._AH_LogsWindowActionTriggered)
        self.received_files_window_action.triggered.connect(self._AH_ReceivedFilesWindowActionTriggered)
        self.screenshots_window_action.triggered.connect(self._AH_ScreenshotsWindowActionTriggered)

    def setupUi(self):
        super(MainWindow, self).setupUi(self)

        self.search_box.shortcut = QShortcut(self.search_box)
        self.search_box.shortcut.setKey('Ctrl+F')

        self.output_devices_group = QActionGroup(self)
        self.input_devices_group = QActionGroup(self)
        self.alert_devices_group = QActionGroup(self)
        self.video_devices_group = QActionGroup(self)

        self.request_screen_action = QAction('Request screen', self, triggered=self._AH_RequestScreenActionTriggered)
        self.share_my_screen_action = QAction('Share my screen', self, triggered=self._AH_ShareMyScreenActionTriggered)
        self.screen_sharing_button.addAction(self.request_screen_action)
        self.screen_sharing_button.addAction(self.share_my_screen_action)

        # adjust search box height depending on theme as the value set in designer isn't suited for all themes
        search_box = self.search_box
        option = QStyleOptionFrameV2()
        search_box.initStyleOption(option)
        frame_width = search_box.style().pixelMetric(QStyle.PM_DefaultFrameWidth, option, search_box)
        if frame_width < 4:
            search_box.setMinimumHeight(20 + 2*frame_width)

        # adjust the combo boxes for themes with too much padding (like the default theme on Ubuntu 10.04)
        option = QStyleOptionComboBox()
        self.identity.initStyleOption(option)
        wide_padding = self.identity.style().subControlRect(QStyle.CC_ComboBox, option, QStyle.SC_ComboBoxEditField, self.identity).height() < 10
        self.identity.setStyleSheet("""QComboBox { padding: 0px 4px 0px 4px; }""" if wide_padding else "")

    def closeEvent(self, event):
        QSettings().setValue("main_window/geometry", self.saveGeometry())
        super(MainWindow, self).closeEvent(event)
        self.about_panel.close()
        self.contact_editor_dialog.close()
        self.google_contacts_dialog.close()
        self.server_tools_window.close()
        for dialog in self.pending_watcher_dialogs[:]:
            dialog.close()

    def show(self):
        super(MainWindow, self).show()
        self.raise_()
        self.activateWindow()

    def set_user_icon(self, icon):
        self.account_state.setIcon(icon or self.default_icon)

    def enable_call_buttons(self, enabled):
        self.audio_call_button.setEnabled(enabled)
        self.video_call_button.setEnabled(enabled)
        self.chat_session_button.setEnabled(enabled)
        self.screen_sharing_button.setEnabled(enabled)

    def load_audio_devices(self):
        settings = SIPSimpleSettings()

        action = QAction(u'System default', self.output_devices_group)
        action.setData(u'system_default')
        self.output_device_menu.addAction(action)
        self.output_device_menu.addSeparator()
        for device in SIPApplication.engine.output_devices:
            action = QAction(device, self.output_devices_group)
            action.setData(device)
            self.output_device_menu.addAction(action)
        action = QAction(u'None', self.output_devices_group)
        action.setData(None)
        self.output_device_menu.addAction(action)
        for action in self.output_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.output_device == action.data():
                action.setChecked(True)

        action = QAction(u'System default', self.input_devices_group)
        action.setData(u'system_default')
        self.input_device_menu.addAction(action)
        self.input_device_menu.addSeparator()
        for device in SIPApplication.engine.input_devices:
            action = QAction(device, self.input_devices_group)
            action.setData(device)
            self.input_device_menu.addAction(action)
        action = QAction(u'None', self.input_devices_group)
        action.setData(None)
        self.input_device_menu.addAction(action)
        for action in self.input_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.input_device == action.data():
                action.setChecked(True)

        action = QAction(u'System default', self.alert_devices_group)
        action.setData(u'system_default')
        self.alert_device_menu.addAction(action)
        self.alert_device_menu.addSeparator()
        for device in SIPApplication.engine.output_devices:
            action = QAction(device, self.alert_devices_group)
            action.setData(device)
            self.alert_device_menu.addAction(action)
        action = QAction(u'None', self.alert_devices_group)
        action.setData(None)
        self.alert_device_menu.addAction(action)
        for action in self.alert_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.alert_device == action.data():
                action.setChecked(True)

    def load_video_devices(self):
        settings = SIPSimpleSettings()

        action = QAction(u'System default', self.video_devices_group)
        action.setData(u'system_default')
        self.video_camera_menu.addAction(action)
        self.video_camera_menu.addSeparator()
        for device in SIPApplication.engine.video_devices:
            action = QAction(device, self.video_devices_group)
            action.setData(device)
            self.video_camera_menu.addAction(action)
        action = QAction(u'None', self.video_devices_group)
        action.setData(None)
        self.video_camera_menu.addAction(action)
        for action in self.video_devices_group.actions():
            action.setCheckable(True)
            if settings.video.device == action.data():
                action.setChecked(True)

    def _AH_AccountActionTriggered(self, action, enabled):
        account = action.data()
        account.enabled = enabled
        account.save()

    def _AH_AudioAlertDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.alert_device = action.data()
        settings.save()

    def _AH_AudioInputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.input_device = action.data()
        settings.save()

    def _AH_AudioOutputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.output_device = action.data()
        settings.save()

    def _AH_VideoDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.video.device = action.data()
        settings.save()

    def _AH_AutoAcceptChatActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.chat.auto_accept = checked
        settings.save()

    def _AH_ReceivedMessagesSoundActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.sounds.play_message_alerts = checked
        settings.save()

    def _AH_EnableAnsweringMachineActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.answering_machine.enabled = checked
        settings.save()

    def _AH_GoogleContactsActionTriggered(self):
        settings = SIPSimpleSettings()
        if settings.google_contacts.authorization_token is not None:
            settings.google_contacts.authorization_token = None
            settings.save()
            self.google_contacts_dialog.hide()
        else:
            self.google_contacts_dialog.open()

    def _AH_RedialActionTriggered(self):
        session_manager = SessionManager()
        if session_manager.last_dialed_uri is not None:
            contact, contact_uri = URIUtils.find_contact(session_manager.last_dialed_uri)
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')]) # TODO: remember used media types and redial with them. -Saul

    def _AH_SIPServerSettings(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_settings_page(account)

    def _AH_SearchForPeople(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_search_for_people_page(account)

    def _AH_HistoryOnServer(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_history_page(account)

    def _AH_ChatWindowActionTriggered(self, checked):
        blink = QApplication.instance()
        blink.chat_window.show()

    def _AH_TransfersWindowActionTriggered(self, checked):
        self.filetransfer_window.show()

    def _AH_LogsWindowActionTriggered(self, checked):
        directory = ApplicationData.get('logs')
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ReceivedFilesWindowActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        directory = settings.file_transfer.directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ScreenshotsWindowActionTriggered(self, checked):
        settings = BlinkSettings()
        directory = settings.screen_sharing.screenshots_directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_VoicemailActionTriggered(self, action, checked):
        account = action.data()
        contact, contact_uri = URIUtils.find_contact(account.voicemail_uri, display_name='Voicemail')
        session_manager = SessionManager()
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account)

    def _SH_HistoryMenuAboutToShow(self):
        self.history_menu.clear()
        if self.history_manager.calls:
            for entry in reversed(self.history_manager.calls):
                action = self.history_menu.addAction(entry.icon, entry.text)
                action.entry = entry
                action.setToolTip(entry.uri)
        else:
            action = self.history_menu.addAction("Call history is empty")
            action.setEnabled(False)

    def _AH_HistoryMenuTriggered(self, action):
        account_manager = AccountManager()
        session_manager = SessionManager()
        try:
            account = account_manager.get_account(action.entry.account_id)
        except KeyError:
            account = None
        contact, contact_uri = URIUtils.find_contact(action.entry.uri)
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account) # TODO: memorize media type and use it? -Saul (not sure about history in/out -Dan)

    def _AH_SystemTrayShowWindow(self, checked):
        self.show()
        self.raise_()
        self.activateWindow()

    def _AH_QuitActionTriggered(self, checked):
        if self.system_tray_icon is not None:
            self.system_tray_icon.hide()
        QApplication.instance().quit()

    def _SH_AccountStateChanged(self):
        self.activity_note.setText(self.account_state.note)
        if self.account_state.state is AccountState.Invisible:
            self.activity_note.inactiveText = u'(invisible)'
            self.activity_note.setEnabled(False)
        else:
            if not self.activity_note.isEnabled():
                self.activity_note.inactiveText = u'Add an activity note here'
                self.activity_note.setEnabled(True)
        if not self.account_state.state.internal:
            self.saved_account_state = None
        blink_settings = BlinkSettings()
        blink_settings.presence.current_state = PresenceState(self.account_state.state, self.account_state.note)
        blink_settings.presence.state_history = [PresenceState(state, note) for state, note in self.account_state.history]
        blink_settings.save()

    def _SH_AccountStateClicked(self, checked):
        filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)")
        if filename:
            self.last_icon_directory = os.path.dirname(filename)
            filename = filename if os.path.realpath(filename) != os.path.realpath(self.default_icon_path) else None
            blink_settings = BlinkSettings()
            icon_manager = IconManager()
            if filename is not None:
                icon = icon_manager.store_file('avatar', filename)
                if icon is not None:
                    blink_settings.presence.icon = IconDescriptor(FileURL(icon.filename), hashlib.sha1(icon.content).hexdigest())
                else:
                    icon_manager.remove('avatar')
                    blink_settings.presence.icon = None
            else:
                icon_manager.remove('avatar')
                blink_settings.presence.icon = None
            blink_settings.save()

    def _SH_ActivityNoteEditingFinished(self):
        self.activity_note.clearFocus()
        note = self.activity_note.text()
        if note != self.account_state.note:
            self.account_state.state.internal = False
            self.account_state.setState(self.account_state.state, note)

    def _SH_AddContactButtonClicked(self, clicked):
        self.contact_editor_dialog.open_for_add(self.search_box.text(), None)

    def _SH_AudioCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartAudioCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_VideoCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartVideoCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio'), StreamDescription('video')])

    def _SH_ChatSessionButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartChatSession()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('chat')], connect=False)

    def _AH_RequestScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_RequestScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])

    def _AH_ShareMyScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_ShareMyScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])

    def _SH_BreakConference(self):
        active_session = self.session_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
        self.session_model.breakConference(active_session.client_conference)

    def _SH_ContactListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.contact_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _SH_ContactModelAddedItems(self, items):
        if not self.search_box.text():
            return
        active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
        self.search_view.setCurrentWidget(active_widget)

    def _SH_ContactModelRemovedItems(self, items):
        if not self.search_box.text():
            return
        if any(type(item) is Contact for item in items) and self.contact_search_model.rowCount() == 0:
            self.search_box.clear() # check this. it is no longer be the correct behaviour as now contacts can be deleted from remote -Dan
        else:
            active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
            self.search_view.setCurrentWidget(active_widget)

    def _SH_DisplayNameEditingFinished(self):
        self.display_name.clearFocus()
        index = self.identity.currentIndex()
        if index != -1:
            name = self.display_name.text()
            account = self.identity.itemData(index).account
            account.display_name = name if name else None
            account.save()

    def _SH_HangupAllButtonClicked(self):
        for session in self.session_model.sessions:
            session.end()

    def _SH_IdentityChanged(self, index):
        account_manager = AccountManager()
        account_manager.default_account = self.identity.itemData(index).account

    def _SH_IdentityCurrentIndexChanged(self, index):
        if index != -1:
            account = self.identity.itemData(index).account
            self.display_name.setText(account.display_name or u'')
            self.display_name.setEnabled(True)
            self.activity_note.setEnabled(True)
            self.account_state.setEnabled(True)
        else:
            self.display_name.clear()
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)
            self.account_state.setState(AccountState.Invisible)
            self.saved_account_state = None

    def _SH_MakeConference(self):
        self.session_model.conferenceSessions([session for session in self.session_model.active_sessions if session.client_conference is None])

    def _SH_MuteButtonClicked(self, muted):
        settings = SIPSimpleSettings()
        settings.audio.muted = muted
        settings.save()

    def _SH_SearchBoxReturnPressed(self):
        address = self.search_box.text()
        if address:
            contact, contact_uri = URIUtils.find_contact(address)
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_SearchBoxTextChanged(self, text):
        self.contact_search_model.setFilterFixedString(text)
        account_manager = AccountManager()
        if text:
            self.switch_view_button.view = SwitchViewButton.ContactView
            if self.contacts_view.currentWidget() is not self.search_panel:
                self.search_list.selectionModel().clearSelection()
            self.contacts_view.setCurrentWidget(self.search_panel)
            self.search_view.setCurrentWidget(self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel)
            selected_items = self.search_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)
        else:
            self.contacts_view.setCurrentWidget(self.contact_list_panel)
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and type(selected_items[0].data(Qt.UserRole)) is Contact)
        self.search_list.detail_model.contact = None
        self.search_list.detail_view.hide()

    def _SH_SearchListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.search_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)

    def _SH_ServerToolsAccountModelChanged(self, parent_index, start, end):
        server_tools_enabled = self.server_tools_account_model.rowCount() > 0
        self.sip_server_settings_action.setEnabled(server_tools_enabled)
        self.search_for_people_action.setEnabled(server_tools_enabled)
        self.history_on_server_action.setEnabled(server_tools_enabled)

    def _SH_SessionListSelectionChanged(self, selected, deselected):
        selected_indexes = selected.indexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in self.session_model.active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)

    def _SH_AudioSessionModelAddedSession(self, session_item):
        if len(session_item.blink_session.streams) == 1:
            self.switch_view_button.view = SwitchViewButton.SessionView

    def _SH_AudioSessionModelRemovedSession(self, session_item):
        if self.session_model.rowCount() == 0:
            self.switch_view_button.view = SwitchViewButton.ContactView

    def _SH_AudioSessionModelChangedStructure(self):
        active_sessions = self.session_model.active_sessions
        self.active_sessions_label.setText(u'There is 1 active call' if len(active_sessions)==1 else u'There are %d active calls' % len(active_sessions))
        self.active_sessions_label.setVisible(any(active_sessions))
        self.hangup_all_button.setEnabled(any(active_sessions))
        selected_indexes = self.session_list.selectionModel().selectedIndexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)
        if active_sessions:
            if self.account_state.state is not AccountState.Invisible:
                if self.saved_account_state is None:
                    self.saved_account_state = self.account_state.state, self.activity_note.text()
                self.account_state.setState(AccountState.Busy.Internal, note=u'On the phone')
        elif self.saved_account_state is not None:
            state, note = self.saved_account_state
            self.saved_account_state = None
            self.account_state.setState(state, note)

    def _SH_SilentButtonClicked(self, silent):
        settings = SIPSimpleSettings()
        settings.audio.silent = silent
        settings.save()

    def _SH_SwitchViewButtonChangedView(self, view):
        self.main_view.setCurrentWidget(self.contacts_panel if view is SwitchViewButton.ContactView else self.sessions_panel)

    def _SH_PendingWatcherDialogFinished(self, result):
        self.pending_watcher_dialogs.remove(self.sender())

    def _SH_SystemTrayIconActivated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()
            self.raise_()
            self.activateWindow()

    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPApplicationWillStart(self, notification):
        account_manager = AccountManager()
        settings = SIPSimpleSettings()
        self.silent_action.setChecked(settings.audio.silent)
        self.silent_button.setChecked(settings.audio.silent)
        self.answering_machine_action.setChecked(settings.answering_machine.enabled)
        self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
        self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
        if settings.google_contacts.authorization_token is None:
            self.google_contacts_action.setText(u'Enable &Google Contacts...')
        else:
            self.google_contacts_action.setText(u'Disable &Google Contacts')
        self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered)
        if not any(account.enabled for account in account_manager.iter_accounts()):
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)

    def _NH_SIPApplicationDidStart(self, notification):
        self.load_audio_devices()
        self.load_video_devices()
        notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
        notification.center.add_observer(self, name='AudioDevicesDidChange')
        blink_settings = BlinkSettings()
        self.account_state.history = [(item.state, item.note) for item in blink_settings.presence.state_history]
        state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
        self.account_state.setState(state, blink_settings.presence.current_state.note)

    def _NH_AudioDevicesDidChange(self, notification):
        for action in self.output_device_menu.actions():
            self.output_devices_group.removeAction(action)
            self.output_device_menu.removeAction(action)
        for action in self.input_device_menu.actions():
            self.input_devices_group.removeAction(action)
            self.input_device_menu.removeAction(action)
        for action in self.alert_device_menu.actions():
            self.alert_devices_group.removeAction(action)
            self.alert_device_menu.removeAction(action)
        if self.session_model.active_sessions:
            old_devices = set(notification.data.old_devices)
            new_devices = set(notification.data.new_devices)
            added_devices = new_devices - old_devices
            if added_devices:
                new_device = added_devices.pop()
                settings = SIPSimpleSettings()
                settings.audio.input_device = new_device
                settings.audio.output_device = new_device
                settings.save()
        self.load_audio_devices()

    def _NH_CFGSettingsObjectDidChange(self, notification):
        settings = SIPSimpleSettings()
        blink_settings = BlinkSettings()
        icon_manager = IconManager()
        if notification.sender is settings:
            if 'audio.muted' in notification.data.modified:
                self.mute_action.setChecked(settings.audio.muted)
                self.mute_button.setChecked(settings.audio.muted)
            if 'audio.silent' in notification.data.modified:
                self.silent_action.setChecked(settings.audio.silent)
                self.silent_button.setChecked(settings.audio.silent)
            if 'audio.output_device' in notification.data.modified:
                action = (action for action in self.output_devices_group.actions() if action.data() == settings.audio.output_device).next()
                action.setChecked(True)
            if 'audio.input_device' in notification.data.modified:
                action = (action for action in self.input_devices_group.actions() if action.data() == settings.audio.input_device).next()
                action.setChecked(True)
            if 'audio.alert_device' in notification.data.modified:
                action = (action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device).next()
                action.setChecked(True)
            if 'video.device' in notification.data.modified:
                action = (action for action in self.video_devices_group.actions() if action.data() == settings.video.device).next()
                action.setChecked(True)
            if 'answering_machine.enabled' in notification.data.modified:
                self.answering_machine_action.setChecked(settings.answering_machine.enabled)
            if 'chat.auto_accept' in notification.data.modified:
                self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
            if 'sounds.play_message_alerts' in notification.data.modified:
                self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
            if 'google_contacts.authorization_token' in notification.data.modified:
                authorization_token = notification.sender.google_contacts.authorization_token
                if authorization_token is None:
                    self.google_contacts_action.setText(u'Enable &Google Contacts...')
                else:
                    self.google_contacts_action.setText(u'Disable &Google Contacts')
                if authorization_token is InvalidToken:
                    self.google_contacts_dialog.open_for_incorrect_password()
        elif notification.sender is blink_settings:
            if 'presence.current_state' in notification.data.modified:
                state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
                self.account_state.setState(state, blink_settings.presence.current_state.note)
            if 'presence.icon' in notification.data.modified:
                self.set_user_icon(icon_manager.get('avatar'))
            if 'presence.offline_note' in notification.data.modified:
                # TODO: set offline note -Saul
                pass
        elif isinstance(notification.sender, (Account, BonjourAccount)):
            account_manager = AccountManager()
            account = notification.sender
            if 'enabled' in notification.data.modified:
                action = (action for action in self.accounts_menu.actions() if action.data() is account).next()
                action.setChecked(account.enabled)
            if 'display_name' in notification.data.modified and account is account_manager.default_account:
                self.display_name.setText(account.display_name or u'')
            if set(['enabled', 'message_summary.enabled', 'message_summary.voicemail_uri']).intersection(notification.data.modified):
                action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
                action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
                action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)

    def _NH_SIPAccountManagerDidAddAccount(self, notification):
        account = notification.data.account
        action = QAction(account.id if account is not BonjourAccount() else u'Bonjour', None)
        action.setEnabled(True if account is not BonjourAccount() else BonjourAccount.mdns_available)
        action.setCheckable(True)
        action.setChecked(account.enabled)
        action.setData(account)
        action.triggered.connect(partial(self._AH_AccountActionTriggered, action))
        self.accounts_menu.addAction(action)
        action = QAction(self.mwi_icons[0], account.id, None)
        action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
        action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
        action.setData(account)
        action.triggered.connect(partial(self._AH_VoicemailActionTriggered, action))
        self.voicemail_menu.addAction(action)

    def _NH_SIPAccountManagerDidRemoveAccount(self, notification):
        account = notification.data.account
        action = (action for action in self.accounts_menu.actions() if action.data() is account).next()
        self.accounts_menu.removeAction(action)
        action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
        self.voicemail_menu.removeAction(action)

    def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification):
        if notification.data.account is None:
            self.enable_call_buttons(False)
        else:
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _NH_SIPAccountGotMessageSummary(self, notification):
        account = notification.sender
        summary = notification.data.message_summary
        action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
        action.setEnabled(account.voicemail_uri is not None)
        if summary.messages_waiting:
            try:
                new_messages = limit(int(summary.summaries['voice-message']['new_messages']), min=0, max=11)
            except (KeyError, ValueError):
                new_messages = 0
        else:
            new_messages = 0
        action.setIcon(self.mwi_icons[new_messages])

    def _NH_SIPAccountGotPendingWatcher(self, notification):
        dialog = PendingWatcherDialog(notification.sender, notification.data.uri, notification.data.display_name)
        dialog.finished.connect(self._SH_PendingWatcherDialogFinished)
        self.pending_watcher_dialogs.append(dialog)
        dialog.show()

    def _NH_BlinkSessionNewOutgoing(self, notification):
        self.search_box.clear()

    def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
        self.search_box.clear()

    def _NH_FileTransferNewIncoming(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)

    def _NH_FileTransferNewOutgoing(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
Exemplo n.º 7
0
class MainWindow(QMainWindow):
    groups = dict()
    typeQListWidgetHeader = 1000
    showHostsInGroups = False
    currentGroupName = None  # used to simple detect currently selected group to show menu

    def __init__(self):
        super(MainWindow, self).__init__()
        self.config = Config()
        self.db = Database(self.config.getConnectionString())

        cryptoKey = self.getCryptoKey()
        self.hosts = Hosts(self.db, cryptoKey)

        # menu used for each host
        self.hostMenu = QMenu()
        self.editAction = QAction(QIcon(':/ico/edit.svg'), "Edit", self.hostMenu)
        self.editAction.triggered.connect(self.editHost)
        self.hostMenu.addAction(self.editAction)

        # menu used for headers of groups
        self.groupsHeaderMenu = QMenu()
        self.editGroupAction = QAction(QIcon(':/ico/edit.svg'), "Edit group", self.groupsHeaderMenu)
        self.editGroupAction.triggered.connect(self.editGroup)
        self.deleteGroupAction = QAction(QIcon(':/ico/remove.svg'), "Delete group", self.groupsHeaderMenu)
        self.deleteGroupAction.triggered.connect(self.deleteGroup)
        self.groupsHeaderMenu.addAction(self.editGroupAction)
        self.groupsHeaderMenu.addAction(self.deleteGroupAction)

        self.duplicateAction = QAction(QIcon(':/ico/copy.svg'), "Duplicate", self.hostMenu)
        self.duplicateAction.triggered.connect(self.duplicateHost)
        self.hostMenu.addAction(self.duplicateAction)

        # todo: confirm for delete action
        self.deleteAction = QAction(QIcon(':/ico/remove.svg'), "Delete", self.hostMenu)
        self.deleteAction.triggered.connect(self.deleteHost)
        self.hostMenu.addAction(self.deleteAction)

        self.connectFramelessMenu = actions.generateScreenChoseMenu(self.hostMenu, self.connectFrameless,
                                                                    ':/ico/frameless.svg', "Connect frameless")
        self.hostMenu.addMenu(self.connectFramelessMenu)

        self.assignGroupAction = QAction("Assign group", self.hostMenu)
        self.assignGroupAction.triggered.connect(self.assignGroup)
        self.hostMenu.addAction(self.assignGroupAction)

        # setup main window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # when top level changed, we changing dock title bar
        self.dockWidgetTileBar = DockWidgetTitleBar()
        self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)
        self.ui.hostsDock.topLevelChanged.connect(self.dockLevelChanged)

        # set global menu
        self.globalMenu = QMenu()
        self.globalMenu.addAction(QIcon(':/ico/add.svg'), 'Add host', self.addHost)

        # groups menu
        self.groupsMenu = QMenu("Groups")
        self.groupsMenu.aboutToShow.connect(self.setGroupsMenu)
        self.globalMenu.addMenu(self.groupsMenu)

        # disable menu indicator
        self.ui.menu.setStyleSheet("QPushButton::menu-indicator {image: none;}")
        self.positionMenu = QMenu("Dock position")
        self.positionMenu.addAction("Left", lambda: self.setDockPosition(Qt.LeftDockWidgetArea))
        self.positionMenu.addAction("Right", lambda: self.setDockPosition(Qt.RightDockWidgetArea))
        self.positionMenu.addAction("Float", self.setDockFloat)
        self.globalMenu.addMenu(self.positionMenu)
        self.globalMenu.addAction('Change tray icon visibility', self.changeTrayIconVisibility)
        self.globalMenu.addAction('Settings', self.showSettings)
        self.globalMenu.addAction('Quit', self.close)
        self.ui.menu.setMenu(self.globalMenu)

        # set events on hosts list
        self.ui.hostsList.itemDoubleClicked.connect(self.slotConnectHost)
        self.ui.hostsList.itemClicked.connect(self.slotShowHost)
        self.ui.hostsList.customContextMenuRequested.connect(self.slotShowHostContextMenu)

        # set tab widget
        self.tabWidget = MyTabWidget()
        self.setCentralWidget(self.tabWidget)
        self.tabWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tabWidget.customContextMenuRequested.connect(self.showCentralWidgetContextMenu)

        # set tray icon
        self.tray = QSystemTrayIcon(QIcon(":/ico/myrdp.svg"))
        self.tray.activated.connect(self.trayActivated)

        self.trayMenu = QMenu()
        self.trayMenu.addAction("Hide tray icon", self.changeTrayIconVisibility)
        self.connectHostMenuTray = ConnectHostMenu(self.hosts)
        self.connectHostMenuTray.triggered.connect(self.connectHostFromTrayMenu)
        self.trayMenu.addMenu(self.connectHostMenuTray)
        self.trayMenu.addAction("Quit", self.close)

        self.tray.setContextMenu(self.trayMenu)
        self.restoreSettings()
        # host list
        self.ui.filter.textChanged.connect(self.setHostList)
        self.setHostList()

    def getCryptoKey(self, passphrase=None):
        try:
            return self.config.getPrivateKey(passphrase)
        except ValueError:
            passwordDialog = PasswordDialog()
            retCode = passwordDialog.exec_()
            if retCode == QtGui.QDialog.Accepted:
                return self.getCryptoKey(passwordDialog.getPassword())
            else:
                raise SystemError("Password required")

    def showSettings(self):
        settingsWidget = self.findChild(QWidget, "settings")
        if settingsWidget is None:
            self.settingsWidget = SettingsPage()
            self.settingsWidget.setObjectName("settings")
            self.tabWidget.insertTab(0, self.settingsWidget, QIcon(":/ico/settings.svg"), 'Settings')

        index = self.tabWidget.indexOf(self.settingsWidget)
        self.tabWidget.setCurrentIndex(index)

    def connectHostFromMenu(self, action):
        self.connectHost(unicode(action.text()))

    def connectHostFromTrayMenu(self, action):
        tabPage = self.connectHost(unicode(action.text()))
        if not self.isVisible():
            self.tabWidget.setDetached(True, tabPage)

    def trayActivated(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.activateWindow()

    def changeTrayIconVisibility(self):
        if self.tray.isVisible():
            self.tray.hide()
            if not self.isVisible():
                self.show()
        else:
            self.tray.show()

    def refreshGroups(self):
        groupList = self.hosts.getGroupsList()
        for group in groupList:
            if group not in self.groups:
                # add new groups as visible
                self.groups[group] = True

        # remove not existing groups
        keysToDelete = set(self.groups.keys()) - set(groupList)
        for key in keysToDelete:
            self.groups.pop(key)

    def assignGroup(self):
        groups = self.hosts.getGroupsList()
        assignGroupDialog = AssignGroupDialog(groups)
        groupToAssign = assignGroupDialog.assign()
        if groupToAssign is not False:  # None could be used to unassign the group
            groupToAssign = None if groupToAssign.isEmpty() else unicode(groupToAssign)
            for hostName in self.getSelectedHosts():
                self.hosts.assignGroup(hostName, groupToAssign)
            self.db.tryCommit()
            self.setHostList()

    def setGroupsMenu(self):
        self.groupsMenu.clear()
        addGroupAction = self.groupsMenu.addAction('Add group')
        addGroupAction.triggered.connect(self.addGroup)

        deleteGroupAction = self.groupsMenu.addAction('Delete group')
        deleteGroupAction.triggered.connect(self.showDeleteGroupDialog)

        showHostsInGroupsAction = self.groupsMenu.addAction('Show host list in groups')
        showHostsInGroupsAction.triggered.connect(self.changeHostListView)
        showHostsInGroupsAction.setCheckable(True)
        showHostsInGroupsAction.setChecked(self.showHostsInGroups)

        self.groupsMenu.addSeparator()
        for group, checked in self.groups.items():
            action = QAction(group, self.groupsMenu)
            action.setCheckable(True)
            action.setChecked(checked)
            action.triggered.connect(self.groupsVisibilityChanged)
            self.groupsMenu.addAction(action)

    def addGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.add()
        self._processHostSubmit(resp)

    def groupsVisibilityChanged(self, checked):
        currentGroup = unicode(self.sender().text())
        self.groups[currentGroup] = checked
        self.setHostList()

    def setDockPosition(self, dockWidgetArea):
        if self.ui.hostsDock.isFloating():
            self.ui.hostsDock.setFloating(False)
        self.addDockWidget(dockWidgetArea, self.ui.hostsDock)

    def setDockFloat(self):
        if self.ui.hostsDock.isFloating():
            return
        # default title bar must be set before is float because sometimes window make strange crash
        self.ui.hostsDock.setTitleBarWidget(None)
        self.ui.hostsDock.setFloating(True)

    def dockLevelChanged(self, isFloating):
        if isFloating:
            # changing title bar widget if is not none, probably true will be only once on start with saved float state
            if self.ui.hostsDock.titleBarWidget():
                self.ui.hostsDock.setTitleBarWidget(None)
        else:
            self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)

    def showFramelessWidget(self):
        self.t.show()
        self.t.setGeometry(self.frameGeometry())

    def getCurrentHostListItemName(self):
        return self.ui.hostsList.currentItem().text()

    def getSelectedHosts(self):
        return [host.text() for host in self.ui.hostsList.selectedItems()]

    def findHostItemByName(self, name):
        result = self.ui.hostsList.findItems(name, Qt.MatchExactly)
        resultLen = len(result)
        if resultLen != 1:  # should be only one host
            logger.error("Host not found. Got %d results" % resultLen)
        return result[0]

    def showCentralWidgetContextMenu(self, pos):
        menu = QMenu()
        title = self.ui.hostsDock.windowTitle()

        hostsDockAction = menu.addAction(title)
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.ui.hostsDock.isVisible())
        hostsDockAction.triggered.connect(self.changeHostsDockWidgetVisibility)

        hostsDockAction = menu.addAction("Tray icon")
        hostsDockAction.setCheckable(True)
        hostsDockAction.setChecked(self.tray.isVisible())
        hostsDockAction.triggered.connect(self.changeTrayIconVisibility)

        connectHostMenuTray = ConnectHostMenu(self.hosts, "Connect")
        connectHostMenuTray.triggered.connect(self.connectHostFromMenu)
        menu.addMenu(connectHostMenuTray)

        menu.exec_(self.tabWidget.mapToGlobal(pos))

    def changeHostListView(self, checked):
        self.showHostsInGroups = checked
        self.setHostList()

    def changeHostsDockWidgetVisibility(self):
        isVisible = self.ui.hostsDock.isVisible()
        self.ui.hostsDock.setVisible(not isVisible)

    def isHostListHeader(self, item):
        if not item or item.type() == self.typeQListWidgetHeader:
            return True
        return False

    def slotShowHostContextMenu(self, pos):
        def changeMenusVisibility(isEnabled):
            self.connectFramelessMenu.setEnabled(isEnabled)
            self.editAction.setEnabled(isEnabled)
            self.duplicateAction.setEnabled(isEnabled)

        # ignore context menu for group headers
        item = self.ui.hostsList.itemAt(pos)

        if self.isHostListHeader(item):
            item = self.ui.hostsList.itemAt(pos)
            widgetItem = self.ui.hostsList.itemWidget(item)
            if widgetItem:
                self.currentGroupName = widgetItem.text()  # yea I'm so dirty
                if self.currentGroupName != unassignedGroupName:
                    self.groupsHeaderMenu.exec_(self.ui.hostsList.mapToGlobal(pos))
            return

        if len(self.ui.hostsList.selectedItems()) == 1:  # single menu
            changeMenusVisibility(True)
        else:
            changeMenusVisibility(False)

        self.hostMenu.exec_(self.ui.hostsList.mapToGlobal(pos))

    def _processHostSubmit(self, resp):
        if resp["code"]:
            self.setHostList()
        hostName = resp.get("name")
        if hostName:
            hostItem = self.findHostItemByName(hostName)
            self.slotConnectHost(hostItem)

    def addHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        self._processHostSubmit(hostDialog.add())

    def editHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.edit(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def editGroup(self):
        groupConfigDialog = GroupConfigDialog(self.hosts.groups)
        resp = groupConfigDialog.edit(self.currentGroupName)
        self._processHostSubmit(resp)

    def deleteGroup(self):
        retCode = self.showOkCancelMessageBox("Do you want to remove selected group? All assigned hosts "
                                              "to this group will be unassigned.",
                                              "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        self.hosts.deleteGroup(self.currentGroupName)
        self.setHostList()

    def showDeleteGroupDialog(self):
        deleteGroupDialog = DeleteGroupDialog(self.hosts)
        deleteGroupDialog.deleteGroup()
        self.setHostList()

    def duplicateHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.duplicate(self.getCurrentHostListItemName())
        self._processHostSubmit(resp)

    def deleteHost(self):
        retCode = self.showOkCancelMessageBox("Do you want to remove selected hosts?",
                                              "Confirmation")
        if retCode == QMessageBox.Cancel:
            return

        for host in self.getSelectedHosts():
            self.hosts.delete(host)
        self.setHostList()

    def connectFrameless(self, screenIndex=None):
        self.connectHost(self.getCurrentHostListItemName(), frameless=True, screenIndex=screenIndex)

    # Fix to release keyboard from QX11EmbedContainer, when we leave widget through wm border
    def leaveEvent(self, event):
        keyG = QWidget.keyboardGrabber()
        if keyG is not None:
            keyG.releaseKeyboard()
        event.accept()  # needed?

    def setHostList(self):
        """ set hosts list in list view """
        self.ui.hostsList.clear()
        self.refreshGroups()
        hostFilter = self.ui.filter.text()
        if self.showHostsInGroups:
            self.showHostListInGroups(hostFilter)
        else:
            self.showHostList(hostFilter)

    def showHostList(self, hostFilter):
        groupFilter = [group for group, visibility in self.groups.items() if visibility]
        hosts = self.hosts.getHostsListByHostNameAndGroup(hostFilter, groupFilter)
        self.ui.hostsList.addItems(hosts)

    def showHostListInGroups(self, hostFilter):
        hosts = self.hosts.getGroupedHostNames(hostFilter)
        for group, hostsList in hosts.items():
            if self.groups.get(group, True):
                if group is None:
                    group = unassignedGroupName
                groupHeader = QtGui.QListWidgetItem(type=self.typeQListWidgetHeader)
                groupLabel = QtGui.QLabel(unicode(group))
                groupLabel.setProperty('class', 'group-title')
                self.ui.hostsList.addItem(groupHeader)
                self.ui.hostsList.setItemWidget(groupHeader, groupLabel)
                self.ui.hostsList.addItems(hostsList)

    def slotShowHost(self, item):
        # on one click we activating tab and showing options
        self.tabWidget.activateTab(item)

    def slotConnectHost(self, item):
        if self.isHostListHeader(item):
            return
        self.connectHost(unicode(item.text()))

    def connectHost(self, hostId, frameless=False, screenIndex=None):
        hostId = unicode(hostId)  # sometimes hostId comes as QString
        tabPage = self.tabWidget.createTab(hostId)
        tabPage.reconnectionNeeded.connect(self.connectHost)

        if frameless:
            self.tabWidget.detachFrameless(tabPage, screenIndex)

        try:
            execCmd, opts = self.getCmd(tabPage, hostId)
        except LookupError:
            logger.error(u"Host {} not found.".format(hostId))
            return

        ProcessManager.start(hostId, tabPage, execCmd, opts)
        return tabPage

    def getCmd(self, tabPage, hostName):
        host = self.hosts.get(hostName)

        # set tabPage widget
        width, height = tabPage.setSizeAndGetCurrent()
        # 1et widget winId to embed rdesktop
        winId = tabPage.x11.winId()

        # set remote desktop client, at this time works only with freerdp
        remoteClientType, remoteClientOptions = self.config.getRdpClient()
        remoteClient = ClientFactory(remoteClientType, **remoteClientOptions)
        remoteClient.setWindowParameters(winId, width, height)
        remoteClient.setUserAndPassword(host.user, host.password)
        remoteClient.setAddress(host.address)
        return remoteClient.getComposedCommand()

    def saveSettings(self):
        self.config.setValue("geometry", self.saveGeometry())
        self.config.setValue("windowState", self.saveState())
        self.config.setValue('trayIconVisibility', self.tray.isVisible())
        self.config.setValue('mainWindowVisibility', self.isVisible())
        self.config.setValue('groups', self.groups)
        self.config.setValue('showHostsInGroups', self.showHostsInGroups)

    def restoreSettings(self):
        try:
            self.restoreGeometry(self.config.getValue("geometry").toByteArray())
            self.restoreState(self.config.getValue("windowState").toByteArray())
        except Exception:
            logger.debug("No settings to restore")

        # restore tray icon state
        trayIconVisibility = self.config.getValue('trayIconVisibility', "true").toBool()
        self.tray.setVisible(trayIconVisibility)

        self.showHostsInGroups = self.config.getValue('showHostsInGroups', 'false').toBool()

        if self.tray.isVisible():
            mainWindowVisibility = self.config.getValue('mainWindowVisibility', "true").toBool()
            self.setVisible(mainWindowVisibility)
        else:  # it tray icon is not visible, always show main window
            self.show()

        self.groups = {unicode(k): v for k, v in self.config.getValue('groups', {}).toPyObject().items()}

    def closeEvent(self, event):
        if not ProcessManager.hasActiveProcess:
            self.saveSettings()
            QCoreApplication.exit()
            return

        ret = self.showOkCancelMessageBox("Are you sure do you want to quit?",
                                          "Exit confirmation")
        if ret == QMessageBox.Cancel:
            event.ignore()
            return

        self.saveSettings()
        ProcessManager.killemall()
        event.accept()
        QCoreApplication.exit()

    def showOkCancelMessageBox(self, messageBoxText, windowTitle):
        msgBox = QMessageBox(self, text=messageBoxText)
        msgBox.setWindowTitle(windowTitle)
        msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        msgBox.setIcon(QMessageBox.Question)
        return msgBox.exec_()
Exemplo n.º 8
0
class Qt4SysTrayIcon:
    def __init__( self ):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config
        self.decode = None

        if len( sys.argv ) > 1:
            if not self.config.set_current_profile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s"
                               %sys.argv[1], self)

        self.qapp = qt4tools.create_qapplication(self.config.APP_NAME)
        translator = qt4tools.get_translator()
        self.qapp.installTranslator(translator)
        self.qapp.setQuitOnLastWindowClosed(False)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(_('Profile: "%s"') % self.config.get_profile_name())
        qt4tools.set_font_bold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()

        self.btnDecode = self.contextMenu.addAction(icon.VIEW_SNAPSHOT_LOG, _('decode paths'))
        self.btnDecode.setCheckable(True)
        self.btnDecode.setVisible(self.config.get_snapshots_mode() == 'ssh_encfs')
        QObject.connect(self.btnDecode, SIGNAL('toggled(bool)'), self.onBtnDecode)

        self.openLog = self.contextMenu.addAction(icon.VIEW_LAST_LOG, _('View Last Log'))
        QObject.connect(self.openLog, SIGNAL('triggered()'), self.onOpenLog)
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO, _('Start BackInTime'))
        QObject.connect(self.startBIT, SIGNAL('triggered()'), self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))

        self.first_error = self.config.is_notify_enabled()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        QObject.connect( self.timer, SIGNAL('timeout()'), self.update_info )

        self.ppid = os.getppid()

    def prepare_exit( self ):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run( self ):
        self.status_icon.show()
        self.timer.start( 500 )

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepare_exit()

    def update_info( self ):
        if not tools.is_process_alive( self.ppid ):
            self.prepare_exit()
            self.qapp.exit(0)
            return

        message = self.snapshots.get_take_snapshot_message()
        if message is None and self.last_message is None:
            message = ( 0, _('Working...') )

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                if self.decode:
                    message = (message[0], self.decode.log(message[1]))
                self.menuStatusMessage.setText('\n'.join(tools.wrap_line(message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                        ))
                self.status_icon.setToolTip(message[1])

        pg = progress.ProgressFile(self.config)
        if pg.isFileReadable():
            pg.load()
            percent = pg.get_int_value('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap, sourceRegion = QRegion(0, -14, 24, 6), flags = QWidget.RenderFlags(QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)) )
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)


    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')) )
        for key, txt in d:
            value = pg.get_str_value(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.get_current_profile()
        cmd = ['backintime-qt4',]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)

    def onOpenLog(self):
        dlg = logviewdialog.LogViewDialog(self, systray = True)
        dlg.decode = self.decode
        dlg.cb_decode.setChecked(self.btnDecode.isChecked())
        dlg.exec_()

    def onBtnDecode(self, checked):
        if checked:
            self.decode = encfstools.Decode(self.config)
            self.last_message = None
            self.update_info()
        else:
            self.decode = None
Exemplo n.º 9
0
class Qt4SysTrayIcon:
    def __init__(self):
        self.snapshots = snapshots.Snapshots()
        self.config = self.snapshots.config

        if len(sys.argv) > 1:
            if not self.config.set_current_profile(sys.argv[1]):
                logger.warning("Failed to change Profile_ID %s" % sys.argv[1],
                               self)

        self.qapp = qt4tools.create_qapplication(self.config.APP_NAME)

        import icon
        self.icon = icon
        self.qapp.setWindowIcon(icon.BIT_LOGO)

        self.status_icon = QSystemTrayIcon(icon.BIT_LOGO)
        #self.status_icon.actionCollection().clear()
        self.contextMenu = QMenu()

        self.menuProfileName = self.contextMenu.addAction(
            _('Profile: "%s"') % self.config.get_profile_name())
        qt4tools.set_font_bold(self.menuProfileName)
        self.contextMenu.addSeparator()

        self.menuStatusMessage = self.contextMenu.addAction(_('Done'))
        self.menuProgress = self.contextMenu.addAction('')
        self.menuProgress.setVisible(False)
        self.contextMenu.addSeparator()
        self.startBIT = self.contextMenu.addAction(icon.BIT_LOGO,
                                                   _('Start BackInTime'))
        QObject.connect(self.startBIT, SIGNAL('triggered()'), self.onStartBIT)
        self.status_icon.setContextMenu(self.contextMenu)

        self.pixmap = icon.BIT_LOGO.pixmap(24)
        self.progressBar = QProgressBar()
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.resize(24, 6)
        self.progressBar.render(self.pixmap,
                                sourceRegion=QRegion(0, -14, 24, 6),
                                flags=QWidget.RenderFlags(
                                    QWidget.DrawChildren))

        self.first_error = self.config.is_notify_enabled()
        self.popup = None
        self.last_message = None

        self.timer = QTimer()
        QObject.connect(self.timer, SIGNAL('timeout()'), self.update_info)

        self.ppid = os.getppid()

    def prepare_exit(self):
        self.timer.stop()

        if not self.status_icon is None:
            self.status_icon.hide()
            self.status_icon = None

        if not self.popup is None:
            self.popup.deleteLater()
            self.popup = None

        self.qapp.processEvents()

    def run(self):
        self.status_icon.show()
        self.timer.start(500)

        logger.info("[qt4systrayicon] begin loop", self)

        self.qapp.exec_()

        logger.info("[qt4systrayicon] end loop", self)

        self.prepare_exit()

    def update_info(self):
        if not tools.is_process_alive(self.ppid):
            self.prepare_exit()
            self.qapp.exit(0)
            return

        message = self.snapshots.get_take_snapshot_message()
        if message is None and self.last_message is None:
            message = (0, _('Working...'))

        if not message is None:
            if message != self.last_message:
                self.last_message = message
                self.menuStatusMessage.setText('\n'.join(tools.wrap_line(self.last_message[1],\
                                                                         size = 80,\
                                                                         delimiters = '',\
                                                                         new_line_indicator = '') \
                                                                        ))
                self.status_icon.setToolTip(self.last_message[1])

        pg = progress.ProgressFile(self.config)
        if pg.isFileReadable():
            pg.load()
            percent = pg.get_int_value('percent')
            if percent != self.progressBar.value():
                self.progressBar.setValue(percent)
                self.progressBar.render(self.pixmap,
                                        sourceRegion=QRegion(0, -14, 24, 6),
                                        flags=QWidget.RenderFlags(
                                            QWidget.DrawChildren))
                self.status_icon.setIcon(QIcon(self.pixmap))

            self.menuProgress.setText(' | '.join(self.getMenuProgress(pg)))
            self.menuProgress.setVisible(True)
        else:
            self.status_icon.setIcon(self.icon.BIT_LOGO)
            self.menuProgress.setVisible(False)

    def getMenuProgress(self, pg):
        d = (('sent',   _('Sent:')), \
             ('speed',  _('Speed:')),\
             ('eta',    _('ETA:')) )
        for key, txt in d:
            value = pg.get_str_value(key, '')
            if not value:
                continue
            yield txt + ' ' + value

    def onStartBIT(self):
        profileID = self.config.get_current_profile()
        cmd = [
            'backintime-qt4',
        ]
        if not profileID == '1':
            cmd += ['--profile-id', profileID]
        proc = subprocess.Popen(cmd)
Exemplo n.º 10
0
class MainWindow(base_class, ui_class):
    implements(IObserver)

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.saved_account_state = None

        notification_center = NotificationCenter()
        notification_center.add_observer(self, name='SIPApplicationWillStart')
        notification_center.add_observer(self, name='SIPApplicationDidStart')
        notification_center.add_observer(self, name='SIPAccountGotMessageSummary')
        notification_center.add_observer(self, name='SIPAccountGotPendingWatcher')
        notification_center.add_observer(self, name='BlinkSessionNewOutgoing')
        notification_center.add_observer(self, name='BlinkSessionDidReinitializeForOutgoing')
        notification_center.add_observer(self, name='BlinkFileTransferNewIncoming')
        notification_center.add_observer(self, name='BlinkFileTransferNewOutgoing')
        notification_center.add_observer(self, name='DocumentSharingFileTransferCompleted')
        notification_center.add_observer(self, sender=AccountManager())

        icon_manager = IconManager()

        self.pending_watcher_dialogs = []

        self.mwi_icons = [QIcon(Resources.get('icons/mwi-%d.png' % i)) for i in xrange(0, 11)]
        self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png')))

        with Resources.directory:
            self.setupUi()

        self.setWindowTitle('Blink')
        self.setWindowIconText('Blink')

        geometry = QSettings().value("main_window/geometry")
        if geometry:
            self.restoreGeometry(geometry)

        self.default_icon_path = Resources.get('icons/default-avatar.png')
        self.default_icon = QIcon(self.default_icon_path)
        self.last_icon_directory = Path('~').normalized
        self.set_user_icon(icon_manager.get('avatar'))

        self.active_sessions_label.hide()
        self.enable_call_buttons(False)
        self.conference_button.setEnabled(False)
        self.hangup_all_button.setEnabled(False)
        self.sip_server_settings_action.setEnabled(False)
        self.search_for_people_action.setEnabled(False)
        self.history_on_server_action.setEnabled(False)
        self.main_view.setCurrentWidget(self.contacts_panel)
        self.contacts_view.setCurrentWidget(self.contact_list_panel)
        self.search_view.setCurrentWidget(self.search_list_panel)

        # System tray
        if QSystemTrayIcon.isSystemTrayAvailable():
            self.system_tray_icon = QSystemTrayIcon(QIcon(Resources.get('icons/blink.png')), self)
            self.system_tray_icon.activated.connect(self._SH_SystemTrayIconActivated)
            menu = QMenu(self)
            menu.addAction(QAction("Show", self, triggered=self._AH_SystemTrayShowWindow))
            menu.addAction(QAction(QIcon(Resources.get('icons/application-exit.png')), "Quit", self, triggered=self._AH_QuitActionTriggered))
            self.system_tray_icon.setContextMenu(menu)
            self.system_tray_icon.show()
        else:
            self.system_tray_icon = None

        # Accounts
        self.account_model = AccountModel(self)
        self.enabled_account_model = ActiveAccountModel(self.account_model, self)
        self.server_tools_account_model = ServerToolsAccountModel(self.account_model, self)
        self.identity.setModel(self.enabled_account_model)

        # Contacts
        self.contact_model = ContactModel(self)
        self.contact_search_model = ContactSearchModel(self.contact_model, self)
        self.contact_list.setModel(self.contact_model)
        self.search_list.setModel(self.contact_search_model)

        # Sessions (audio)
        self.session_model = AudioSessionModel(self)
        self.session_list.setModel(self.session_model)
        self.session_list.selectionModel().selectionChanged.connect(self._SH_SessionListSelectionChanged)

        # History
        self.history_manager = HistoryManager()

        # Windows, dialogs and panels
        self.about_panel = AboutPanel(self)
        self.conference_dialog = ConferenceDialog(self)
        self.contact_editor_dialog = ContactEditorDialog(self)
        self.google_contacts_dialog = GoogleContactsDialog(self)
        self.filetransfer_window = FileTransferWindow()
        self.preferences_window = PreferencesWindow(self.account_model, None)
        self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)

        self.documents_window = DocumentsWindow()

        # Signals
        self.account_state.stateChanged.connect(self._SH_AccountStateChanged)
        self.account_state.clicked.connect(self._SH_AccountStateClicked)
        self.activity_note.editingFinished.connect(self._SH_ActivityNoteEditingFinished)
        self.add_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.add_search_contact_button.clicked.connect(self._SH_AddContactButtonClicked)
        self.audio_call_button.clicked.connect(self._SH_AudioCallButtonClicked)
        self.video_call_button.clicked.connect(self._SH_VideoCallButtonClicked)
        self.chat_session_button.clicked.connect(self._SH_ChatSessionButtonClicked)
        self.share_document_button.clicked.connect(self._SH_ShareDocumentButtonClicked)
        self.back_to_contacts_button.clicked.connect(self.search_box.clear) # this can be set in designer -Dan
        self.conference_button.makeConference.connect(self._SH_MakeConference)
        self.conference_button.breakConference.connect(self._SH_BreakConference)

        self.contact_list.selectionModel().selectionChanged.connect(self._SH_ContactListSelectionChanged)
        self.contact_model.itemsAdded.connect(self._SH_ContactModelAddedItems)
        self.contact_model.itemsRemoved.connect(self._SH_ContactModelRemovedItems)

        self.display_name.editingFinished.connect(self._SH_DisplayNameEditingFinished)
        self.hangup_all_button.clicked.connect(self._SH_HangupAllButtonClicked)

        self.identity.activated[int].connect(self._SH_IdentityChanged)
        self.identity.currentIndexChanged[int].connect(self._SH_IdentityCurrentIndexChanged)

        self.mute_button.clicked.connect(self._SH_MuteButtonClicked)

        self.search_box.textChanged.connect(self._SH_SearchBoxTextChanged)
        self.search_box.returnPressed.connect(self._SH_SearchBoxReturnPressed)
        self.search_box.shortcut.activated.connect(self.search_box.setFocus)

        self.search_list.selectionModel().selectionChanged.connect(self._SH_SearchListSelectionChanged)

        self.server_tools_account_model.rowsInserted.connect(self._SH_ServerToolsAccountModelChanged)
        self.server_tools_account_model.rowsRemoved.connect(self._SH_ServerToolsAccountModelChanged)

        self.session_model.sessionAdded.connect(self._SH_AudioSessionModelAddedSession)
        self.session_model.sessionRemoved.connect(self._SH_AudioSessionModelRemovedSession)
        self.session_model.structureChanged.connect(self._SH_AudioSessionModelChangedStructure)

        self.silent_button.clicked.connect(self._SH_SilentButtonClicked)
        self.switch_view_button.viewChanged.connect(self._SH_SwitchViewButtonChangedView)

        # Blink menu actions
        self.about_action.triggered.connect(self.about_panel.show)
        self.add_account_action.triggered.connect(self.preferences_window.show_add_account_dialog)
        self.manage_accounts_action.triggered.connect(self.preferences_window.show_for_accounts)
        self.help_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/help-qt.phtml')))
        self.preferences_action.triggered.connect(self.preferences_window.show)
        self.auto_accept_chat_action.triggered.connect(self._AH_AutoAcceptChatActionTriggered)
        self.received_messages_sound_action.triggered.connect(self._AH_ReceivedMessagesSoundActionTriggered)
        self.answering_machine_action.triggered.connect(self._AH_EnableAnsweringMachineActionTriggered)
        self.release_notes_action.triggered.connect(partial(QDesktopServices.openUrl, QUrl(u'http://icanblink.com/changelog-qt.phtml')))
        self.quit_action.triggered.connect(self._AH_QuitActionTriggered)

        # Call menu actions
        self.redial_action.triggered.connect(self._AH_RedialActionTriggered)
        self.join_conference_action.triggered.connect(self.conference_dialog.show)
        self.history_menu.aboutToShow.connect(self._SH_HistoryMenuAboutToShow)
        self.history_menu.triggered.connect(self._AH_HistoryMenuTriggered)
        self.output_devices_group.triggered.connect(self._AH_AudioOutputDeviceChanged)
        self.input_devices_group.triggered.connect(self._AH_AudioInputDeviceChanged)
        self.alert_devices_group.triggered.connect(self._AH_AudioAlertDeviceChanged)
        self.video_devices_group.triggered.connect(self._AH_VideoDeviceChanged)
        self.mute_action.triggered.connect(self._SH_MuteButtonClicked)
        self.silent_action.triggered.connect(self._SH_SilentButtonClicked)

        # Tools menu actions
        self.sip_server_settings_action.triggered.connect(self._AH_SIPServerSettings)
        self.search_for_people_action.triggered.connect(self._AH_SearchForPeople)
        self.history_on_server_action.triggered.connect(self._AH_HistoryOnServer)

        # Window menu actions
        self.chat_window_action.triggered.connect(self._AH_ChatWindowActionTriggered)
        self.transfers_window_action.triggered.connect(self._AH_TransfersWindowActionTriggered)
        self.logs_window_action.triggered.connect(self._AH_LogsWindowActionTriggered)
        self.received_files_window_action.triggered.connect(self._AH_ReceivedFilesWindowActionTriggered)
        self.screenshots_window_action.triggered.connect(self._AH_ScreenshotsWindowActionTriggered)
        self.documents_window_action.triggered.connect(self._AH_DocumentsWindowActionTriggered)

    def setupUi(self):
        super(MainWindow, self).setupUi(self)

        self.search_box.shortcut = QShortcut(self.search_box)
        self.search_box.shortcut.setKey('Ctrl+F')

        self.output_devices_group = QActionGroup(self)
        self.input_devices_group = QActionGroup(self)
        self.alert_devices_group = QActionGroup(self)
        self.video_devices_group = QActionGroup(self)

        self.request_screen_action = QAction('Request screen', self, triggered=self._AH_RequestScreenActionTriggered)
        self.share_my_screen_action = QAction('Share my screen', self, triggered=self._AH_ShareMyScreenActionTriggered)
        self.screen_sharing_button.addAction(self.request_screen_action)
        self.screen_sharing_button.addAction(self.share_my_screen_action)

        # adjust search box height depending on theme as the value set in designer isn't suited for all themes
        search_box = self.search_box
        option = QStyleOptionFrameV2()
        search_box.initStyleOption(option)
        frame_width = search_box.style().pixelMetric(QStyle.PM_DefaultFrameWidth, option, search_box)
        if frame_width < 4:
            search_box.setMinimumHeight(20 + 2*frame_width)

        # adjust the combo boxes for themes with too much padding (like the default theme on Ubuntu 10.04)
        option = QStyleOptionComboBox()
        self.identity.initStyleOption(option)
        wide_padding = self.identity.style().subControlRect(QStyle.CC_ComboBox, option, QStyle.SC_ComboBoxEditField, self.identity).height() < 10
        self.identity.setStyleSheet("""QComboBox { padding: 0px 4px 0px 4px; }""" if wide_padding else "")

    def closeEvent(self, event):
        QSettings().setValue("main_window/geometry", self.saveGeometry())
        super(MainWindow, self).closeEvent(event)
        self.about_panel.close()
        self.contact_editor_dialog.close()
        self.google_contacts_dialog.close()
        self.server_tools_window.close()
        for dialog in self.pending_watcher_dialogs[:]:
            dialog.close()

    def show(self):
        super(MainWindow, self).show()
        self.raise_()
        self.activateWindow()

    def set_user_icon(self, icon):
        self.account_state.setIcon(icon or self.default_icon)

    def enable_call_buttons(self, enabled):
        self.audio_call_button.setEnabled(enabled)
        self.video_call_button.setEnabled(enabled)
        self.chat_session_button.setEnabled(enabled)
        self.screen_sharing_button.setEnabled(enabled)
        self.share_document_button.setEnabled(enabled)

    def load_audio_devices(self):
        settings = SIPSimpleSettings()

        action = QAction(u'System default', self.output_devices_group)
        action.setData(u'system_default')
        self.output_device_menu.addAction(action)
        self.output_device_menu.addSeparator()
        for device in SIPApplication.engine.output_devices:
            action = QAction(device, self.output_devices_group)
            action.setData(device)
            self.output_device_menu.addAction(action)
        action = QAction(u'None', self.output_devices_group)
        action.setData(None)
        self.output_device_menu.addAction(action)
        for action in self.output_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.output_device == action.data():
                action.setChecked(True)

        action = QAction(u'System default', self.input_devices_group)
        action.setData(u'system_default')
        self.input_device_menu.addAction(action)
        self.input_device_menu.addSeparator()
        for device in SIPApplication.engine.input_devices:
            action = QAction(device, self.input_devices_group)
            action.setData(device)
            self.input_device_menu.addAction(action)
        action = QAction(u'None', self.input_devices_group)
        action.setData(None)
        self.input_device_menu.addAction(action)
        for action in self.input_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.input_device == action.data():
                action.setChecked(True)

        action = QAction(u'System default', self.alert_devices_group)
        action.setData(u'system_default')
        self.alert_device_menu.addAction(action)
        self.alert_device_menu.addSeparator()
        for device in SIPApplication.engine.output_devices:
            action = QAction(device, self.alert_devices_group)
            action.setData(device)
            self.alert_device_menu.addAction(action)
        action = QAction(u'None', self.alert_devices_group)
        action.setData(None)
        self.alert_device_menu.addAction(action)
        for action in self.alert_devices_group.actions():
            action.setCheckable(True)
            if settings.audio.alert_device == action.data():
                action.setChecked(True)

    def load_video_devices(self):
        settings = SIPSimpleSettings()

        action = QAction(u'System default', self.video_devices_group)
        action.setData(u'system_default')
        self.video_camera_menu.addAction(action)
        self.video_camera_menu.addSeparator()
        for device in SIPApplication.engine.video_devices:
            action = QAction(device, self.video_devices_group)
            action.setData(device)
            self.video_camera_menu.addAction(action)
        action = QAction(u'None', self.video_devices_group)
        action.setData(None)
        self.video_camera_menu.addAction(action)
        for action in self.video_devices_group.actions():
            action.setCheckable(True)
            if settings.video.device == action.data():
                action.setChecked(True)

    def _AH_AccountActionTriggered(self, action, enabled):
        account = action.data()
        account.enabled = enabled
        account.save()

    def _AH_AudioAlertDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.alert_device = action.data()
        settings.save()

    def _AH_AudioInputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.input_device = action.data()
        settings.save()

    def _AH_AudioOutputDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.audio.output_device = action.data()
        settings.save()

    def _AH_VideoDeviceChanged(self, action):
        settings = SIPSimpleSettings()
        settings.video.device = action.data()
        settings.save()

    def _AH_AutoAcceptChatActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.chat.auto_accept = checked
        settings.save()

    def _AH_ReceivedMessagesSoundActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.sounds.play_message_alerts = checked
        settings.save()

    def _AH_EnableAnsweringMachineActionTriggered(self, checked):
        settings = SIPSimpleSettings()
        settings.answering_machine.enabled = checked
        settings.save()

    def _AH_GoogleContactsActionTriggered(self):
        settings = SIPSimpleSettings()
        if settings.google_contacts.authorization_token is not None:
            settings.google_contacts.authorization_token = None
            settings.save()
            self.google_contacts_dialog.hide()
        else:
            self.google_contacts_dialog.open()

    def _AH_RedialActionTriggered(self):
        session_manager = SessionManager()
        if session_manager.last_dialed_uri is not None:
            contact, contact_uri = URIUtils.find_contact(session_manager.last_dialed_uri)
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')]) # TODO: remember used media types and redial with them. -Saul

    def _AH_SIPServerSettings(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_settings_page(account)

    def _AH_SearchForPeople(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_search_for_people_page(account)

    def _AH_HistoryOnServer(self, checked):
        account = self.identity.itemData(self.identity.currentIndex()).account
        account = account if account is not BonjourAccount() and account.server.settings_url else None
        self.server_tools_window.open_history_page(account)

    def _AH_ChatWindowActionTriggered(self, checked):
        blink = QApplication.instance()
        blink.chat_window.show()

    def _AH_TransfersWindowActionTriggered(self, checked):
        self.filetransfer_window.show()

    def _AH_LogsWindowActionTriggered(self, checked):
        directory = ApplicationData.get('logs')
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ReceivedFilesWindowActionTriggered(self, checked):
        settings = BlinkSettings()
        directory = settings.transfers_directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_ScreenshotsWindowActionTriggered(self, checked):
        settings = BlinkSettings()
        directory = settings.screenshots_directory.normalized
        makedirs(directory)
        QDesktopServices.openUrl(QUrl.fromLocalFile(directory))

    def _AH_DocumentsWindowActionTriggered(self, checked):
        self.documents_window.show()

    def _AH_VoicemailActionTriggered(self, action, checked):
        account = action.data()
        contact, contact_uri = URIUtils.find_contact(account.voicemail_uri, display_name='Voicemail')
        session_manager = SessionManager()
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account)

    def _SH_HistoryMenuAboutToShow(self):
        self.history_menu.clear()
        if self.history_manager.calls:
            for entry in reversed(self.history_manager.calls):
                action = self.history_menu.addAction(entry.icon, entry.text)
                action.entry = entry
                action.setToolTip(entry.uri)
        else:
            action = self.history_menu.addAction("Call history is empty")
            action.setEnabled(False)

    def _AH_HistoryMenuTriggered(self, action):
        account_manager = AccountManager()
        session_manager = SessionManager()
        try:
            account = account_manager.get_account(action.entry.account_id)
        except KeyError:
            account = None
        contact, contact_uri = URIUtils.find_contact(action.entry.uri)
        session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account) # TODO: memorize media type and use it? -Saul (not sure about history in/out -Dan)

    def _AH_SystemTrayShowWindow(self, checked):
        self.show()
        self.raise_()
        self.activateWindow()

    def _AH_QuitActionTriggered(self, checked):
        if self.system_tray_icon is not None:
            self.system_tray_icon.hide()
        QApplication.instance().quit()

    def _SH_AccountStateChanged(self):
        self.activity_note.setText(self.account_state.note)
        if self.account_state.state is AccountState.Invisible:
            self.activity_note.inactiveText = u'(invisible)'
            self.activity_note.setEnabled(False)
        else:
            if not self.activity_note.isEnabled():
                self.activity_note.inactiveText = u'Add an activity note here'
                self.activity_note.setEnabled(True)
        if not self.account_state.state.internal:
            self.saved_account_state = None
        blink_settings = BlinkSettings()
        blink_settings.presence.current_state = PresenceState(self.account_state.state, self.account_state.note)
        blink_settings.presence.state_history = [PresenceState(state, note) for state, note in self.account_state.history]
        blink_settings.save()

    def _SH_AccountStateClicked(self, checked):
        filename = QFileDialog.getOpenFileName(self, u'Select Icon', self.last_icon_directory, u"Images (*.png *.tiff *.jpg *.xmp *.svg)")
        if filename:
            self.last_icon_directory = os.path.dirname(filename)
            filename = filename if os.path.realpath(filename) != os.path.realpath(self.default_icon_path) else None
            blink_settings = BlinkSettings()
            icon_manager = IconManager()
            if filename is not None:
                icon = icon_manager.store_file('avatar', filename)
                if icon is not None:
                    blink_settings.presence.icon = IconDescriptor(FileURL(icon.filename), hashlib.sha1(icon.content).hexdigest())
                else:
                    icon_manager.remove('avatar')
                    blink_settings.presence.icon = None
            else:
                icon_manager.remove('avatar')
                blink_settings.presence.icon = None
            blink_settings.save()

    def _SH_ActivityNoteEditingFinished(self):
        self.activity_note.clearFocus()
        note = self.activity_note.text()
        if note != self.account_state.note:
            self.account_state.state.internal = False
            self.account_state.setState(self.account_state.state, note)

    def _SH_AddContactButtonClicked(self, clicked):
        self.contact_editor_dialog.open_for_add(self.search_box.text(), None)

    def _SH_AudioCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartAudioCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_VideoCallButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartVideoCall()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio'), StreamDescription('video')])

    def _SH_ChatSessionButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_StartChatSession()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('chat')], connect=False)

    def _AH_RequestScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_RequestScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='viewer'), StreamDescription('audio')])

    def _AH_ShareMyScreenActionTriggered(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list
        if list_view.detail_view.isVisible():
            list_view.detail_view._AH_ShareMyScreen()
        else:
            selected_indexes = list_view.selectionModel().selectedIndexes()
            if selected_indexes:
                contact = selected_indexes[0].data(Qt.UserRole)
                contact_uri = contact.uri
            else:
                contact, contact_uri = URIUtils.find_contact(self.search_box.text())
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('screen-sharing', mode='server'), StreamDescription('audio')])

    def _SH_ShareDocumentButtonClicked(self):
        list_view = self.contact_list if self.contacts_view.currentWidget() is self.contact_list_panel else self.search_list

        selected_indexes = list_view.selectionModel().selectedIndexes()
        if selected_indexes:
            contact = selected_indexes[0].data(Qt.UserRole)
            contact_uri = contact.uri
        else:
            contact, contact_uri = URIUtils.find_contact(self.search_box.text())

        filename = QFileDialog.getOpenFileName(self, "Share a Document", "", "OpenDocument Files (*.odt)")

        if filename:
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('document-sharing', filename=filename)])


    def _SH_BreakConference(self):
        active_session = self.session_list.selectionModel().selectedIndexes()[0].data(Qt.UserRole)
        self.session_model.breakConference(active_session.client_conference)

    def _SH_ContactListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.contact_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _SH_ContactModelAddedItems(self, items):
        if not self.search_box.text():
            return
        active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
        self.search_view.setCurrentWidget(active_widget)

    def _SH_ContactModelRemovedItems(self, items):
        if not self.search_box.text():
            return
        if any(type(item) is Contact for item in items) and self.contact_search_model.rowCount() == 0:
            self.search_box.clear() # check this. it is no longer be the correct behaviour as now contacts can be deleted from remote -Dan
        else:
            active_widget = self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel
            self.search_view.setCurrentWidget(active_widget)

    def _SH_DisplayNameEditingFinished(self):
        self.display_name.clearFocus()
        index = self.identity.currentIndex()
        if index != -1:
            name = self.display_name.text()
            account = self.identity.itemData(index).account
            account.display_name = name if name else None
            account.save()

    def _SH_HangupAllButtonClicked(self):
        for session in self.session_model.sessions:
            session.end()

    def _SH_IdentityChanged(self, index):
        account_manager = AccountManager()
        account_manager.default_account = self.identity.itemData(index).account

    def _SH_IdentityCurrentIndexChanged(self, index):
        if index != -1:
            account = self.identity.itemData(index).account
            self.display_name.setText(account.display_name or u'')
            self.display_name.setEnabled(True)
            self.activity_note.setEnabled(True)
            self.account_state.setEnabled(True)
        else:
            self.display_name.clear()
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)
            self.account_state.setState(AccountState.Invisible)
            self.saved_account_state = None

    def _SH_MakeConference(self):
        self.session_model.conferenceSessions([session for session in self.session_model.active_sessions if session.client_conference is None])

    def _SH_MuteButtonClicked(self, muted):
        settings = SIPSimpleSettings()
        settings.audio.muted = muted
        settings.save()

    def _SH_SearchBoxReturnPressed(self):
        address = self.search_box.text()
        if address:
            contact, contact_uri = URIUtils.find_contact(address)
            session_manager = SessionManager()
            session_manager.create_session(contact, contact_uri, [StreamDescription('audio')])

    def _SH_SearchBoxTextChanged(self, text):
        self.contact_search_model.setFilterFixedString(text)
        account_manager = AccountManager()
        if text:
            self.switch_view_button.view = SwitchViewButton.ContactView
            if self.contacts_view.currentWidget() is not self.search_panel:
                self.search_list.selectionModel().clearSelection()
            self.contacts_view.setCurrentWidget(self.search_panel)
            self.search_view.setCurrentWidget(self.search_list_panel if self.contact_search_model.rowCount() else self.not_found_panel)
            selected_items = self.search_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)
        else:
            self.contacts_view.setCurrentWidget(self.contact_list_panel)
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)==1 and type(selected_items[0].data(Qt.UserRole)) is Contact)
        self.search_list.detail_model.contact = None
        self.search_list.detail_view.hide()

    def _SH_SearchListSelectionChanged(self, selected, deselected):
        account_manager = AccountManager()
        selected_items = self.search_list.selectionModel().selectedIndexes()
        self.enable_call_buttons(account_manager.default_account is not None and len(selected_items)<=1)

    def _SH_ServerToolsAccountModelChanged(self, parent_index, start, end):
        server_tools_enabled = self.server_tools_account_model.rowCount() > 0
        self.sip_server_settings_action.setEnabled(server_tools_enabled)
        self.search_for_people_action.setEnabled(server_tools_enabled)
        self.history_on_server_action.setEnabled(server_tools_enabled)

    def _SH_SessionListSelectionChanged(self, selected, deselected):
        selected_indexes = selected.indexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in self.session_model.active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)

    def _SH_AudioSessionModelAddedSession(self, session_item):
        if len(session_item.blink_session.streams) == 1:
            self.switch_view_button.view = SwitchViewButton.SessionView

    def _SH_AudioSessionModelRemovedSession(self, session_item):
        if self.session_model.rowCount() == 0:
            self.switch_view_button.view = SwitchViewButton.ContactView

    def _SH_AudioSessionModelChangedStructure(self):
        active_sessions = self.session_model.active_sessions
        self.active_sessions_label.setText(u'There is 1 active call' if len(active_sessions)==1 else u'There are %d active calls' % len(active_sessions))
        self.active_sessions_label.setVisible(any(active_sessions))
        self.hangup_all_button.setEnabled(any(active_sessions))
        selected_indexes = self.session_list.selectionModel().selectedIndexes()
        active_session = selected_indexes[0].data(Qt.UserRole) if selected_indexes else Null
        if active_session.client_conference:
            self.conference_button.setEnabled(True)
            self.conference_button.setChecked(True)
        else:
            self.conference_button.setEnabled(len([session for session in active_sessions if session.client_conference is None]) > 1)
            self.conference_button.setChecked(False)
        if active_sessions:
            if self.account_state.state is not AccountState.Invisible:
                if self.saved_account_state is None:
                    self.saved_account_state = self.account_state.state, self.activity_note.text()
                self.account_state.setState(AccountState.Busy.Internal, note=u'On the phone')
        elif self.saved_account_state is not None:
            state, note = self.saved_account_state
            self.saved_account_state = None
            self.account_state.setState(state, note)

    def _SH_SilentButtonClicked(self, silent):
        settings = SIPSimpleSettings()
        settings.audio.silent = silent
        settings.save()

    def _SH_SwitchViewButtonChangedView(self, view):
        self.main_view.setCurrentWidget(self.contacts_panel if view is SwitchViewButton.ContactView else self.sessions_panel)

    def _SH_PendingWatcherDialogFinished(self, result):
        self.pending_watcher_dialogs.remove(self.sender())

    def _SH_SystemTrayIconActivated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            self.show()
            self.raise_()
            self.activateWindow()

    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    def _NH_SIPApplicationWillStart(self, notification):
        account_manager = AccountManager()
        settings = SIPSimpleSettings()
        self.silent_action.setChecked(settings.audio.silent)
        self.silent_button.setChecked(settings.audio.silent)
        self.answering_machine_action.setChecked(settings.answering_machine.enabled)
        self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
        self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
        if settings.google_contacts.authorization_token is None:
            self.google_contacts_action.setText(u'Enable &Google Contacts...')
        else:
            self.google_contacts_action.setText(u'Disable &Google Contacts')
        self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered)
        if not any(account.enabled for account in account_manager.iter_accounts()):
            self.display_name.setEnabled(False)
            self.activity_note.setEnabled(False)
            self.account_state.setEnabled(False)

    def _NH_SIPApplicationDidStart(self, notification):
        self.load_audio_devices()
        self.load_video_devices()
        notification.center.add_observer(self, name='CFGSettingsObjectDidChange')
        notification.center.add_observer(self, name='AudioDevicesDidChange')
        blink_settings = BlinkSettings()
        self.account_state.history = [(item.state, item.note) for item in blink_settings.presence.state_history]
        state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
        self.account_state.setState(state, blink_settings.presence.current_state.note)

    def _NH_AudioDevicesDidChange(self, notification):
        for action in self.output_device_menu.actions():
            self.output_devices_group.removeAction(action)
            self.output_device_menu.removeAction(action)
        for action in self.input_device_menu.actions():
            self.input_devices_group.removeAction(action)
            self.input_device_menu.removeAction(action)
        for action in self.alert_device_menu.actions():
            self.alert_devices_group.removeAction(action)
            self.alert_device_menu.removeAction(action)
        if self.session_model.active_sessions:
            old_devices = set(notification.data.old_devices)
            new_devices = set(notification.data.new_devices)
            added_devices = new_devices - old_devices
            if added_devices:
                new_device = added_devices.pop()
                settings = SIPSimpleSettings()
                settings.audio.input_device = new_device
                settings.audio.output_device = new_device
                settings.save()
        self.load_audio_devices()

    def _NH_CFGSettingsObjectDidChange(self, notification):
        settings = SIPSimpleSettings()
        blink_settings = BlinkSettings()
        icon_manager = IconManager()
        if notification.sender is settings:
            if 'audio.muted' in notification.data.modified:
                self.mute_action.setChecked(settings.audio.muted)
                self.mute_button.setChecked(settings.audio.muted)
            if 'audio.silent' in notification.data.modified:
                self.silent_action.setChecked(settings.audio.silent)
                self.silent_button.setChecked(settings.audio.silent)
            if 'audio.output_device' in notification.data.modified:
                action = (action for action in self.output_devices_group.actions() if action.data() == settings.audio.output_device).next()
                action.setChecked(True)
            if 'audio.input_device' in notification.data.modified:
                action = (action for action in self.input_devices_group.actions() if action.data() == settings.audio.input_device).next()
                action.setChecked(True)
            if 'audio.alert_device' in notification.data.modified:
                action = (action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device).next()
                action.setChecked(True)
            if 'video.device' in notification.data.modified:
                action = (action for action in self.video_devices_group.actions() if action.data() == settings.video.device).next()
                action.setChecked(True)
            if 'answering_machine.enabled' in notification.data.modified:
                self.answering_machine_action.setChecked(settings.answering_machine.enabled)
            if 'chat.auto_accept' in notification.data.modified:
                self.auto_accept_chat_action.setChecked(settings.chat.auto_accept)
            if 'sounds.play_message_alerts' in notification.data.modified:
                self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts)
            if 'google_contacts.authorization_token' in notification.data.modified:
                authorization_token = notification.sender.google_contacts.authorization_token
                if authorization_token is None:
                    self.google_contacts_action.setText(u'Enable &Google Contacts...')
                else:
                    self.google_contacts_action.setText(u'Disable &Google Contacts')
                if authorization_token is InvalidToken:
                    self.google_contacts_dialog.open_for_incorrect_password()
        elif notification.sender is blink_settings:
            if 'presence.current_state' in notification.data.modified:
                state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available)
                self.account_state.setState(state, blink_settings.presence.current_state.note)
            if 'presence.icon' in notification.data.modified:
                self.set_user_icon(icon_manager.get('avatar'))
            if 'presence.offline_note' in notification.data.modified:
                # TODO: set offline note -Saul
                pass
        elif isinstance(notification.sender, (Account, BonjourAccount)):
            account_manager = AccountManager()
            account = notification.sender
            if 'enabled' in notification.data.modified:
                action = (action for action in self.accounts_menu.actions() if action.data() is account).next()
                action.setChecked(account.enabled)
            if 'display_name' in notification.data.modified and account is account_manager.default_account:
                self.display_name.setText(account.display_name or u'')
            if set(['enabled', 'message_summary.enabled', 'message_summary.voicemail_uri']).intersection(notification.data.modified):
                action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
                action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
                action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)

    def _NH_SIPAccountManagerDidAddAccount(self, notification):
        account = notification.data.account
        action = QAction(account.id if account is not BonjourAccount() else u'Bonjour', None)
        action.setEnabled(True if account is not BonjourAccount() else BonjourAccount.mdns_available)
        action.setCheckable(True)
        action.setChecked(account.enabled)
        action.setData(account)
        action.triggered.connect(partial(self._AH_AccountActionTriggered, action))
        self.accounts_menu.addAction(action)
        action = QAction(self.mwi_icons[0], account.id, None)
        action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
        action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
        action.setData(account)
        action.triggered.connect(partial(self._AH_VoicemailActionTriggered, action))
        self.voicemail_menu.addAction(action)

    def _NH_SIPAccountManagerDidRemoveAccount(self, notification):
        account = notification.data.account
        action = (action for action in self.accounts_menu.actions() if action.data() is account).next()
        self.accounts_menu.removeAction(action)
        action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
        self.voicemail_menu.removeAction(action)

    def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification):
        if notification.data.account is None:
            self.enable_call_buttons(False)
        else:
            selected_items = self.contact_list.selectionModel().selectedIndexes()
            self.enable_call_buttons(len(selected_items)==1 and isinstance(selected_items[0].data(Qt.UserRole), Contact))

    def _NH_SIPAccountGotMessageSummary(self, notification):
        account = notification.sender
        summary = notification.data.message_summary
        action = (action for action in self.voicemail_menu.actions() if action.data() is account).next()
        action.setEnabled(account.voicemail_uri is not None)
        if summary.messages_waiting:
            try:
                new_messages = limit(int(summary.summaries['voice-message']['new_messages']), min=0, max=11)
            except (KeyError, ValueError):
                new_messages = 0
        else:
            new_messages = 0
        action.setIcon(self.mwi_icons[new_messages])

    def _NH_SIPAccountGotPendingWatcher(self, notification):
        dialog = PendingWatcherDialog(notification.sender, notification.data.uri, notification.data.display_name)
        dialog.finished.connect(self._SH_PendingWatcherDialogFinished)
        self.pending_watcher_dialogs.append(dialog)
        dialog.show()

    def _NH_BlinkSessionNewOutgoing(self, notification):
        self.search_box.clear()

    def _NH_BlinkSessionDidReinitializeForOutgoing(self, notification):
        self.search_box.clear()

    def _NH_BlinkFileTransferNewIncoming(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)

    def _NH_BlinkFileTransferNewOutgoing(self, notification):
        self.filetransfer_window.show(activate=QApplication.activeWindow() is not None)
Exemplo n.º 11
0
class MainWindow(QMainWindow):
    def __init__(self, config):
        super(MainWindow, self).__init__()
        self.config = Config(config)
        db = Database(self.config.getConnectionString())
        db.create()
        self.hosts = Hosts(db)

        # menu used for each host
        self.hostMenu = QMenu()
        self.hostMenu.addAction(QIcon(":/ico/edit.svg"), "Edit", self.editHost)
        self.hostMenu.addAction(QIcon(":/ico/remove.svg"), "Delete", self.deleteHost)
        actions.addActionWithScreenChose(
            self.hostMenu, self.connectFrameless, ":/ico/frameless.svg", "Connect frameless"
        )

        # setup main window
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # when top level changed, we changing dock title bar
        self.dockWidgetTileBar = DockWidgetTitleBar()
        self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)
        self.ui.hostsDock.topLevelChanged.connect(self.dockLevelChanged)

        # set global menu
        self.globalMenu = QMenu()
        self.globalMenu.addAction(QIcon(":/ico/add.svg"), "Add host", self.addHost)
        # disable menu indicator
        self.ui.menu.setStyleSheet("QPushButton::menu-indicator {image: none;}")
        self.positionMenu = QMenu("Dock position")
        self.positionMenu.addAction("Left", lambda: self.setDockPosition(Qt.LeftDockWidgetArea))
        self.positionMenu.addAction("Right", lambda: self.setDockPosition(Qt.RightDockWidgetArea))
        self.positionMenu.addAction("Float", self.setDockFloat)
        self.globalMenu.addMenu(self.positionMenu)
        self.globalMenu.addAction("Change tray icon visibility", self.changeTrayIconVisibility)
        self.globalMenu.addAction("Quit", self.close)
        self.ui.menu.setMenu(self.globalMenu)

        # set events on hosts list
        self.ui.hostsList.itemDoubleClicked.connect(self.slotConnectHost)
        self.ui.hostsList.itemClicked.connect(self.slotShowHost)
        self.ui.hostsList.customContextMenuRequested.connect(self.slotShowHostContextMenu)

        # set tab widget
        self.tabWidget = MyTabWidget()
        self.setCentralWidget(self.tabWidget)

        # set tray icon
        self.tray = QSystemTrayIcon(QIcon(":/ico/myrdp.svg"))
        self.tray.activated.connect(self.trayActivated)

        self.trayMenu = QMenu()
        self.trayMenu.addAction("Hide tray icon", self.changeTrayIconVisibility)
        self.trayMenu.addAction("Quit", self.close)

        self.tray.setContextMenu(self.trayMenu)

        # host list
        self.ui.filter.textChanged.connect(self.setHostList)
        self.setHostList()
        self.restoreSettings()

    def trayActivated(self, reason):
        if reason != QSystemTrayIcon.Trigger:
            return
        if self.isVisible():
            self.hide()
        else:
            self.show()
            self.activateWindow()

    def changeTrayIconVisibility(self):
        if self.tray.isVisible():
            self.tray.hide()
            if not self.isVisible():
                self.show()
        else:
            self.tray.show()

    def setDockPosition(self, dockWidgetArea):
        if self.ui.hostsDock.isFloating():
            self.ui.hostsDock.setFloating(False)
        self.addDockWidget(dockWidgetArea, self.ui.hostsDock)

    def setDockFloat(self):
        if self.ui.hostsDock.isFloating():
            return
        # default title bar must be set before is float because sometimes window make strange crash
        self.ui.hostsDock.setTitleBarWidget(None)
        self.ui.hostsDock.setFloating(True)

    def dockLevelChanged(self, isFloating):
        if isFloating:
            # changing title bar widget if is not none, probably true will be only once on start with saved float state
            if self.ui.hostsDock.titleBarWidget():
                self.ui.hostsDock.setTitleBarWidget(None)
        else:
            self.ui.hostsDock.setTitleBarWidget(self.dockWidgetTileBar)

    def showFramelessWidget(self):
        self.t.show()
        self.t.setGeometry(self.frameGeometry())

    def getCurrentHostListItemName(self):
        return self.ui.hostsList.currentItem().text()

    def findHostItemByName(self, name):
        result = self.ui.hostsList.findItems(name, Qt.MatchExactly)
        resultLen = len(result)
        if resultLen != 1:  # should be only one host
            logging.error("Host not found. Got %d results" % resultLen)
        return result[0]

    def slotShowHostContextMenu(self, pos):
        """ slot needed to show menu in proper position, or i'm doing something wrong
        """
        self.hostMenu.exec_(self.ui.hostsList.mapToGlobal(pos))

    def addHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.add()
        if resp["code"]:
            self.setHostList()
        hostName = resp.get("name")
        if hostName:
            hostItem = self.findHostItemByName(hostName)
            self.slotConnectHost(hostItem)

    def editHost(self):
        hostDialog = HostConfigDialog(self.hosts)
        resp = hostDialog.edit(self.getCurrentHostListItemName())
        if resp["code"]:
            self.setHostList()

    def deleteHost(self):
        self.hosts.delete(self.getCurrentHostListItemName())
        self.setHostList()

    def connectFrameless(self, screenIndex=None):
        self.connectHost(self.getCurrentHostListItemName(), frameless=True, screenIndex=screenIndex)

    # Fix to release keyboard from QX11EmbedContainer, when we leave widget through wm border
    def leaveEvent(self, event):
        keyG = QWidget.keyboardGrabber()
        if keyG is not None:
            keyG.releaseKeyboard()
        event.accept()  # needed?

    def setHostList(self):
        """ set hosts list in list view """
        self.ui.hostsList.clear()
        self.ui.hostsList.addItems(self.hosts.getFilteredHostsNames(self.ui.filter.text()))

    def slotShowHost(self, item):
        # on one click we activating tab and showing options
        self.tabWidget.activateTab(item)

    def slotConnectHost(self, item):
        self.connectHost(unicode(item.text()))

    def connectHost(self, hostId, frameless=False, screenIndex=None):
        hostId = unicode(hostId)  # sometimes hostId comes as QString
        tabPage = self.tabWidget.createTab(hostId)
        tabPage.reconnectionNeeded.connect(self.connectHost)

        if frameless:
            self.tabWidget.detachFrameless(tabPage, screenIndex)

        execCmd, opts = self.getCmd(tabPage, hostId)
        ProcessManager.start(hostId, tabPage, execCmd, opts)

    def getCmd(self, tabPage, hostName):
        host = self.hosts.get(hostName)

        # set tabPage widget
        width, height = tabPage.setSizeAndGetCurrent()
        # 1et widget winId to embed rdesktop
        winId = tabPage.x11.winId()

        # set remote desktop client, at this time works only with freerdp
        remoteClientType, remoteClientOptions = self.config.getRdpClient()
        remoteClient = ClientFactory(remoteClientType, **remoteClientOptions)
        remoteClient.setWindowParameters(winId, width, height)
        remoteClient.setUserAndPassword(host.user, host.password)
        remoteClient.setAddress(host.address)
        return remoteClient.getComposedCommand()

    def saveSettings(self):
        settings = QSettings("MyRDP")
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("windowState", self.saveState())
        settings.setValue("trayIconVisibility", self.tray.isVisible())

    def restoreSettings(self):
        settings = QSettings("MyRDP")

        try:
            self.restoreGeometry(settings.value("geometry").toByteArray())
            self.restoreState(settings.value("windowState").toByteArray())
        except Exception:
            logging.debug("No settings to restore")

        # restore tray icon state
        trayIconVisibility = settings.value("trayIconVisibility").toBool()
        self.tray.setVisible(trayIconVisibility)

    def closeEvent(self, event):
        if not ProcessManager.hasActiveProcess:
            self.saveSettings()
            return

        msgBox = QMessageBox(self, text="Are you sure do you want to quit?")
        msgBox.setWindowTitle("Exit confirmation")
        msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        ret = msgBox.exec_()
        if ret == QMessageBox.Cancel:
            event.ignore()
            return

        self.saveSettings()
        ProcessManager.killemall()
        event.accept()
Exemplo n.º 12
0
class MainWindow(QMainWindow,Ui_MainWindow):

    def __init__(self,parent=None):
        QMainWindow.__init__(self,parent)         
        self.setupUi(self)
        self.textshorturl.setEnabled(False)
        self.convertprogress.hide()        
        self.trayIcon = QSystemTrayIcon()
        self.trayIcon.setIcon(QIcon("/usr/share/pixmaps/nixiconsvg.svg"))
        trayIconMenu = QMenu()
        self.appabout = trayIconMenu.addAction("About")
        self.appexit = trayIconMenu.addAction("Exit")
        self.trayIcon.setContextMenu(trayIconMenu)       
        self.connect(self.trayIcon,SIGNAL("activated(QSystemTrayIcon::ActivationReason)"),self.iconActivated)
        self.connect(self.buttonurlconvert,SIGNAL('clicked()'),self.converturl)
        self.connect(self.appexit,SIGNAL('triggered()'),self.close)
        self.connect(self.appabout,SIGNAL('triggered()'),self.showabout)
        self.connect(self.actionAbout,SIGNAL('triggered()'),self.showabout)
        self.connect(self.buttonreset,SIGNAL('clicked()'),self.resetall)
        self.connect(self.actionNew,SIGNAL('triggered()'),self.resetall)
        
    
    def iconActivated(self,dovod):
        if dovod == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            else:
                self.show()
                 
    def resetall(self):
        self.texturl.setText("")
        self.textshorturl.setText("")
        self.textshorturl.setEnabled(False)
        self.buttonurlconvert.setEnabled(True)
        self.comboservice.setCurrentIndex(0)
        self.convertprogress.hide()
        
    def converturl(self):
        url=str(self.texturl.text())
        url=url.strip()
        if url=="":
            QMessageBox.warning(self,"Error","Please Enter the valid URL")
        else:
            self.convertprogress.show()
            self.convertprogress.setValue(10)
            self.textshorturl.setEnabled(True)
            self.convertprogress.setValue(30)
            self.currentservice=self.comboservice.currentText()
            self.convertprogress.setValue(50)
            self.buttonurlconvert.setEnabled(False)
            if self.currentservice == "TinyURL":
                try:
                    self.textshorturl.setText(str(tinyurl.create_one(url)))
                    self.convertprogress.setValue(100)
                    self.buttonurlconvert.setEnabled(True)
                except:
                    QMessageBox.warning(self,"Error","Host can not be resolved")
                            
            elif self.currentservice == "google":
                try:
                    self.textshorturl.setText(str(google.shorten(url)))
                    self.convertprogress.setValue(100)
                    self.buttonurlconvert.setEnabled(True)
                    self.textshorturl.copy()
                except:
                    QMessageBox.warning(self,"Error","Host can not be resolved")
                    
            elif self.currentservice == "bit.ly":
                try:
                    a=bitly.Api()
                    self.textshorturl.setText(str(a.shorten(url)))
                    self.convertprogress.setValue(100)
                    self.buttonurlconvert.setEnabled(True)
                except:
                    QMessageBox.warning(self,"Error","Host can not be resolved")
    
    def showEvent(self,event):
        self.show()
        self.trayIcon.hide()
        event.ignore()
     
    def closeEvent(self,event):
        reply = QMessageBox.question(self, 'Message',"Are you sure to quit?", QMessageBox.Yes | QMessageBox.No,QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.trayIcon.hide()
            event.accept()
        else:
            self.hide()
            self.trayIcon.show()
            event.ignore()  

    def showabout(self):
        ab=About()
        ab.exec_()
Exemplo n.º 13
0
class MainWindow(QMainWindow):
	def __init__(self):
		QMainWindow.__init__(self)
		
		self.setWindowTitle('%s %s' % (QApplication.applicationName(), QApplication.applicationVersion()));

		self.config = ConfigHandler(os.path.join(os.path.expanduser('~'), '.pywv/pywv.cfg'), self)

		self.setStyle(QStyleFactory.create(self.config.loadStyle()))
		if self.config.loadStyleSheet():
			self.setStyleSheet(self.config.loadStyleSheet())
		else:
			self.setStyleSheet("* {}") # without any stylesheet, windowstyles won't apply


		self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks | QMainWindow.VerticalTabs);

#		self.dummy = QWidget(self)
		self.setCentralWidget(QWidget(self))

		self.pBar = QProgressBar(self)
		self.pBar.setRange(0, self.config.loadReloadInterval())
		self.pBar.setFormat("%v Sekunden")
		if not self.config.loadAutoload():
			self.pBar.hide()

		self.statusBar = QStatusBar(self)
		self.setStatusBar(self.statusBar)
		self.statusBar.addWidget(self.pBar)

		self.reloadTimer = QTimer(self);
		self.reloadTimer.setInterval(self.config.loadReloadInterval() * 1000)
		self.connect(self.reloadTimer, SIGNAL('timeout()'), self.reload_)
		if self.config.loadAutoload():
			self.reloadTimer.start()

		self.autoloadStatusTimer = QTimer(self)
		self.autoloadStatusTimer.setInterval(1000) # 1 sec
		self.connect(self.autoloadStatusTimer, SIGNAL('timeout()'), self.onAutoloadStatus)
		self.autoloadStatusTimer.start()

		self.mAction = self.menuBar().addMenu(self.tr("&Action"))
		self.mAction.addAction(self.tr("&update"), self.reload_, QKeySequence('F5'))
		self.mAction.addAction(self.tr("e&xit"), self.onExit, 'Ctrl+Q')

		self.mStyle = QMenu(self.tr("&Style"), self)
		for s in list(QStyleFactory.keys()):#       // fill in all available Styles
			self.mStyle.addAction(s)
		self.connect(self.mStyle, SIGNAL('triggered(QAction*)'), self.onStyleMenu)

		self.mOption = self.menuBar().addMenu(self.tr("&Options"))
		self.mOption.addAction(self.tr("reloadinterval") , self.onReloadTime , 'F8')
		self.mOption.addAction(self.tr("manage links")   , self.onNewLink    , 'F6')
		self.mOption.addSeparator()

		self.ontopAction       = QAction(self.tr("always on &top")  , self)
		self.showTrayAction    = QAction(self.tr("show tray &icon") , self)
		self.closeToTrayAction = QAction(self.tr("close to &tray")  , self)
		self.autoloadAction    = QAction(self.tr("auto&load")       , self)

		self.ontopAction.setCheckable(True)
		self.showTrayAction.setCheckable(True)
		self.closeToTrayAction.setCheckable(True)
		self.autoloadAction.setCheckable(True)

		self.showTrayAction.setChecked   (self.config.loadShowTray()   )
		self.ontopAction.setChecked      (self.config.loadOntop()      )
		self.closeToTrayAction.setChecked(self.config.loadCloseToTray())
		self.autoloadAction.setChecked   (self.config.loadAutoload()   )

		self.connect(self.ontopAction       , SIGNAL('toggled(bool)') , self.onOntopAction)
		self.connect(self.showTrayAction    , SIGNAL('toggled(bool)') , self.onShowTrayAction)
		self.connect(self.closeToTrayAction , SIGNAL('toggled(bool)') , self.onCloseToTrayAction)
		self.connect(self.autoloadAction    , SIGNAL('toggled(bool)') , self.onAutoloadAction)

		self.mOption.addAction(self.ontopAction)
		self.mOption.addAction(self.showTrayAction)
		self.mOption.addAction(self.closeToTrayAction)
		self.mOption.addAction(self.autoloadAction)
		self.mOption.addSeparator()
		self.mOption.addMenu(self.mStyle)

		self.trayIcon = QSystemTrayIcon(QIcon(':/appicon'), self);
		self.trayMgr = TrayManager(self.config, self.trayIcon)
		self.connect(self.trayIcon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), self.onTrayIcon)
		if self.config.loadShowTray(): self.trayIcon.show()

		self.trayIconMenu = QMenu()
		self.trayIconMenu.addAction(self.tr("e&xit"), self.onExit)
		self.trayIcon.setContextMenu(self.trayIconMenu)

		self.mAbout = self.menuBar().addMenu(self.tr("&about"))
		self.mAbout.addAction(QApplication.applicationName(), self.onAboutAppAction)
		self.mAbout.addAction("Qt", self.onAboutQtAction)

		self.createWidgets()

		self.resize(self.config.loadWindowSize())
		self.restoreState(self.config.loadWindowState())

		if self.config.loadIsVisible():
			self.show()
			self.reload_()

	def __del__(self):
		self.config.saveWindowState(self.saveState())

	def createSingleWidget(self, name):
#		print 'crt', name, type(name), '\n', self.widgets
#		print 'crt', name, type(name)
		links = self.config.loadLinks()
		if links[name]['type'] == 'generic':
			self.widgets[name] = GenericWidget(name, self.config, self)
		else:
			pluginsAvail = classdirPlugins().all_()
			for plugin in pluginsAvail:
				if links[name]['type'] == plugin['class']:
					pluginClass = plugin['class']
					break
				else:
					continue

			exec('self.widgets[name] = %s(name, self.config, self)' % pluginClass)
		#	print(('loaded plugin', self.widgets[name]))

		self.addDockWidget(0x4, self.widgets[name])
		self.widgets[name].reload_()

	def delWidget(self, name):
		#print 'del', name, type(name), '\n', self.widgets
		self.removeDockWidget(self.widgets[name])
		self.widgets[name].deleteLater()
		self.widgets[name] = None
		del self.widgets[name]

	def createWidgets(self):
		self.widgets = {}
		for name in self.config.loadLinks():
			self.createSingleWidget(name)


	@pyqtSlot()
	def onExit(self):
		self.config.saveWindowSize(self.size())
		QApplication.exit();

	def closeEvent(self, event):
		self.config.saveWindowSize(self.size())
#	       	QApplication.exit()
		# tray is visible -> close to tray
		# else close app
		if self.trayIcon.isVisible():
			event.accept()
		else:
			QApplication.exit()
			return
		# if close-to-tray is set, do so
		if self.config.loadCloseToTray():
			event.accept()
		else:
			QApplication.exit()
			return;
		# save this state
		if	self.trayIcon.isVisible():	self.config.saveIsVisible(False)
		else:					self.config.saveIsVisible(True);
	@pyqtSlot()
	def reload_(self):
		for name in self.widgets:
			self.widgets[name].reload_()
		self.pBar.setValue(self.config.loadReloadInterval())
		self.reloadTimer.start(self.config.loadReloadInterval()*1000)

	@pyqtSlot()
	def onAutoloadStatus(self):
		self.pBar.setValue(self.pBar.value()-1)
#		print([idx for idx in self.widgets])

	def onStyleMenu(self, a):
		QApplication.setStyle(QStyleFactory.create(a.text()))
		self.setStyle(QStyleFactory.create(a.text()))
		self.config.saveStyle(a.text())

	def onReloadTime(self):
		ok = False
		value, ok = QInputDialog.getInteger(self,
			self.tr("reloadinterval"), # title
			self.tr("insert time in s"), # text
			self.config.loadReloadInterval(), # default
			10, # minimum
			86400, # maximum (at least once a day)
			1, # step
			)
		if ok:
			self.config.saveReloadInterval(value)
			self.pBar.setRange(0,self.config.loadReloadInterval())
			self.reload_()

	def onAutoloadAction(self, b):
		if b:
			self.reloadTimer.start()
			self.pBar.show()
			self.reload_()
		else:
			self.reloadTimer.stop()
			self.pBar.hide()
		self.config.saveAutoload(b)

	def onNewLink(self):
		inp = LinkInput(self.config, self)
		if inp.exec_():
			# sync active widgets
			for name in inp.modifiedWidgets():
				if name in self.widgets:
					self.delWidget(name)
					self.createSingleWidget(name)
				else:
					self.createSingleWidget(name)

			# remove deleted
#			for name in self.widgets: print 'shown', name
#			for name in self.config.loadLinks(): print 'conf', name
			todel = []
			for name in self.widgets:
				if name not in self.config.loadLinks():
					todel.append(name)

			for widget in todel:
				self.delWidget(widget)

	def onOntopAction(self, b):
		if b:	self.setWindowFlags(Qt.Dialog | Qt.WindowStaysOnTopHint)
		else:	self.setWindowFlags(Qt.Dialog)
		self.setWindowIcon(QIcon(':/appicon'))
		self.show();
		self.config.saveOntop(b)

	def onShowTrayAction(self, b):
		if b:	self.trayIcon.show()
		else:	self.trayIcon.hide()
		self.config.saveShowTray(b)

	def onCloseToTrayAction(self, b):
		self.config.saveCloseToTray(b)

	def onTrayIcon(self, reason):
		if reason == QSystemTrayIcon.Trigger:
			if(self.isVisible()):
				self.config.saveWindowSize(self.size())
				self.hide()
				self.config.saveIsVisible(False)
			else:
				self.show()
				self.resize(self.config.loadWindowSize())
				self.config.saveIsVisible(True)

	def onAboutAppAction(self):
		QMessageBox.about(self, self.tr("&about"), self.tr("name %1 version %2").arg(QApplication.applicationName()).arg(QApplication.applicationVersion()))
	def onAboutQtAction(self):
		QMessageBox.aboutQt(self, self.tr("&about"))
Exemplo n.º 14
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        PyQt4.uic.loadUi('ui/pyrex.ui', self)

        # Icon:
        self.setWindowIcon(QIcon("res/rex_18.png"))

        # Tray Icon:
        self.trayIcon = QSystemTrayIcon(QIcon("res/rex_18.png"), self)
        trayMenu = QMenu()
        self.actionQuit = QAction("Quitter", trayMenu)
        self.actionQuit.triggered.connect(self.close)
        trayMenu.addAction(self.actionQuit)
        self.trayIcon.setContextMenu(trayMenu)
        #self.trayIcon.show()

        QObject.connect(self.trayIcon, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.icon_activated)
 
        # Tabs:
        self.tabs = QTabWidget()
        QObject.connect(self.tabs, SIGNAL('currentChanged(int)'), self.change_tab)

        self.downloads = TabDownloads(self.tabs)
        self.search = TabSearch(None, self.downloads, self.tabs)
        self.adv_search = TabAdvSearch(self.search, self.tabs)
        self.peers = TabPeers(self.search, self.downloads, self.tabs)
        self.options = TabOptions(self.tabs)
        self.shares = TabShares(self.tabs)
        self.informations = TabInformations(self.tabs)

        self.tabs.insertTab(0, self.search, u"Recherches")
        self.tabs.insertTab(1, self.adv_search, u"Recherche avancée")
        self.tabs.insertTab(2, self.downloads, u"Téléchargements")
        self.tabs.insertTab(3, self.peers, u"Utilisateurs")
        self.tabs.insertTab(4, self.options, u'Options')
        self.tabs.insertTab(5, self.shares, u'Mes partages')
        self.tabs.insertTab(6, self.informations, u'Informations')
        
        self.setCentralWidget(self.tabs)

    def change_tab(self, tab):
        if tab == 3:
            self.peers.update_peers()
        elif tab == 4:
            self.options.update_conf()
        elif tab == 5:
            self.shares.update_sharedirs()
        elif tab == 6:
            self.informations.update_informations()

    def closeEvent(self, event):
        if not self.trayIcon.isVisible() and Configuration.icon:
            self.trayIcon.show()
            self.hide()
            event.ignore()
        else:
            termine = True
            # On vérifie que tous les téléchargements soient finis
            for download in self.downloads.instance.downloads:
                if download.state == 3:
                    termine = False
            # Si il y a un download en cours on affiche la fenêtre
            if not termine and not Configuration.close_window:
                # Un petit messageBox avec bouton clickable :)
                msgBox = QMessageBox(QMessageBox.Question, u"Voulez-vous vraiment quitter?", u"Un ou plusieurs téléchargements sont en cours, et pyRex ne gère pas encore la reprise des téléchargements. Si vous quittez maintenant, toute progression sera perdue!")
                checkBox = QCheckBox(u"Ne plus afficher ce message", msgBox)
                checkBox.blockSignals(True)
                msgBox.addButton(checkBox, QMessageBox.ActionRole)
                msgBox.addButton("Annuler", QMessageBox.NoRole)
                yesButton = msgBox.addButton("Valider", QMessageBox.YesRole)
                msgBox.exec_()
                
                if msgBox.clickedButton() == yesButton:
                    # On save l'état du bouton à cliquer
                    if checkBox.checkState() == Qt.Checked:
                        Configuration.close_window = True
                        Configuration.write_config()
                    event.accept()
                else:
                    event.ignore()
            else:
                event.accept()
   
    def changeEvent(self, event):
        if event.type() == QEvent.WindowStateChange:
            if self.isMinimized():
                self.trayIcon.show()
                self.hide()
 
    def icon_activated(self, reason):
        if reason == QSystemTrayIcon.Trigger:
            if self.isVisible():
                self.hide()
            else:
                self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
                self.activateWindow()
                self.show()
                self.trayIcon.hide()
Exemplo n.º 15
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self._init_menu()
        self._init_actions()
        self._init_models()
        self.lt = ListenThread(self.add_new_device)
        self._install_akmods = False
        self._main_pko = PackageKitQt()

        # right frame
        self.comboBoxModules.currentIndexChanged.connect(self._handle_select_module)
        self.buttonBoxDetails.clicked.connect(self._handle_rbb)

        self.__debug_mode__ = True

        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setIcon(QIcon(":/img/gears"))
        self.trayIcon.activated.connect(self._handle_tray_icon_activate)
        self.hide()

    def closeEvent(self, event):
        event.ignore()
        self.hide()

    def _init_menu(self):
        self.actionFileExit.triggered.connect(self._handle_exit)

        self.listView.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.listViewActions.setContextMenuPolicy(Qt.ActionsContextMenu)

        # context menu for listView
        for act in self.menuDevices.actions():
            self.listView.addAction(act)
        # context menu for listViewActions
        for act in self.menuActions.actions():
            self.listViewActions.addAction(act)

    def _init_actions(self):
        self.actionDevicesDisable.triggered.connect(self._handle_disable_device)
        self.actionDevicesDisableAll.triggered.connect(self._handle_disable_all)
        self.actionActionsDelete.triggered.connect(self._handle_remove_current_action)
        self.actionActionsClear.triggered.connect(self._handle_clean_actions)
        self.actionActionsApply.triggered.connect(self._handle_apply_actions)

    def _init_models(self):
        self.model = DevicesListModel(self)
        self.listView.setModel(self.model)
        self.listView.selectionModel().currentChanged.connect(self._handle_select_item)

        self.act_model = ActionsModel(self)
        self.listViewActions.setModel(self.act_model)
        self.listViewActions.selectionModel().currentChanged.connect(self._handle_action_select_item)

        self.act_model.actionDeleted.connect(self.model.reset_changes)
        self.model.dataChanged.connect(self._handle_data_changed_in_model)

    def _init_pk(self):
        if self._main_pko is None:
            self._main_pko = PackageKitQt()

    def add_new_device(self, dev):
        self.model.add_new_device(dev)
        self.show_notification()

    def disen_device_notif(self, device_id, disable=False):
        if disable:
            self.lt.disable_device_notif(device_id)
        else:
            self.lt.enable_device_notif(device_id)

    def show_notification(self):
        # if self.isHidden():
        #    self.show()
        self.trayIcon.setVisible(True)

    def start_listen(self):
        self.lt.start()

    def _right_frame_apply(self, idx):
        d = self.model.device_by_index(idx)
        sel_cb_idx = self.comboBoxModules.currentIndex()
        sel_module = str(self.comboBoxModules.itemData(sel_cb_idx).toString())
        if sel_module == d.current_driver():
            return

        self.act_model.remove_actions_by_devid(d.device_id(), sel_module)
        pkgsi = d.packages_to_install(sel_module)
        pkgsr = d.packages_to_remove(sel_module)
        if len(pkgsi) > 0:
            self.act_model.add_new_action(d.device_id(), sel_module, pkgsi, 0)
        if len(pkgsr) > 0:
            rem_mds = str(", ").join(d.device_modules(sel_module))
            self.act_model.add_new_action(d.device_id(), rem_mds, pkgsr, 1)
        d.set_selected_driver(sel_module)
        self.set_right_frame(idx)

    def _current_device(self, idx=None):
        cur_idx = idx
        if idx is None:
            cur_idx = self.listView.selectionModel().currentIndex()
        return self.model.device_by_index(cur_idx)

    def set_right_frame(self, idx):
        self.comboBoxModules.clear()
        d = self._current_device(idx)
        curdrv = QString()
        if d.current_driver() is None or len(d.current_driver()) == 0:
            curdrv = self.tr("Not installed")
        else:
            curdrv = QString("- %s -" % d.current_driver())
        self.comboBoxModules.addItem(curdrv, QString(d.current_driver()))

        self.lineEditName.setText(d.device_name())
        our_sel_idx = -1
        for m in d.device_modules():
            devmod = QString()
            if m == d.current_driver():
                continue
            elif m == d.selected_driver():
                devmod = QString("* %s *" % m)
                our_sel_idx = self.comboBoxModules.count()
            else:
                devmod = QString("%s" % m)
            self.comboBoxModules.addItem(devmod, QString(m))

        if our_sel_idx != -1:
            self.comboBoxModules.setCurrentIndex(our_sel_idx)

    def _do_resolve_packages(self, pkgs, to_remove=False):
        if len(pkgs) == 0:
            return []
        self.debug_print(pkgs)
        self._init_pk()
        pkc = self._main_pko.getTransaction()
        pkc.SetHints()
        filt = "none"
        if to_remove:
            filt = "installed"
            pkc.SearchNames(filt, pkgs)
            if pkc.has_error():
                return []
            pkg_ids = pkc.get_package_ids()
            return pkg_ids

        pkc.Resolve(filt, pkgs)
        if pkc.has_error():
            return []
        pkg_ids = pkc.get_package_ids()

        return pkg_ids

    def _do_install_packages(self, pkgs):
        if len(pkgs) == 0:
            return
        print "Begin install packages"
        self._init_pk()
        pkc = self._main_pko.getTransaction()  # PackageKitClient()
        pkc.SetHints()
        pkc.InstallPackages(False, pkgs)
        if pkc.has_error():
            err_code, err_msg = pkc.error()
            self.debug_print("Error: [%s] %s" % (err_code, err_msg))

    def _do_remove_packages(self, pkgs):
        if len(pkgs) == 0:
            return
        print "Begin remove packages"
        self._init_pk()
        pkc = self._main_pko.getTransaction()
        pkc.RemovePackages(pkgs, True, True)
        if pkc.has_error():
            err_code, err_msg = pkc.error()
            self.debug_print("Error: [%s] %s" % (err_code, err_msg))

    def _do_only_ids(self, pkgs):
        res_ids = []
        if pkgs is None:
            return res_ids
        if len(pkgs) == 0:
            return res_ids
        print pkgs
        for installed, id, summary in pkgs:
            res_ids.append(id)
        return res_ids

    def _debug_print_pkg_ids(self, pkg_ids):
        if pkg_ids is None:
            return
        elif len(pkg_ids) == 0:
            return
        for pkg_id in pkg_ids:
            self.debug_print("+ Installed: %s" % (pkg_id))

    def _do_act(self):
        pkgs_to_install, pkgs_to_remove = self.act_model.get_packages(self._install_akmods)
        if len(pkgs_to_install) + len(pkgs_to_remove) == 0:
            QMessageBox.information(self, self.tr("Empty actions"), self.tr("Nothing to install/remove"))
            return

        # Resolve all packages
        pkg_ids_install = self._do_resolve_packages(pkgs_to_install)
        pkg_ids_remove = self._do_resolve_packages(pkgs_to_remove, True)

        self.debug_print("To install: %s" % pkg_ids_install)
        self.debug_print("To remove: %s" % pkg_ids_remove)

        self._do_remove_packages(pkg_ids_remove)
        self._do_install_packages(pkg_ids_install)

        print "Packages to install:"
        self._debug_print_pkg_ids(pkg_ids_install)

        print "Packages to remove:"
        self._debug_print_pkg_ids(pkg_ids_remove)
        res = QMessageBox.question(
            self,
            self.tr("Operations done"),
            self.tr("All operations applied. You may reboot a system. Reboot now?"),
            QMessageBox.Yes and QMessageBox.No,
            QMessageBox.Yes,
        )
        if res == QMessageBox.Yes:
            print "rebooting"

    def debug_print(self, msg):
        if not self.__debug_mode__:
            return
        print (msg)

    # slots
    def _handle_data_changed_in_model(self, begin_idx, end_idx):
        cur_idx = self.listView.selectionModel().currentIndex()
        if not cur_idx.isValid():
            return
        cur_row = cur_idx.row()
        row_range = range(begin_idx.row(), end_idx.row())
        if len(row_range) == 0:
            row_range = [begin_idx.row()]
        if cur_row in row_range:
            self.set_right_frame(cur_idx)

    def _handle_remove_current_action(self):
        cur_idx = self.listViewActions.selectionModel().currentIndex()
        self.act_model.removeRows(cur_idx.row(), 1)

    def _handle_clean_actions(self):
        self.act_model.clearRows()

    def _handle_disable_all(self):
        devs = self.model.disable_all_devices()
        self.disen_device_notif(devs, True)
        self.trayIcon.hide()

    def _handle_disable_device(self):
        cur_idx = self.listView.selectionModel().currentIndex()
        this_is_hide_item = self.model.index_is_hide(cur_idx)
        need_id = self.model.index_hide(cur_idx, not this_is_hide_item)
        self._handle_select_item(cur_idx, cur_idx)

        self.disen_device_notif(need_id, not this_is_hide_item)

    def _handle_exit(self):
        self.hide()

    def _handle_action_select_item(self, cur_idx, prev_idx):
        self.actionActionsDelete.setEnabled(cur_idx.isValid())

    def _handle_select_item(self, current_idx, prev_idx):
        self.actionDevicesDisable.setEnabled(current_idx.isValid())
        if not current_idx.isValid():
            return

        if self.model.index_is_hide(current_idx):
            self.actionDevicesDisable.setText(self.tr("&Enable notification"))
        else:
            self.actionDevicesDisable.setText(self.tr("&Disable notification"))
        self.set_right_frame(current_idx)

    def _handle_rbb(self, but):
        cur_idx = self.listView.selectionModel().currentIndex()
        if self.buttonBoxDetails.buttonRole(but) == QDialogButtonBox.ResetRole:
            self.set_right_frame(cur_idx)
        elif self.buttonBoxDetails.buttonRole(but) == QDialogButtonBox.ApplyRole:
            self._right_frame_apply(cur_idx)

    def _handle_select_module(self, module_index):
        if module_index == -1:
            self.labelDetails.setText("")
            return
        selection_module_name = str(self.comboBoxModules.itemData(module_index).toString())
        d = self._current_device()
        pkgsi = d.packages_to_install(selection_module_name)
        pkgsr = d.packages_to_remove(selection_module_name)
        detail_html = QString("<h4>%1 </h4>").arg(self.tr("For installing this module need:"))
        if len(pkgsi) > 0:
            detail_html += QString("<p>%1 <ul>").arg(self.tr("Packages to install:"))
            for p in pkgsi:
                detail_html += QString("<li>%1</li>").arg(p)
            detail_html += QString("</ul></p>")
        if len(pkgsr) > 0:
            detail_html += QString("<p>%1 <ul>").arg(self.tr("Packages to remove: "))
            for p in pkgsr:
                detail_html += QString("<li>%1</li>").arg(p)
            detail_html += QString("</ul></p>")

        self.labelDetails.setText(detail_html)

    def _handle_apply_actions(self):
        if self.act_model.pkgs_to_install_exist():
            result = QMessageBox.question(
                self,
                self.tr("Install akmods too"),
                self.tr("Do you have install also akmod (automated kernel module) packages too?"),
                QMessageBox.Yes and QMessageBox.No,
                QMessageBox.Yes,
            )
            if result == QMessageBox.Yes:
                self._install_akmods = True

        self.setEnabled(False)
        self._do_act()
        self.setEnabled(True)

    def _handle_tray_icon_activate(self, reason):
        if self.isHidden():
            self.show()
        else:
            self.hide()