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()
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."
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_()
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_()
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)
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_()
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
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)
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)
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()
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_()
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"))
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()
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()