def _create_filter_menus(self): # Airport filters arrDep_action_group = QActionGroup(self) self._add_filter_action(arrDep_action_group, 'All', self.list_model.filter_arrDep_all, ticked=True) self._add_filter_action(arrDep_action_group, 'On map', self.list_model.filter_arrDep_inRange) if env.airport_data != None: self._add_filter_action(arrDep_action_group, 'Here only', self.list_model.filter_arrDep_here) arrDep_filter_menu = QMenu() arrDep_filter_menu.addActions(arrDep_action_group.actions()) self.filterArrDep_button.setMenu(arrDep_filter_menu) # Date filters date_action_group = QActionGroup(self) self._add_filter_action(date_action_group, '+/- 3 days', self.list_model.filter_date_week) self._add_filter_action(date_action_group, 'Today (~24 h)', self.list_model.filter_date_today, ticked=True) date_filter_menu = QMenu() date_filter_menu.addActions(date_action_group.actions()) self.filterDate_button.setMenu(date_filter_menu)
class FullscreenMenu(QMenu): def __init__(self, main_win, fullscreen_mngr): super().__init__(parent=main_win) self.fullscreen_mngr = fullscreen_mngr self.main_win = main_win self.setTitle("Fullscreen") self.setIcon(icons.get("fullscreen_menu_bttn")) self.qguiapp = QApplication.instance() self.action_group = QActionGroup(self) self.stop_fs_action = StopFullscreenAction( parent=self, fullscreen_mngr=self.fullscreen_mngr ) self.qscreens = None self.refresh_items() self.fullscreen_mngr.fullscreenstarted.connect(self.on_fullscreenstarted) self.fullscreen_mngr.fullscreenstopped.connect(self.on_fullscreenstopped) def refresh_items(self): # Clear action group for action in self.action_group.actions(): self.action_group.removeAction(action) del action # Add qscreen actions to group this_qscreen = get_qscreen_at(self.main_win) primary_qscreen = self.qguiapp.primaryScreen() sorted_qscreens = sorted(self.qguiapp.screens(), key=lambda s: s.name()) for qscreen in sorted_qscreens: is_primary = qscreen == primary_qscreen is_this_screen = qscreen == this_qscreen action = StartFullscreenAction( qscreen=qscreen, fullscreen_mngr=self.fullscreen_mngr, is_primary=is_primary, is_this_screen=is_this_screen, main_win=self.main_win, ) action.setCheckable(True) action.setIcon(icons.get("display_screen")) self.action_group.addAction(action) self.action_group.addAction(self.stop_fs_action) self.addActions(self.action_group.actions()) def on_menu_aboutToShow(self): self.setChecked(self.fullscreen_mngr.is_fullscreen()) @pyqtSlot(QAction) def on_fullscreenstarted(self, action): self.stop_fs_action.setEnabled(True) @pyqtSlot() def on_fullscreenstopped(self): self.stop_fs_action.setEnabled(False) def on_aboutToShow(self): self.refresh_items()
def create_view_buttons(self): self.tb_views.addWidget(QLabel("View mode: ")) ag_views = QActionGroup(self) ag_views.setExclusive(True) for v in self.views.keys(): a = QAction(v) a.triggered.connect(self.change_view) a.setCheckable(True) ag_views.addAction(a) self.tb_views.addActions(ag_views.actions()) ag_views.actions()[0].setChecked(True) stretch = QWidget() stretch.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) self.tb_views.addWidget(stretch)
def create_toolbar(self, parent): from PyQt5.QtWidgets import QAction, QToolBar, QActionGroup from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt, QSize tb = QToolBar(self.display_name, parent) tb.setStyleSheet('QToolBar{spacing:0px;}\n' 'QToolButton{padding:0px; margin:0px; border:none;}') s = self._icon_size tb.setIconSize(QSize(s, s)) parent.add_tool_bar(self, Qt.LeftToolBarArea, tb) group = QActionGroup(tb) for mode in self.modes: action = QAction(self._icon(mode.icon_path), mode.name, group) action.setCheckable(True) def button_press_cb(event, mode=mode): mname = mode.name if ' ' in mname: mname = '"%s"' % mname from chimerax.core.commands import run run(self.session, 'ui mousemode %s %s' % (self.button_to_bind, mname)) action.triggered.connect(button_press_cb) action.vr_mode = lambda m=mode: m # To handle virtual reality button clicks. group.addAction(action) tb.addActions(group.actions()) tb.show() return tb
def _get_menu(self): # main menu menu = QMenu() main_menu_action_group = QActionGroup(menu) main_menu_action_group.setObjectName("main") # character menu map_action = QAction(menu) map_action.setText("Toggle Map") main_menu_action_group.addAction(map_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) characters_action = QAction(menu) characters_action.setText("Switch Characters") main_menu_action_group.addAction(characters_action) separator = QAction(menu) separator.setSeparator(True) main_menu_action_group.addAction(separator) settings_action = QAction(menu) settings_action.setText("Settings") main_menu_action_group.addAction(settings_action) quit_action = QAction(menu) quit_action.setText("Quit") main_menu_action_group.addAction(quit_action) menu.addActions(main_menu_action_group.actions()) menu.triggered[QAction].connect(self._menu_actions) return menu
class PangoMenuBarWidget(QToolBar): def __init__(self, parent=None): super().__init__(parent) self.setMovable(False) self.setIconSize(QSize(16, 16)) self.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # Widgets spacer_left = QWidget() spacer_left.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) spacer_right = QWidget() spacer_right.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # Actions self.action_group = QActionGroup(self) self.prefs_action = QAction("Prefs") self.prefs_action.setIcon(pango_get_icon("prefs")) self.action_group.addAction(self.prefs_action) self.open_images_action = QAction("Open Images") self.open_images_action.setIcon(pango_get_icon("open")) self.action_group.addAction(self.open_images_action) self.import_action = QAction("Import") self.import_action.setIcon(pango_get_icon("import")) self.action_group.addAction(self.import_action) self.export_action = QAction("Export") self.export_action.setIcon(pango_get_icon("export")) self.action_group.addAction(self.export_action) self.save_project_action = QAction("Save Project") self.save_project_action.setIcon(pango_get_icon("save")) self.action_group.addAction(self.save_project_action) self.run_action = QAction("PyTorch") self.run_action.setIcon(pango_get_icon("fire")) self.action_group.addAction(self.run_action) self.addAction(self.action_group.actions()[0]) #self.addWidget(spacer_left) self.addActions(self.action_group.actions()[1:-1]) self.addWidget(spacer_right) self.addAction(self.action_group.actions()[-1])
def _context_menu(self, point): """ Called when the widget is right-clicked to display the context menu. :param point: the location where the click happened """ logger.debug("Opening widget context menu") menu = QMenu(self) # Add the network settings settings = QAction('Network Settings', menu) iconPath = self._plugin.resource('settings.png') settings.setIcon(QIcon(iconPath)) # Add a handler on the action def settingsActionTriggered(): dialog = NetworkSettingsDialog(self._plugin) dialog.exec_() settings.triggered.connect(settingsActionTriggered) menu.addAction(settings) # Add each of the servers if self._plugin.core.servers: menu.addSeparator() serverGroup = QActionGroup(self) def serverActionTriggered(serverAction): if not self._plugin.network.connected and \ serverAction.isChecked(): self._plugin.network.connect(serverAction._server.host, serverAction._server.port) else: self._plugin.network.disconnect() for server in self._plugin.core.servers: isConnected = self._plugin.network.connected \ and server.host == self._plugin.network.host \ and server.port == self._plugin.network.port serverAction = QAction('%s:%d' % (server.host, server.port), menu, checkable=True) serverAction._server = server serverAction.setChecked(isConnected) serverGroup.addAction(serverAction) menu.addActions(serverGroup.actions()) serverGroup.triggered.connect(serverActionTriggered) # Show the context menu menu.exec_(self.mapToGlobal(point))
class TrayContextMenu(QtWidgets.QMenu): instances = set() def __init__(self, trayIcon): """ trayIcon = the object with the methods to call """ QtWidgets.QMenu.__init__(self) TrayContextMenu.instances.add(self) self.trayIcon = trayIcon self._buildMenu() def _buildMenu(self): self.framelessCheck = QtWidgets.QAction("Frameless Window", self, checkable=True) self.framelessCheck.triggered.connect(self.trayIcon.changeFrameless) self.addAction(self.framelessCheck) self.addSeparator() self.requestCheck = QtWidgets.QAction( "Show status request notifications", self, checkable=True) self.requestCheck.setChecked(True) self.addAction(self.requestCheck) self.requestCheck.triggered.connect(self.trayIcon.switchRequest) self.alarmCheck = QtWidgets.QAction("Show alarm notifications", self, checkable=True) self.alarmCheck.setChecked(True) self.alarmCheck.triggered.connect(self.trayIcon.switchAlarm) self.addAction(self.alarmCheck) distanceMenu = self.addMenu("Alarm Distance") self.distanceGroup = QActionGroup(self) for i in range(0, 6): action = QAction("{0} Jumps".format(i), None, checkable=True) if i == 0: action.setChecked(True) action.alarmDistance = i action.triggered.connect(self.changeAlarmDistance) self.distanceGroup.addAction(action) distanceMenu.addAction(action) self.addMenu(distanceMenu) self.addSeparator() self.quitAction = QAction("Quit", self) self.quitAction.triggered.connect(self.trayIcon.quit) self.addAction(self.quitAction) def changeAlarmDistance(self): for action in self.distanceGroup.actions(): if action.isChecked(): self.trayIcon.alarmDistance = action.alarmDistance self.trayIcon.changeAlarmDistance()
class ItemHistoryMenu(ItemHistory): """ Class for storing the history of items added to a QMenu """ def __init__(self, fileName: str, maxSize: int, menu: Any, callbackMethod: Any): ItemHistory.__init__(self, fileName, maxSize) self.menu = menu self.callbackMethod = callbackMethod items = self.tree.getElementsByTagName("item") self.actionGroup = QActionGroup(None) for node in items: text = self._getText(node) # type: str a = self.menu.addAction(text) # type: Any self.actionGroup.addAction(a) self.actionGroup.triggered.connect(self.actionTriggered) def _removeLastItem(self) -> None: actionList = self.actionGroup.actions() if actionList: a = actionList[-1] self.menu.removeAction(a) self.actionGroup.removeAction(a) def _insertItem(self, index: int, item: str) -> None: actionList = self.actionGroup.actions() actions = [] # type: List[str] for a in actionList: actions.append(a.text()) self.actionGroup.removeAction(a) self.menu.clear() actions = [item] + actions for action in actions: a = self.menu.addAction(action) self.actionGroup.addAction(a) self.actionGroup.triggered.connect(self.actionTriggered) def actionTriggered(self, action: QAction) -> None: self.callbackMethod(action.text())
def build_toolbars(self): main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=32, label_position=Qt.ToolButtonIconOnly) main_toolbar.setObjectName("main_toolbar") self.addToolBar(main_toolbar) main_toolbar.addAction(QIcon("./GUI/icons/connections.png"), "Configure MQTT broker", self.setup_broker) agBroker = QActionGroup(self) agBroker.setExclusive(True) self.actConnect = CheckableAction(QIcon("./GUI/icons/connect.png"), "Connect to the broker", agBroker) self.actDisconnect = CheckableAction(QIcon("./GUI/icons/disconnect.png"), "Disconnect from broker", agBroker) self.actDisconnect.setChecked(True) self.actConnect.triggered.connect(self.mqtt_connect) self.actDisconnect.triggered.connect(self.mqtt_disconnect) main_toolbar.addActions(agBroker.actions()) main_toolbar.addSeparator()
def rebuildBgMenu(self): self.scene.clearBgImg() # clears background bg_action_group = QActionGroup(self) bg_action_group.addAction(self.clearBg_action) for file_spec, pixmap, scale, title in settings.loose_strip_bay_backgrounds: action = QAction(title, self) action.setCheckable(True) action.triggered.connect( lambda b, px=pixmap, sc=scale: self.scene.setBgImg(px, sc)) bg_action_group.addAction(action) bg_menu = QMenu() bg_menu.addAction(self.clearBg_action) bg_menu.addSeparator() bg_menu.addActions( bg_action_group.actions()[1:]) # index 0 is self.clearBg_action self.background_menuButton.setMenu(bg_menu) self.clearBg_action.setChecked(True) self.background_menuButton.setEnabled( settings.loose_strip_bay_backgrounds != [])
def _create_game_menu(self): game_menu = QMenu(self) game_menu.setTitle('&Game') # New game action new_action = QAction('&New', self) new_action.setShortcut(QKeySequence('F2')) new_action.setObjectName('new_menu_item') game_menu.addAction(new_action) game_menu.addSeparator() # Difficulty group menu items. ## Create difficulty actions. beginner = game_menu.addAction('Beginner') intermediate = game_menu.addAction('Intermediate') expert = game_menu.addAction('Expert') #custom = game_menu.addAction('Custom', self) ## Name each of the actions. beginner.setObjectName('beginner') intermediate.setObjectName('intermediate') expert.setObjectName('expert') #custom.setObjectName('custom') ## Add each action to the group. difficulty_group = QActionGroup(self) difficulty_group.addAction(beginner) difficulty_group.addAction(intermediate) difficulty_group.addAction(expert) #difficulty_group.addAction(custom) # Make the group checkable and set a check mark in front of the selected difficulty. difficulty_group.setExclusive(True) for item in difficulty_group.actions(): item.setCheckable(True) expert.setChecked(True) game_menu.addSeparator() # Quit action quit_action = QAction('Quit', self) quit_action.setObjectName('quit_menu_item') game_menu.addAction(quit_action) return game_menu
class MainWindow(QMainWindow, Ui_MainWindow): dictChanged = pyqtSignal(str) # Tab indexes TabInfos = 0 TabSummary = 1 TabPersos = 2 TabPlots = 3 TabWorld = 4 TabOutline = 5 TabRedac = 6 def __init__(self): QMainWindow.__init__(self) self.setupUi(self) self.currentProject = None self.readSettings() # UI self.setupMoreUi() # Welcome self.welcome.updateValues() # self.welcome.btnCreate.clicked.connect self.stack.setCurrentIndex(0) # Word count self.mprWordCount = QSignalMapper(self) for t, i in [ (self.txtSummarySentence, 0), (self.txtSummaryPara, 1), (self.txtSummaryPage, 2), (self.txtSummaryFull, 3) ]: t.textChanged.connect(self.mprWordCount.map) self.mprWordCount.setMapping(t, i) self.mprWordCount.mapped.connect(self.wordCount) # Snowflake Method Cycle self.mapperCycle = QSignalMapper(self) for t, i in [ (self.btnStepTwo, 0), (self.btnStepThree, 1), (self.btnStepFour, 2), (self.btnStepFive, 3), (self.btnStepSix, 4), (self.btnStepSeven, 5), (self.btnStepEight, 6) ]: t.clicked.connect(self.mapperCycle.map) self.mapperCycle.setMapping(t, i) self.mapperCycle.mapped.connect(self.clickCycle) self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged) self.cmbSummary.setCurrentIndex(0) self.cmbSummary.currentIndexChanged.emit(0) # Main Menu for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) self.actSettings.triggered.connect(self.settingsWindow) self.actCloseProject.triggered.connect(self.closeProject) self.actQuit.triggered.connect(self.close) self.actToolFrequency.triggered.connect(self.frequencyAnalyzer) self.generateViewMenu() self.makeUIConnections() # self.loadProject(os.path.join(appPath(), "test_project.zip")) ############################################################################### # SUMMARY ############################################################################### def summaryPageChanged(self, index): fractalButtons = [ self.btnStepTwo, self.btnStepThree, self.btnStepFive, self.btnStepSeven, ] for b in fractalButtons: b.setVisible(fractalButtons.index(b) == index) ############################################################################### # OUTLINE ############################################################################### def outlineRemoveItemsRedac(self): self.treeRedacOutline.delete() def outlineRemoveItemsOutline(self): self.treeOutlineOutline.delete() ############################################################################### # PERSOS ############################################################################### def changeCurrentPerso(self, trash=None): index = self.lstPersos.currentPersoIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) for w in [ self.txtPersoName, self.sldPersoImportance, self.txtPersoMotivation, self.txtPersoGoal, self.txtPersoConflict, self.txtPersoEpiphany, self.txtPersoSummarySentence, self.txtPersoSummaryPara, self.txtPersoSummaryFull, self.txtPersoNotes, ]: w.setCurrentModelIndex(index) # Button color self.mdlPersos.updatePersoColor(index) # Perso Infos self.tblPersoInfos.setRootIndex(index) if self.mdlPersos.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): # Hide columns for i in range(self.mdlPersos.columnCount()): self.tblPersoInfos.hideColumn(i) self.tblPersoInfos.showColumn(Perso.infoName.value) self.tblPersoInfos.showColumn(Perso.infoData.value) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( Perso.infoName.value, QHeaderView.ResizeToContents) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( Perso.infoData.value, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() ############################################################################### # PLOTS ############################################################################### def changeCurrentPlot(self): index = self.lstPlots.currentPlotIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPlot.setEnabled(True) self.txtPlotName.setCurrentModelIndex(index) self.txtPlotDescription.setCurrentModelIndex(index) self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex(index.sibling(index.row(), Plot.persos.value)) subplotindex = index.sibling(index.row(), Plot.subplots.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex()) self.txtSubPlotSummary.setEnabled(False) self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText("") self._updatingSubPlot = False self.lstPlotPerso.selectionModel().clear() def updateSubPlotView(self): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) self.lstSubPlots.showColumn(Subplot.name.value) self.lstSubPlots.showColumn(Subplot.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( Subplot.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( Subplot.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. index = index.sibling(index.row(), Subplot.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) return self.txtSubPlotSummary.setEnabled(True) txt = item.text() self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText(txt) self._updatingSubPlot = False def updateSubPlotSummary(self): if self._updatingSubPlot: return index = self.lstSubPlots.currentIndex() if not index.isValid(): return index = index.sibling(index.row(), Subplot.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True item.setText(self.txtSubPlotSummary.toPlainText()) self._updatingSubPlot = False def plotPersoSelectionChanged(self): "Enables or disables remove plot perso button." self.btnRmPlotPerso.setEnabled( len(self.lstPlotPerso.selectedIndexes()) != 0) ############################################################################### # WORLD ############################################################################### def changeCurrentWorld(self): index = self.mdlWorld.selectedIndex() if not index.isValid(): self.tabWorld.setEnabled(False) return self.tabWorld.setEnabled(True) self.txtWorldName.setCurrentModelIndex(index) self.txtWorldDescription.setCurrentModelIndex(index) self.txtWorldPassion.setCurrentModelIndex(index) self.txtWorldConflict.setCurrentModelIndex(index) ############################################################################### # LOAD AND SAVE ############################################################################### def loadProject(self, project, loadFromFile=True): """Loads the project ``project``. If ``loadFromFile`` is False, then it does not load datas from file. It assumes that the datas have been populated in a different way.""" if loadFromFile and not os.path.exists(project): print(self.tr("The file {} does not exist. Try again.").format(project)) self.statusBar().showMessage( self.tr("The file {} does not exist. Try again.").format(project), 5000) return if loadFromFile: # Load empty settings imp.reload(settings) # Load data self.loadEmptyDatas() self.loadDatas(project) self.makeConnections() # Load settings for i in settings.openIndexes: idx = self.mdlOutline.indexFromPath(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) self.actSpellcheck.setChecked(settings.spellcheck) self.toggleSpellcheck(settings.spellcheck) self.updateMenuDict() self.setDictionary() self.mainEditor.setFolderView(settings.folderView) self.mainEditor.updateFolderViewButtons(settings.folderView) self.tabMain.setCurrentIndex(settings.lastTab) # We force to emit even if it opens on the current tab self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() # Set autosave self.saveTimer = QTimer() self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) self.saveTimer.setSingleShot(False) self.saveTimer.timeout.connect(self.saveDatas) if settings.autoSave: self.saveTimer.start() # Set autosave if no changes self.saveTimerNoChanges = QTimer() self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) self.mdlPersos.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) self.mdlStatus.dataChanged.connect(self.startTimerNoChanges) self.mdlLabels.dataChanged.connect(self.startTimerNoChanges) self.saveTimerNoChanges.timeout.connect(self.saveDatas) self.saveTimerNoChanges.stop() # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(True) # FIXME: set Window's name: project name # Stuff # self.checkPersosID() # Should'n be necessary any longer self.currentProject = project QSettings().setValue("lastProject", project) # Show main Window self.stack.setCurrentIndex(1) def closeProject(self): # Save datas self.saveDatas() self.currentProject = None QSettings().setValue("lastProject", "") # FIXME: close all opened tabs in mainEditor # Clear datas self.loadEmptyDatas() self.saveTimer.stop() # UI for i in [self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuMode, self.menuView, self.menuTools, self.menuHelp]: i.setEnabled(False) # Reload recent files self.welcome.updateValues() # Show welcome dialog self.stack.setCurrentIndex(0) def readSettings(self): # Load State and geometry sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) if sttgns.contains("geometry"): self.restoreGeometry(sttgns.value("geometry")) if sttgns.contains("windowState"): self.restoreState(sttgns.value("windowState")) else: self.dckCheatSheet.hide() self.dckSearch.hide() if sttgns.contains("metadataState"): state = [False if v == "false" else True for v in sttgns.value("metadataState")] self.redacMetadata.restoreState(state) if sttgns.contains("revisionsState"): state = [False if v == "false" else True for v in sttgns.value("revisionsState")] self.redacMetadata.revisions.restoreState(state) if sttgns.contains("splitterRedacH"): self.splitterRedacH.restoreState(sttgns.value("splitterRedacH")) if sttgns.contains("splitterRedacV"): self.splitterRedacV.restoreState(sttgns.value("splitterRedacV")) if sttgns.contains("toolbar"): # self.toolbar is not initialized yet, so we just store balue self._toolbarState = sttgns.value("toolbar") else: self._toolbarState = "" def closeEvent(self, event): # Save State and geometry and other things sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) sttgns.setValue("geometry", self.saveGeometry()) sttgns.setValue("windowState", self.saveState()) sttgns.setValue("metadataState", self.redacMetadata.saveState()) sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState()) sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState()) sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState()) sttgns.setValue("toolbar", self.toolbar.saveState()) # Specific settings to save before quitting settings.lastTab = self.tabMain.currentIndex() if self.currentProject: # Remembering the current items sel = [] for i in range(self.mainEditor.tab.count()): sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models if self.currentProject and settings.saveOnQuit: self.saveDatas() # closeEvent # QMainWindow.closeEvent(self, event) # Causin segfaults? def startTimerNoChanges(self): if settings.autoSaveNoChanges: self.saveTimerNoChanges.start() def saveDatas(self, projectName=None): """Saves the current project (in self.currentProject). If ``projectName`` is given, currentProject becomes projectName. In other words, it "saves as...". """ if projectName: self.currentProject = projectName QSettings().setValue("lastProject", projectName) # Saving files = [] files.append((saveStandardItemModelXML(self.mdlFlatData), "flatModel.xml")) files.append((saveStandardItemModelXML(self.mdlPersos), "perso.xml")) files.append((saveStandardItemModelXML(self.mdlWorld), "world.xml")) files.append((saveStandardItemModelXML(self.mdlLabels), "labels.xml")) files.append((saveStandardItemModelXML(self.mdlStatus), "status.xml")) files.append((saveStandardItemModelXML(self.mdlPlots), "plots.xml")) files.append((self.mdlOutline.saveToXML(), "outline.xml")) files.append((settings.save(), "settings.pickle")) saveFilesToZip(files, self.currentProject) # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) self.statusBar().showMessage( self.tr("Project {} saved.").format(self.currentProject), 5000) def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) self.mdlPersos = persosModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) self.mdlStatus = QStandardItemModel(self) self.mdlPlots = plotModel(self) self.mdlOutline = outlineModel(self) self.mdlWorld = worldModel(self) def loadDatas(self, project): # Loading files = loadFilesFromZip(project) errors = [] if "flatModel.xml" in files: loadStandardItemModelXML(self.mdlFlatData, files["flatModel.xml"], fromString=True) else: errors.append("flatModel.xml") if "perso.xml" in files: loadStandardItemModelXML(self.mdlPersos, files["perso.xml"], fromString=True) else: errors.append("perso.xml") if "world.xml" in files: loadStandardItemModelXML(self.mdlWorld, files["world.xml"], fromString=True) else: errors.append("world.xml") if "labels.xml" in files: loadStandardItemModelXML(self.mdlLabels, files["labels.xml"], fromString=True) else: errors.append("perso.xml") if "status.xml" in files: loadStandardItemModelXML(self.mdlStatus, files["status.xml"], fromString=True) else: errors.append("perso.xml") if "plots.xml" in files: loadStandardItemModelXML(self.mdlPlots, files["plots.xml"], fromString=True) else: errors.append("perso.xml") if "outline.xml" in files: self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True) else: errors.append("perso.xml") if "settings.pickle" in files: settings.load(files["settings.pickle"], fromString=True) else: errors.append("perso.xml") # Giving some feedback if not errors: print(self.tr("Project {} loaded.").format(project)) self.statusBar().showMessage( self.tr("Project {} loaded.").format(project), 5000) else: print(self.tr("Project {} loaded with some errors:").format(project)) for e in errors: print(self.tr(" * {} wasn't found in project file.").format(e)) self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) ############################################################################### # MAIN CONNECTIONS ############################################################################### def makeUIConnections(self): "Connections that have to be made once only, event when new project is loaded." self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) self.txtSubPlotSummary.document().contentsChanged.connect( self.updateSubPlotSummary, AUC) self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC) self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC) self.btnOutlineAddFolder.clicked.connect(self.treeOutlineOutline.addFolder, AUC) self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC) self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC) self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC) self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, AUC) self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup) def makeConnections(self): # Flat datas (Summary and general infos) for widget, col in [ (self.txtSummarySituation, 0), (self.txtSummarySentence, 1), (self.txtSummarySentence_2, 1), (self.txtSummaryPara, 2), (self.txtSummaryPara_2, 2), (self.txtPlotSummaryPara, 2), (self.txtSummaryPage, 3), (self.txtSummaryPage_2, 3), (self.txtPlotSummaryPage, 3), (self.txtSummaryFull, 4), (self.txtPlotSummaryFull, 4), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(1, col)) for widget, col in [ (self.txtGeneralTitle, 0), (self.txtGeneralSubtitle, 1), (self.txtGeneralSerie, 2), (self.txtGeneralVolume, 3), (self.txtGeneralGenre, 4), (self.txtGeneralLicense, 5), (self.txtGeneralAuthor, 6), (self.txtGeneralEmail, 7), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Persos self.lstPersos.setPersosModel(self.mdlPersos) self.tblPersoInfos.setModel(self.mdlPersos) self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC) self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC) self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC) self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC) self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC) for w, c in [ (self.txtPersoName, Perso.name.value), (self.sldPersoImportance, Perso.importance.value), (self.txtPersoMotivation, Perso.motivation.value), (self.txtPersoGoal, Perso.goal.value), (self.txtPersoConflict, Perso.conflict.value), (self.txtPersoEpiphany, Perso.epiphany.value), (self.txtPersoSummarySentence, Perso.summarySentence.value), (self.txtPersoSummaryPara, Perso.summaryPara.value), (self.txtPersoSummaryFull, Perso.summaryFull.value), (self.txtPersoNotes, Perso.notes.value) ]: w.setModel(self.mdlPersos) w.setColumn(c) self.tabPersos.setEnabled(False) # Plots self.lstPlots.setPlotModel(self.mdlPlots) self.lstPlotPerso.setModel(self.mdlPlots) self.lstSubPlots.setModel(self.mdlPlots) self._updatingSubPlot = False self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC) self.btnRmPlot.clicked.connect(lambda: self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC) self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC) self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC) self.lstPlotPerso.selectionModel().selectionChanged.connect(self.plotPersoSelectionChanged) self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC) for w, c in [ (self.txtPlotName, Plot.name.value), (self.txtPlotDescription, Plot.description.value), (self.txtPlotResult, Plot.result.value), (self.sldPlotImportance, Plot.importance.value), ]: w.setModel(self.mdlPlots) w.setColumn(c) self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self) self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) for i in range(self.mdlWorld.columnCount()): self.treeWorld.hideColumn(i) self.treeWorld.showColumn(0) self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu()) self.treeWorld.selectionModel().selectionChanged.connect(self.changeCurrentWorld, AUC) self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC) self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC) for w, c in [ (self.txtWorldName, World.name.value), (self.txtWorldDescription, World.description.value), (self.txtWorldPassion, World.passion.value), (self.txtWorldConflict, World.conflict.value), ]: w.setModel(self.mdlWorld) w.setColumn(c) self.tabWorld.setEnabled(False) self.treeWorld.expandAll() # Outline self.treeRedacOutline.setModel(self.mdlOutline) self.treeOutlineOutline.setModelPersos(self.mdlPersos) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos, self.mdlLabels, self.mdlStatus) self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) self.treeOutlineOutline.clicked.connect(lambda: self.outlineItemEditor.selectionChanged(self.treeOutlineOutline), AUC) # Sync selection self.treeRedacOutline.selectionModel().selectionChanged.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.clicked.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.selectionModel().selectionChanged.connect(self.mainEditor.selectionChanged, AUC) # Cheat Sheet self.cheatSheet.setModels() # Debug self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) self.tblDebugPersos.setModel(self.mdlPersos) self.tblDebugPersosInfos.setModel(self.mdlPersos) self.tblDebugPersos.selectionModel().currentChanged.connect( lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index( self.tblDebugPersos.selectionModel().currentIndex().row(), Perso.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) self.tblDebugSubPlots.setModel(self.mdlPlots) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.persos.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.subplots.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) self.lstDebugStatus.setModel(self.mdlStatus) ############################################################################### # GENERAL AKA UNSORTED ############################################################################### def clickCycle(self, i): if i == 0: # step 2 - paragraph summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(1) if i == 1: # step 3 - characters summary self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(0) if i == 2: # step 4 - page summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(2) if i == 3: # step 5 - characters description self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(1) if i == 4: # step 6 - four page synopsis self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(3) if i == 5: # step 7 - full character charts self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(2) if i == 6: # step 8 - scene list self.tabMain.setCurrentIndex(self.TabPlots) def wordCount(self, i): src = { 0: self.txtSummarySentence, 1: self.txtSummaryPara, 2: self.txtSummaryPage, 3: self.txtSummaryFull }[i] lbl = { 0: self.lblSummaryWCSentence, 1: self.lblSummaryWCPara, 2: self.lblSummaryWCPage, 3: self.lblSummaryWCFull }[i] wc = wordCount(src.toPlainText()) if i in [2, 3]: pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.) else: pages = "" lbl.setText(self.tr("Words: {}{}").format(wc, pages)) def setupMoreUi(self): # Tool bar on the right self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self) self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots) self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) if self._toolbarState: self.toolbar.restoreState(self._toolbarState) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) for i in range(self.tabMain.count()): icons = ["general-128px.png", "summary-128px.png", "characters-128px.png", "plot-128px.png", "world-128px.png", "outline-128px.png", "redaction-128px.png", "" ] self.tabMain.setTabIcon(i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i])))) item = QListWidgetItem(self.tabMain.tabIcon(i), self.tabMain.tabText(i)) item.setSizeHint(QSize(item.sizeHint().width(), 64)) item.setTextAlignment(Qt.AlignCenter) self.lstTabs.addItem(item) self.tabMain.tabBar().hide() self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex) self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow) # Splitters self.splitterPersos.setStretchFactor(0, 25) self.splitterPersos.setStretchFactor(1, 75) self.splitterPlot.setStretchFactor(0, 20) self.splitterPlot.setStretchFactor(1, 60) self.splitterPlot.setStretchFactor(2, 30) self.splitterWorld.setStretchFactor(0, 25) self.splitterWorld.setStretchFactor(1, 75) self.splitterOutlineH.setStretchFactor(0, 25) self.splitterOutlineH.setStretchFactor(1, 75) self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) self.splitterRedacV.setStretchFactor(0, 75) self.splitterRedacV.setStretchFactor(1, 25) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) self.splitterRedacH.setStretchFactor(2, 30) # QFormLayout stretch for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]: s = w.sizePolicy() s.setVerticalStretch(1) w.setSizePolicy(s) # Help box references = [ (self.lytTabOverview, self.tr("Enter infos about your book, and yourself."), 0), (self.lytSituation, self.tr( """The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter)"""), 1), (self.lytSummary, self.tr( """Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary."""), 1), (self.lytTabPersos, self.tr("Create your characters."), 0), (self.lytTabPlot, self.tr("Develop plots."), 0), (self.lytTabOutline, self.tr("Create the outline of your masterpiece."), 0), (self.lytTabRedac, self.tr("Write."), 0), (self.lytTabDebug, self.tr("Debug infos. Sometimes useful."), 0) ] for widget, text, pos in references: label = helpLabel(text, self) self.actShowHelp.toggled.connect(label.setVisible, AUC) widget.layout().insertWidget(pos, label) self.actShowHelp.setChecked(False) # Spellcheck if enchant: self.menuDict = QMenu(self.tr("Dictionary")) self.menuDictGroup = QActionGroup(self) self.updateMenuDict() self.menuTools.addMenu(self.menuDict) self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC) self.dictChanged.connect(self.mainEditor.setDict, AUC) self.dictChanged.connect(self.redacMetadata.setDict, AUC) self.dictChanged.connect(self.outlineItemEditor.setDict, AUC) else: # No Spell check support self.actSpellcheck.setVisible(False) a = QAction(self.tr("Install PyEnchant to use spellcheck"), self) a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning)) a.triggered.connect(self.openPyEnchantWebPage, AUC) self.menuTools.addAction(a) ############################################################################### # SPELLCHECK ############################################################################### def updateMenuDict(self): if not enchant: return self.menuDict.clear() for i in enchant.list_dicts(): a = QAction(str(i[0]), self) a.setCheckable(True) if settings.dict is None: settings.dict = enchant.get_default_language() if str(i[0]) == settings.dict: a.setChecked(True) a.triggered.connect(self.setDictionary, AUC) self.menuDictGroup.addAction(a) self.menuDict.addAction(a) def setDictionary(self): if not enchant: return for i in self.menuDictGroup.actions(): if i.isChecked(): # self.dictChanged.emit(i.text().replace("&", "")) settings.dict = i.text().replace("&", "") # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.setDict(settings.dict) def openPyEnchantWebPage(self): from PyQt5.QtGui import QDesktopServices QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/")) def toggleSpellcheck(self, val): settings.spellcheck = val # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.toggleSpellcheck(val) ############################################################################### # SETTINGS ############################################################################### def settingsLabel(self): self.settingsWindow(3) def settingsStatus(self): self.settingsWindow(4) def settingsWindow(self, tab=None): self.sw = settingsWindow(self) self.sw.hide() self.sw.setWindowModality(Qt.ApplicationModal) self.sw.setWindowFlags(Qt.Dialog) r = self.sw.geometry() r2 = self.geometry() self.sw.move(r2.center() - r.center()) if tab: self.sw.setTab(tab) self.sw.show() ############################################################################### # TOOLS ############################################################################### def frequencyAnalyzer(self): self.fw = frequencyAnalyzer(self) self.fw.show() ############################################################################### # VIEW MENU ############################################################################### def generateViewMenu(self): values = [ (self.tr("Nothing"), "Nothing"), (self.tr("POV"), "POV"), (self.tr("Label"), "Label"), (self.tr("Progress"), "Progress"), (self.tr("Compile"), "Compile"), ] menus = [ (self.tr("Tree"), "Tree"), (self.tr("Index cards"), "Cork"), (self.tr("Outline"), "Outline") ] submenus = { "Tree": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], "Cork": [ (self.tr("Icon"), "Icon"), (self.tr("Text"), "Text"), (self.tr("Background"), "Background"), (self.tr("Border"), "Border"), (self.tr("Corner"), "Corner"), ], "Outline": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], } self.menuView.clear() # print("Generating menus with", settings.viewSettings) for mnu, mnud in menus: m = QMenu(mnu, self.menuView) for s, sd in submenus[mnud]: m2 = QMenu(s, m) agp = QActionGroup(m2) for v, vd in values: a = QAction(v, m) a.setCheckable(True) a.setData("{},{},{}".format(mnud, sd, vd)) if settings.viewSettings[mnud][sd] == vd: a.setChecked(True) a.triggered.connect(self.setViewSettingsAction, AUC) agp.addAction(a) m2.addAction(a) m.addMenu(m2) self.menuView.addMenu(m) def setViewSettingsAction(self): action = self.sender() item, part, element = action.data().split(",") self.setViewSettings(item, part, element) def setViewSettings(self, item, part, element): settings.viewSettings[item][part] = element if item == "Cork": self.mainEditor.updateCorkView() if item == "Outline": self.mainEditor.updateTreeView() self.treeOutlineOutline.viewport().update() if item == "Tree": self.treeRedacOutline.viewport().update() ############################################################################### # COMPILE ############################################################################### def doCompile(self): self.compileDialog = compileDialog() self.compileDialog.show()
class PlotWidget(QMainWindow): def __init__(self, window, genotypes, fitness, forces, bridge, fitness_graph=None, threshold_graph=None): super().__init__(window) self.genotypes = genotypes self.fitness = fitness self.forces = forces self.bridge = bridge self.threshold_graph = threshold_graph self.fitness_graph = fitness_graph self.cached_canvas = [None]*len(genotypes) self.current_fig = None self.current_toolbar = None self.current_canvas = None toolbar = QToolBar("Individuals") self.addToolBar(Qt.BottomToolBarArea, toolbar) self.actiongroup = QActionGroup(self) for i in range(len(self.genotypes)): action = QAction(QIcon(), str(i+1), self) action.setCheckable(True) self.actiongroup.addAction(action) self.actiongroup.triggered.connect(self.plot_genotype) self.boxlayout = QVBoxLayout() toolbar.addActions(self.actiongroup.actions()) self.actiongroup.setExclusive(True) self.actiongroup.actions()[0].activate(QAction.Trigger) # self.plot(0) @pyqtSlot(QAction) def plot_genotype(self, action: QAction): idx = self.actiongroup.actions().index(action) self.plot(idx) def onpick(self, event): self.plot(event.ind[0]) self.actiongroup.actions()[event.ind[0]].activate(QAction.Trigger) def plot(self, index: int): if self.current_toolbar is not None: plt.close(self.current_fig) self.removeToolBar(self.current_toolbar) self.current_toolbar = None statically_correct = True (x, det, MFmat, RFmat) = self.forces[index] if x is None or x != 0 or det == 0: statically_correct = False b = self.bridge b.setGenotype(self.genotypes[index]) genotype = b.genotype supports = b.supports nodes = b.nodes members = b.members materials = b.materials loads = b.loads force, cost, length = self.fitness[index] tmat = np.transpose(nodes[members], axes=(0, 2, 1)) submat = np.subtract.reduce(tmat, axis=2) member_lengths = np.hypot.reduce(submat, axis=1, dtype=float) max_member_lengths = np.array([MAX_MEMBER_LENGTHS[ int(m)] for m in materials]).reshape(-1, 1) rows = int(self.fitness_graph is not None) * 100 fig = plt.figure(figsize=plt.figaspect( 0.5 if self.fitness_graph is not None else 0.33)) # fig, axs = plt.subplots(rows, 1, figsize=figsize) self.current_fig = fig canvas = FigureCanvas(fig) ax = plt.subplot(121 + rows) material_labels = ["Street", "Wood", "Steel"] colors = ["black", "peru", "brown"] cmap = ListedColormap(colors, name="materials") norm = col.Normalize(vmin=0, vmax=2) for i in range(len(members[:, 0])): ls = ":" if member_lengths[i] > max_member_lengths[i] else "-" c = cmap(norm(int(materials[i, 0]))) l = material_labels[int(materials[i])] ax.plot(nodes[members[i, :], 0], nodes[members[i, :], 1], linestyle=ls, color=c, label=l, lw=5) def legend_without_duplicate_labels(ax): handles, labels = ax.get_legend_handles_labels() unique = [(h, l) for i, (h, l) in enumerate( zip(handles, labels)) if l not in labels[:i]] ax.legend(*zip(*unique)) mask = np.ones(nodes.shape[0], dtype=bool) mask[supports[:, 0].ravel()] = 0 _nodes = nodes[mask].reshape(-1, 2) _supports = nodes[np.invert(mask)].reshape(-1, 2) ax.scatter(_nodes[:, 0], _nodes[:, 1], s=100, zorder=3, color="w", edgecolors="black") ax.scatter(_supports[:, 0], _supports[:, 1], s=100, marker="s", zorder=3, color="w", edgecolors="black") ax.set_aspect('equal', adjustable='box') plt.grid() if statically_correct: ax = plt.subplot(122 + rows) cmap = plt.get_cmap('coolwarm') abs_val = np.abs(MFmat) max_abs = np.max(abs_val) norm = col.Normalize(vmin=-max_abs, vmax=max_abs) for i in range(len(members[:, 0])): f = MFmat[i, 0] # TODO out of bounds ls = ":" if member_lengths[i] > max_member_lengths[i] else "-" ax.plot(nodes[members[i, :], 0], nodes[members[i, :], 1], linestyle=ls, color=cmap(norm(f)), lw=5) _nodes = nodes[members[i, :]] center = (np.sum(_nodes, axis=0)/2.0).reshape(2, -1) if np.abs(f) < 1e-12: c = "k" elif f < 0: c = "blue" else: c = "red" ax.text(*center, "{:.1f}".format(f), backgroundcolor="w", color=c, weight="medium") mask = np.ones(nodes.shape[0], dtype=bool) mask[supports[:, 0].ravel()] = 0 _nodes = nodes[mask].reshape(-1, 2) _supports = nodes[np.invert(mask)].reshape(-1, 2) ax.scatter(_nodes[:, 0], _nodes[:, 1], s=100, zorder=3, color="w", edgecolors="black") ax.scatter(_supports[:, 0], _supports[:, 1], s=100, marker="s", zorder=3, color="w", edgecolors="black") # plt.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax) ax2_divider = make_axes_locatable(ax) cax2 = ax2_divider.append_axes("right", size="7%", pad="2%") cb2 = fig.colorbar(cm.ScalarMappable( norm=norm, cmap=cmap), cax=cax2, orientation="vertical") # cax2.xaxis.set_ticks_position("right") ax.set_aspect('equal', adjustable='box') fig.suptitle( "Forces: {:0.5f}, Costs: {:0.5f}, Lenghts: {:0.5f}".format(force, cost, length)) if self.fitness_graph: if len(self.fitness) > 1: ax = plt.subplot(124 + rows, projection='3d') X = self.fitness face_col = normalize(X, norm="max", axis=0) ax.scatter(X[:, 0], X[:, 1], zs=X[:, 2], facecolors=face_col, marker="o", picker=True) ax.set_xlabel("Forces") ax.set_ylabel("Costs") ax.set_zlabel("Lengths") ax = plt.subplot(123 + rows) else: ax = plt.subplot(112 + rows) # ax.set_yscale("log") color = 'tab:blue' ax.plot(range(len(self.fitness_graph)), self.fitness_graph, label="Best Fitness", color=color) ax.set_ylabel("Fitness", color=color) ax.set_xlabel("Iterations") ax.tick_params(axis='y', labelcolor=color) ax.grid() if self.threshold_graph: ax = ax.twinx() # instantiate a second axes that shares the same x-axis color = "tab:orange" # we already handled the x-label with ax1 ax.set_ylabel('Threshold', color=color) ax.plot(range(len(self.threshold_graph)), self.threshold_graph, label="Accept", color=color) ax.tick_params(axis='y', labelcolor=color) fig.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) fig.canvas.mpl_connect('pick_event', self.onpick) self.current_fig = fig self.current_canvas = canvas self.current_toolbar = NavigationToolbar(canvas, self) self.current_toolbar.setObjectName("Figure Control") self.addToolBar(Qt.BottomToolBarArea, self.current_toolbar) self.insertToolBarBreak(self.current_toolbar) self.setCentralWidget(canvas)
class StripRacksPane(QWidget, Ui_stripPane): view_modes = DIVIDE, SCROLL, TABBED = range( 3) # NB: order matters for menu action descriptions windowClosing = pyqtSignal() # workspace-level signal def __init__(self, parent=None): QWidget.__init__(self, parent) self.setupUi(self) self.pull_menuButton.setIcon(QIcon(IconFile.pixmap_strip)) self.deleteStrip_button.setIcon(QIcon(IconFile.button_bin)) self.rack_filter_model = RackedStripsFilterModel(self) self.stripRacks_view.setModel(self.rack_filter_model) self.current_view_mode = StripRacksPane.DIVIDE self.updateViewFromMode() # PULL menu self.pullFplDetails_action = QAction('Pull FPL details', self) self.pullXpdrDetails_action = QAction('Pull XPDR details', self) pull_menu = QMenu() pull_menu.addAction(self.pullFplDetails_action) pull_menu.addAction(self.pullXpdrDetails_action) self.pullFplDetails_action.triggered.connect( lambda: pull_FPL_details(self)) self.pullXpdrDetails_action.triggered.connect( lambda: pull_XPDR_details(self)) self.pull_menuButton.setMenu(pull_menu) # VIEW menu createNewRack_action = QAction('New rack...', self) createNewRack_action.setIcon(QIcon(IconFile.action_newRack)) createNewRack_action.triggered.connect(self.createNewRack) moveRacks_action = QAction('Bring racks to this view...', self) moveRacks_action.triggered.connect(self.moveRacksToView) self.view_mode_action_group = QActionGroup(self) for mode, txt in zip( StripRacksPane.view_modes, ['Divided width', 'Horizontal scroll', 'Tabbed racks']): view_action = QAction(txt, self) view_action.setCheckable(True) view_action.triggered.connect( lambda toggle, m=mode: self.selectViewMode(m)) self.view_mode_action_group.addAction(view_action) if mode == self.current_view_mode: view_action.setChecked(True) view_menu = QMenu() view_menu.addAction(createNewRack_action) view_menu.addAction(moveRacks_action) view_menu.addSeparator() view_menu.addActions(self.view_mode_action_group.actions()) self.view_menuButton.setMenu(view_menu) # Other buttons and signals self.updateButtonsAndActions() self.deleteStrip_button.clicked.connect( lambda: discard_strip(self, selection.strip, False)) self.shelf_button.stripDropped.connect( lambda strip: discard_strip(self, strip, True)) self.shelf_button.clicked.connect(signals.openShelfRequest.emit) # External signals below. CAUTION: these must all be disconnected on widget deletion env.strips.columnsRemoved.connect(self.updateAfterRackDeletion) signals.rackEdited.connect(self.updateAfterRackEdit) signals.rackVisibilityTaken.connect(self.hideRacks) signals.selectionChanged.connect(self.updateSelections) signals.selectionChanged.connect(self.updateButtonsAndActions) signals.mainWindowClosing.connect(self.close) def getViewRacks(self): ''' returns the racks currently shown in the panel, in visible order (whether tabbed or in columns) ''' if self.stacked_view_widget.currentWidget() is self.tableView_page: hh = self.stripRacks_view.horizontalHeader() return [ self.rack_filter_model.rackName(hh.logicalIndex(i)) for i in range(hh.count()) ] else: return [ w.singleRackFilter() for w in self.stripRacks_tabs.rackTabs() ] def setViewRacks(self, racks): self.rack_filter_model.setRackFilter(racks) hh = self.stripRacks_view.horizontalHeader() for vis, rack in enumerate(racks): try: curr_vis = next(i for i in range(vis + 1, hh.count()) if self.rack_filter_model.rackName( hh.logicalIndex(i)) == rack) hh.moveSection(curr_vis, vis) except StopIteration: pass self.stripRacks_tabs.setTabs(racks) self.updateViewFromMode() ## GUI UPDATES def updateAfterRackEdit(self, old_name, new_name): if new_name != old_name: self.rack_filter_model.updateRackFilter({old_name: new_name}) self.stripRacks_tabs.updateTabName(old_name, new_name) self.stripRacks_tabs.updateTabIcons() self.updateSelections() def updateAfterRackDeletion(self): self.rack_filter_model.updateRackFilter({}) still_existing = env.strips.rackNames() self.stripRacks_tabs.setTabs( [r for r in self.getViewRacks() if r in still_existing]) self.updateViewFromMode() self.updateSelections() def updateSelections(self): self.stripRacks_view.updateSelection() self.stripRacks_tabs.updateSelection() def updateViewFromMode(self): if self.current_view_mode == StripRacksPane.TABBED: self.stacked_view_widget.setCurrentWidget(self.tabView_page) self.stripRacks_tabs.updateTabIcons() else: self.stacked_view_widget.setCurrentWidget(self.tableView_page) self.stripRacks_view.setDivideHorizWidth( self.current_view_mode == StripRacksPane.DIVIDE) def updateButtonsAndActions(self): self.pull_menuButton.setEnabled(selection.strip != None) self.pullXpdrDetails_action.setEnabled( selection.strip != None and selection.strip.linkedAircraft() != None) self.pullFplDetails_action.setEnabled( selection.strip != None and selection.strip.linkedFPL() != None) self.deleteStrip_button.setEnabled( selection.strip != None and selection.strip.lookup(rack_detail) != None) def hideRacks(self, racks): self.setViewRacks([r for r in self.getViewRacks() if r not in racks]) ## ACTIONS def createNewRack(self): i = 1 new_rack_name = 'Rack 1' while not env.strips.validNewRackName(new_rack_name): i += 1 new_rack_name = 'Rack %d' % i env.strips.addRack(new_rack_name) self.setViewRacks(self.getViewRacks() + [new_rack_name]) def moveRacksToView(self): available_racks = [ r for r in env.strips.rackNames() if r not in self.getViewRacks() ] dialog = RackVisibilityDialog(available_racks, parent=self) dialog.exec() if dialog.result() > 0: new_rack_visibility = self.getViewRacks() + dialog.getSelection() signals.rackVisibilityTaken.emit(new_rack_visibility) self.setViewRacks(new_rack_visibility) def selectViewMode(self, view_mode): rack_order_to_replicate = self.getViewRacks( ) # views not necessarily in sync if sections/tabs were moved self.current_view_mode = view_mode self.updateViewFromMode() self.setViewRacks(rack_order_to_replicate) ## SAVED STATES def stateSave(self): return { 'view_mode': str(self.current_view_mode), 'visible_racks': ','.join( str(env.strips.rackIndex(r)) for r in self.getViewRacks()) } def restoreState(self, saved_state): try: view_mode = int(saved_state['view_mode']) view_mode_action = self.view_mode_action_group.actions()[view_mode] view_mode_action.setChecked(True) view_mode_action.trigger() except (KeyError, IndexError, ValueError): pass # missing or invalid view mode state attr try: racks = [ env.strips.rackName(int(ir)) for ir in saved_state['visible_racks'].split(',') ] signals.rackVisibilityTaken.emit(racks) self.setViewRacks(racks) except KeyError: pass # no visible racks saved except (IndexError, ValueError): pass # bad int list value ## CLOSING def closeEvent(self, event): env.strips.columnsRemoved.disconnect(self.updateAfterRackDeletion) signals.mainWindowClosing.disconnect(self.close) signals.selectionChanged.disconnect(self.updateButtonsAndActions) signals.selectionChanged.disconnect(self.updateSelections) signals.rackEdited.disconnect(self.updateAfterRackEdit) signals.rackVisibilityTaken.disconnect(self.hideRacks) event.accept() signals.rackVisibilityLost.emit(self.getViewRacks()) self.windowClosing.emit() QWidget.closeEvent(self, event)
class Gui(QMainWindow, Ui_MainWindow): NOTEON = 0x9 NOTEOFF = 0x8 MIDICTRL = 11 GREEN = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(125,242,0);}") BLUE = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(0, 130, 240);}") RED = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(255, 21, 65);}") AMBER = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(255, 102, 0);}") PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(130, 0, 240);}") DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(217, 217, 217);}") RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}" "QPushButton:pressed {background-color: " "rgb(98, 98, 98);}") RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}" "QPushButton:pressed {background-color: " "rgb(98, 98, 98);}") STATE_COLORS = {Clip.STOP: RED, Clip.STARTING: GREEN, Clip.START: GREEN, Clip.STOPPING: RED, Clip.PREPARE_RECORD: AMBER, Clip.RECORDING: AMBER} STATE_BLINK = {Clip.STOP: False, Clip.STARTING: True, Clip.START: False, Clip.STOPPING: True, Clip.PREPARE_RECORD: True, Clip.RECORDING: False} BLINK_DURATION = 200 PROGRESS_PERIOD = 300 ADD_PORT_LABEL = 'Add new Port...' updateUi = pyqtSignal() readQueueIn = pyqtSignal() updatePorts = pyqtSignal() songLoad = pyqtSignal() def __init__(self, song, jack_client): QObject.__init__(self) super(Gui, self).__init__() self._jack_client = jack_client self.setupUi(self) self.clip_volume.knobRadius = 3 self.is_learn_device_mode = False self.queue_out, self.queue_in = Queue(), Queue() self.updateUi.connect(self.update) self.readQueueIn.connect(self.readQueue) self.current_vol_block = 0 self.last_clip = None # Load devices self.deviceGroup = QActionGroup(self.menuDevice) self.devices = [] device_settings = QSettings('superboucle', 'devices') if ((device_settings.contains('devices') and device_settings.value('devices'))): for raw_device in device_settings.value('devices'): self.devices.append(Device(pickle.loads(raw_device))) else: self.devices.append(Device({'name': 'No Device',})) self.updateDevices() self.deviceGroup.triggered.connect(self.onDeviceSelect) self.settings = QSettings('superboucle', 'session') # Qsetting appear to serialize empty lists as @QInvalid # which is then read as None :( # Load playlist self.playlist = self.settings.value('playlist', []) or [] # Load paths self.paths_used = self.settings.value('paths_used', {}) self.auto_connect = self.settings.value('auto_connect', 'true') == "true" # Load song self.port_by_name = {} self.initUI(song) self.actionNew.triggered.connect(self.onActionNew) self.actionOpen.triggered.connect(self.onActionOpen) self.actionSave.triggered.connect(self.onActionSave) self.actionSave_As.triggered.connect(self.onActionSaveAs) self.actionAdd_Device.triggered.connect(self.onAddDevice) self.actionManage_Devices.triggered.connect(self.onManageDevice) self.actionPlaylist_Editor.triggered.connect(self.onPlaylistEditor) self.actionScene_Manager.triggered.connect(self.onSceneManager) self.actionPort_Manager.triggered.connect(self.onPortManager) self.actionFullScreen.triggered.connect(self.onActionFullScreen) self.master_volume.valueChanged.connect(self.onMasterVolumeChange) self.bpm.valueChanged.connect(self.onBpmChange) self.beat_per_bar.valueChanged.connect(self.onBeatPerBarChange) self.rewindButton.clicked.connect(self.onRewindClicked) self.playButton.clicked.connect(self._jack_client.transport_start) self.pauseButton.clicked.connect(self._jack_client.transport_stop) self.gotoButton.clicked.connect(self.onGotoClicked) self.recordButton.clicked.connect(self.onRecord) self.clip_name.textChanged.connect(self.onClipNameChange) self.clip_volume.valueChanged.connect(self.onClipVolumeChange) self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange) self.output.activated.connect(self.onOutputChange) self.mute_group.valueChanged.connect(self.onMuteGroupChange) self.frame_offset.valueChanged.connect(self.onFrameOffsetChange) self.beat_offset.valueChanged.connect(self.onBeatOffsetChange) self.revertButton.clicked.connect(self.onRevertClip) self.normalizeButton.clicked.connect(self.onNormalizeClip) self.exportButton.clicked.connect(self.onExportClip) self.deleteButton.clicked.connect(self.onDeleteClipClicked) self.blktimer = QTimer() self.blktimer.state = False self.blktimer.timeout.connect(self.toggleBlinkButton) self.blktimer.start(self.BLINK_DURATION) self.disptimer = QTimer() self.disptimer.start(self.PROGRESS_PERIOD) self.disptimer.timeout.connect(self.updateProgress) self._jack_client.set_timebase_callback(self.timebase_callback) self.show() def initUI(self, song): # remove old buttons self.btn_matrix = [[None for y in range(song.height)] for x in range(song.width)] self.state_matrix = [[-1 for y in range(song.height)] for x in range(song.width)] for i in reversed(range(self.gridLayout.count())): self.gridLayout.itemAt(i).widget().close() self.gridLayout.itemAt(i).widget().setParent(None) # first pass without removing old ports self.updateJackPorts(song, remove_ports=False) self.song = song # second pass with removing self.updateJackPorts(song, remove_ports=True) self.frame_clip.setEnabled(False) self.output.clear() self.output.addItems(song.outputsPorts) self.output.addItem(Gui.ADD_PORT_LABEL) self.master_volume.setValue(song.volume * 256) self.bpm.setValue(song.bpm) self.beat_per_bar.setValue(song.beat_per_bar) for x in range(song.width): for y in range(song.height): clip = song.clips_matrix[x][y] cell = Cell(self, clip, x, y) self.btn_matrix[x][y] = cell self.gridLayout.addWidget(cell, y, x) # send init command for init_cmd in self.device.init_command: self.queue_out.put(init_cmd) self.setWindowTitle("Super Boucle - {}" .format(song.file_name or "Empty Song")) if self.song.initial_scene in self.song.scenes: self.song.loadScene(self.song.initial_scene) self.update() self.songLoad.emit() timer = QTimer() timer.singleShot(1000,self.send_clip_state_feedback) def openSongFromDisk(self, file_name): self._jack_client.transport_stop() self._jack_client.transport_locate(0) self.setEnabled(False) message = QMessageBox(self) message.setWindowTitle("Loading ....") message.setText("Reading Files, please wait ...") message.show() self.initUI(load_song_from_file(file_name)) message.close() self.setEnabled(True) def closeEvent(self, event): device_settings = QSettings('superboucle', 'devices') device_settings.setValue('devices', [pickle.dumps(x.mapping) for x in self.devices]) self.settings.setValue('playlist', self.playlist) self.settings.setValue('paths_used', self.paths_used) self.settings.setValue('auto_connect', self.auto_connect) def onStartStopClicked(self): clip = self.sender().parent().parent().clip self.startStop(clip.x, clip.y) def startStop(self, x, y): clip = self.btn_matrix[x][y].clip if clip is None: return if self.song.is_record: self.song.is_record = False self.updateRecordBtn() # calculate buffer size state, position = self._jack_client.transport_query() bps = position['beats_per_minute'] / 60 fps = position['frame_rate'] size = (1 / bps) * clip.beat_diviser * fps self.song.init_record_buffer(clip, 2, size, fps) # set frame offset based on jack block size clip.frame_offset = self._jack_client.blocksize clip.state = Clip.PREPARE_RECORD self.recordButton.setStyleSheet(self.RECORD_DEFAULT) else: self.song.toggle(clip.x, clip.y) self.update() def onEdit(self): self.last_clip = self.sender().parent().parent().clip if self.last_clip: self.frame_clip.setEnabled(True) self.clip_name.setText(self.last_clip.name) self.frame_offset.setValue(self.last_clip.frame_offset) self.beat_offset.setValue(self.last_clip.beat_offset) self.beat_diviser.setValue(self.last_clip.beat_diviser) self.output.setCurrentText(self.last_clip.output) self.mute_group.setValue(self.last_clip.mute_group) self.clip_volume.setValue(self.last_clip.volume * 256) state, position = self._jack_client.transport_query() fps = position['frame_rate'] bps = self.bpm.value() / 60 if self.bpm.value() and fps: size_in_beat = (bps / fps) * self.song.length(self.last_clip) else: size_in_beat = "No BPM info" clip_description = ("Size in sample : %s\nSize in beat : %s" % (self.song.length(self.last_clip), round(size_in_beat, 1))) self.clip_description.setText(clip_description) def onAddClipClicked(self): cell = self.sender().parent().parent() if QApplication.keyboardModifiers() == Qt.ControlModifier: cell.setClip(cell.openClip()) else: AddClipDialog(self, cell) def onRevertClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file self.song.data[audio_file] = self.song.data[audio_file][::-1] def onNormalizeClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file absolute_val = np.absolute(self.song.data[audio_file]) current_level = np.ndarray.max(absolute_val) self.song.data[audio_file][:] *= (1 / current_level) def onExportClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file file_name, a = self.getSaveFileName( 'Export Clip : %s' % self.last_clip.name, 'WAVE (*.wav)') if file_name: file_name = verify_ext(file_name, 'wav') sf.write(self.song.data[audio_file], file_name, self.song.samplerate[audio_file], subtype=sf.default_subtype('WAV'), format='WAV') def onDeleteClipClicked(self): if self.last_clip: response = QMessageBox.question(self, "Delete Clip ?", ("Are you sure " "to delete the clip ?")) if response == QMessageBox.Yes: self.frame_clip.setEnabled(False) self.song.removeClip(self.last_clip) self.initUI(self.song) def onMasterVolumeChange(self): self.song.volume = (self.master_volume.value() / 256) def onBpmChange(self): self.song.bpm = self.bpm.value() def onBeatPerBarChange(self): self.song.beat_per_bar = self.beat_per_bar.value() def onGotoClicked(self): state, position = self._jack_client.transport_query() new_position = (position['beats_per_bar'] * (self.gotoTarget.value() - 1) * position['frame_rate'] * (60 / position['beats_per_minute'])) self._jack_client.transport_locate(int(round(new_position, 0))) def onRecord(self): self.song.is_record = not self.song.is_record self.updateRecordBtn() def updateRecordBtn(self): if not self.song.is_record: self.recordButton.setStyleSheet(self.RECORD_DEFAULT) if self.device.record_btn: (msg_type, channel, pitch, velocity) = self.device.record_btn if self.song.is_record: color = self.device.blink_amber_vel else: color = self.device.black_vel self.queue_out.put(((msg_type << 4) + channel, pitch, color)) def onRewindClicked(self): self._jack_client.transport_locate(0) def onClipNameChange(self): self.last_clip.name = self.clip_name.text() cell = self.btn_matrix[self.last_clip.x][self.last_clip.y] cell.clip_name.setText(self.last_clip.name) def onClipVolumeChange(self): self.last_clip.volume = (self.clip_volume.value() / 256) def onBeatDiviserChange(self): self.last_clip.beat_diviser = self.beat_diviser.value() def onOutputChange(self): new_port = self.output.currentText() if new_port == Gui.ADD_PORT_LABEL: AddPortDialog(self) else: self.last_clip.output = new_port def addPort(self, name): self.song.outputsPorts.add(name) self.updateJackPorts(self.song) if self.output.findText(name) == -1: self.output.insertItem(self.output.count() - 1, name) if self.last_clip: self.last_clip.output = name self.output.setCurrentText(name) def removePort(self, name): if name != Clip.DEFAULT_OUTPUT: self.song.outputsPorts.remove(name) for c in self.song.clips: if c.output == name: c.output = Clip.DEFAULT_OUTPUT self.updateJackPorts(self.song) self.output.removeItem(self.output.findText(name)) if self.last_clip: self.output.setCurrentText(self.last_clip.output) def updateJackPorts(self, song, remove_ports=True): '''Update jack port based on clip output settings update dict containing ports with shortname as key''' current_ports = set() for port in self._jack_client.outports: current_ports.add(port.shortname) wanted_ports = set() for port_basename in song.outputsPorts: for ch in Song.CHANNEL_NAMES: port = Song.CHANNEL_NAME_PATTERN.format(port=port_basename, channel=ch) wanted_ports.add(port) # remove unwanted ports if remove_ports: port_to_remove = [] for port in self._jack_client.outports: if port.shortname not in wanted_ports: current_ports.remove(port.shortname) port_to_remove.append(port) for port in port_to_remove: port.unregister() # create new ports for new_port_name in wanted_ports - current_ports: self._jack_client.outports.register(new_port_name) self.port_by_name = {port.shortname: port for port in self._jack_client.outports} self.updatePorts.emit() def onMuteGroupChange(self): self.last_clip.mute_group = self.mute_group.value() def onFrameOffsetChange(self): self.last_clip.frame_offset = self.frame_offset.value() def onBeatOffsetChange(self): self.last_clip.beat_offset = self.beat_offset.value() def onActionNew(self): NewSongDialog(self) def getOpenFileName(self, title, file_type, parent=None, dialog=QFileDialog.getOpenFileName): path = self.paths_used.get(file_type, expanduser('~')) file_name, a = dialog(parent or self, title, path, file_type) if a and file_name: if isinstance(file_name, list): self.paths_used[file_type] = dirname(file_name[0]) else: self.paths_used[file_type] = dirname(file_name) return file_name, a def getSaveFileName(self, *args): return self.getOpenFileName(*args, dialog=QFileDialog.getSaveFileName) def onActionOpen(self): file_name, a = self.getOpenFileName('Open Song', 'Super Boucle Song (*.sbs)') if a and file_name: self.openSongFromDisk(file_name) def onActionSave(self): if self.song.file_name: self.song.save() else: self.onActionSaveAs() def onActionSaveAs(self): file_name, a = self.getSaveFileName('Save Song', 'Super Boucle Song (*.sbs)') if file_name: file_name = verify_ext(file_name, 'sbs') self.song.file_name = file_name self.song.save() print("File saved to : {}".format(self.song.file_name)) def onAddDevice(self): self.learn_device = LearnDialog(self, self.addDevice) self.is_learn_device_mode = True def onManageDevice(self): ManageDialog(self) def onPlaylistEditor(self): PlaylistDialog(self) def onSceneManager(self): SceneManager(self) def onPortManager(self): PortManager(self) def onActionFullScreen(self): if self.isFullScreen(): self.showNormal() else: self.showFullScreen() self.show() def send_clip_state_feedback(self): for x in range(self.song.width): for y in range(self.song.height): clip = self.song.clips_matrix[x][y] state = clip.state if clip else None self._update_clip_state(x, y, state) def _update_clip_state(self, x, y, state): clip = self.song.clips_matrix[x][y] if clip: self.btn_matrix[x][y].setColor(state) try: self.queue_out.put(self.device.generateNote(x, y, state)) except IndexError: # print("No cell associated to %s x %s" # % (clp.x, clp.y)) pass self.state_matrix[x][y] = state def update(self): for x in range(len(self.song.clips_matrix)): line = self.song.clips_matrix[x] for y in range(len(line)): clp = line[y] if clp is None: state = None else: state = clp.state if state != self.state_matrix[x][y]: self._update_clip_state(x, y, state) def redraw(self): self.state_matrix = [[-1 for x in range(self.song.height)] for x in range(self.song.width)] self.update() def readQueue(self): try: while True: note = self.queue_in.get(block=False) if len(note) == 3: status, pitch, vel = struct.unpack('3B', note) channel = status & 0xF msg_type = status >> 4 self.processNote(msg_type, channel, pitch, vel) # else: # print("Invalid message length") except Empty: pass def processNote(self, msg_type, channel, pitch, vel): btn_id = (msg_type, channel, pitch, vel) btn_id_vel = (msg_type, channel, pitch, -1) ctrl_key = (msg_type, channel, pitch) # master volume if ctrl_key == self.device.master_volume_ctrl: self.song.master_volume = vel / 127 (self.master_volume .setValue(self.song.master_volume * 256)) elif self.device.play_btn in [btn_id, btn_id_vel]: self._jack_client.transport_start() elif self.device.pause_btn in [btn_id, btn_id_vel]: self._jack_client.transport_stop() elif self.device.rewind_btn in [btn_id, btn_id_vel]: self.onRewindClicked() elif self.device.goto_btn in [btn_id, btn_id_vel]: self.onGotoClicked() elif self.device.record_btn in [btn_id, btn_id_vel]: self.onRecord() elif ctrl_key in self.device.ctrls: try: ctrl_index = self.device.ctrls.index(ctrl_key) clip = (self.song.clips_matrix [ctrl_index] [self.current_vol_block]) if clip: clip.volume = vel / 127 if self.last_clip == clip: self.clip_volume.setValue(self.last_clip.volume * 256) except KeyError: pass elif (btn_id in self.device.scene_buttons or btn_id_vel in self.device.scene_buttons): try: scene_id = self.device.scene_buttons.index(btn_id) except ValueError: scene_id = self.device.scene_buttons.index(btn_id_vel) try: self.song.loadSceneId(scene_id) self.update() except IndexError: print('cannot load scene {} - there are only {} scenes.' ''.format(scene_id, len(self.song.scenes))) elif (btn_id in self.device.block_buttons or btn_id_vel in self.device.block_buttons): try: self.current_vol_block = ( self.device.block_buttons.index(btn_id)) except ValueError: self.current_vol_block = ( self.device.block_buttons.index(btn_id_vel)) for i in range(len(self.device.block_buttons)): (a, b_channel, b_pitch, b) = self.device.block_buttons[i] if i == self.current_vol_block: color = self.device.red_vel else: color = self.device.black_vel self.queue_out.put(((self.NOTEON << 4) + b_channel, b_pitch, color)) else: x, y = -1, -1 try: x, y = self.device.getXY(btn_id) except IndexError: pass except KeyError: try: x, y = self.device.getXY(btn_id_vel) except KeyError: pass if (x >= 0 and y >= 0): self.startStop(x, y) def toggleBlinkButton(self): for line in self.btn_matrix: for btn in line: if btn.blink: if self.blktimer.state: btn.setStyleSheet(btn.color) else: btn.setStyleSheet(self.DEFAULT) if self.song.is_record: if self.blktimer.state: self.recordButton.setStyleSheet(self.RECORD_BLINK) else: self.recordButton.setStyleSheet(self.RECORD_DEFAULT) self.blktimer.state = not self.blktimer.state def updateProgress(self): state, pos = self._jack_client.transport_query() if 'bar' in pos: bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick']) else: bbt = "-|-|-" seconds = int(pos['frame'] / pos['frame_rate']) (minutes, second) = divmod(seconds, 60) (hour, minute) = divmod(minutes, 60) time = "%d:%02d:%02d" % (hour, minute, second) self.bbtLabel.setText("%s\n%s" % (bbt, time)) for line in self.btn_matrix: for btn in line: if btn.clip and btn.clip.audio_file: value = ((btn.clip.last_offset / self.song.length(btn.clip)) * 97) btn.clip_position.setValue(value) btn.clip_position.repaint() def updateDevices(self): for action in self.deviceGroup.actions(): self.deviceGroup.removeAction(action) self.menuDevice.removeAction(action) for device in self.devices: action = QAction(device.name, self.menuDevice) action.setCheckable(True) action.setData(device) self.menuDevice.addAction(action) self.deviceGroup.addAction(action) action.setChecked(True) self.device = device def addDevice(self, device): self.devices.append(device) self.updateDevices() self.is_learn_device_mode = False def onDeviceSelect(self): self.device = self.deviceGroup.checkedAction().data() if self.device: if self.device.init_command: for note in self.device.init_command: self.queue_out.put(note) self.redraw() def timebase_callback(self, state, nframes, pos, new_pos): if pos.frame_rate == 0: return None pos.valid = 0x10 pos.bar_start_tick = BAR_START_TICK pos.beats_per_bar = self.beat_per_bar.value() pos.beat_type = BEAT_TYPE pos.ticks_per_beat = TICKS_PER_BEAT pos.beats_per_minute = self.bpm.value() ticks_per_second = (pos.beats_per_minute * pos.ticks_per_beat) / 60 ticks = (ticks_per_second * pos.frame) / pos.frame_rate (beats, pos.tick) = divmod(int(round(ticks, 0)), int(round(pos.ticks_per_beat, 0))) (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0))) (pos.bar, pos.beat) = (bar + 1, beat + 1) return None
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): QMainWindow.__init__(self) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowIcon(QIcon('icon.png')) self.ui.actionLoad_Lyrics.triggered.connect(self._load_lyrics_thread) self.cb_backends() # about widget self.aboutUi = About() self.ui.actionAbout.triggered.connect(self._about) self.aboutUi.setWindowModality(Qt.WindowModal) self._load_lyrics_thread() def cb_backends(self): self.cb_backends = QComboBox() self.cb_backends.addItem('Auto') menuLyricSource = QMenu(self.ui.menuEdit) menuLyricSource.setTitle('Lyric source') self.lyricGroup = QActionGroup(self) def addAction(name, checked=False): action = QAction(name, self) action.setText(name) action.setCheckable(True) action.setChecked(checked) action.setActionGroup(self.lyricGroup) return action menuLyricSource.addAction(addAction('Auto', True)) menuLyricSource.addSeparator() menuLyricSource.triggered.connect(self._menu_backend_change) for backend in Pyrics.get_backends(): menuLyricSource.addAction(addAction(backend.__name__)) self.cb_backends.addItem(backend.__name__) self.ui.menuEdit.addMenu(menuLyricSource) self.ui.toolBar.addWidget(self.cb_backends) self.cb_backends.currentIndexChanged.connect(self._cb_backend_change) def _load_lyrics_thread(self): self.thread = LoadLyricsWorker(self.cb_backends.currentIndex(), self) self.thread.trigger.connect(self._load_lyrics) self.thread.start() def _load_lyrics(self, content): self.ui.txLyrics.setText(content) def _about(self): self.aboutUi.show() def _menu_backend_change(self, action): index = self.cb_backends.findText(action.text()) self._update_backend(index) def _cb_backend_change(self, item): self._update_backend(item) def _update_backend(self, index): """Keep lyrics source combo in sync with the lyrics source in the menu""" if index >= 0: self.cb_backends.setCurrentIndex(index) name = self.cb_backends.currentText() for action in self.lyricGroup.actions(): action.setChecked(False) if action.text() == name: action.setChecked(True)
def __create_toolbars_and_menus(self): """Create the toolbars and menus.""" toolbar_weeks = QToolBar(self) self.addToolBar(Qt.TopToolBarArea, toolbar_weeks) toolbar_application = QToolBar(self) self.addToolBar(Qt.TopToolBarArea, toolbar_application) self.addToolBarBreak() toolbar_days = QToolBar(self) self.addToolBar(Qt.TopToolBarArea, toolbar_days) days_action_group = QActionGroup(self) for counter, day in enumerate(WeekDay, start=1): if day == WeekDay.Monday: tr_name = self.tr('Monday') elif day == WeekDay.Tuesday: tr_name = self.tr('Tuesday') elif day == WeekDay.Wednesday: tr_name = self.tr('Wednesday') elif day == WeekDay.Thursday: tr_name = self.tr('Thursday') elif day == WeekDay.Friday: tr_name = self.tr('Friday') elif day == WeekDay.Saturday: tr_name = self.tr('Saturday') elif day == WeekDay.Sunday: tr_name = self.tr('Sunday') action = QAction(QIcon(':/' + day.name.lower() + '.png'), tr_name, self) action.setObjectName(day.name) action.setShortcut('Alt+' + str(counter)) action.setCheckable(True) action.setStatusTip(self.tr('Go to {day}').format(day=tr_name)) action.triggered.connect(self.__change_current_day) days_action_group.addAction(action) self.day_actions[day] = action toolbar_days.addAction(action) previous_act = QAction(QIcon(':/previous.png'), self.tr('Previous Week'), self) previous_act.setShortcut('Ctrl+P') previous_act.triggered.connect(self.__previous_week) previous_act.setStatusTip(self.tr('Go to Previous Week')) next_act = QAction(QIcon(':/next.png'), self.tr('Next Week'), self) next_act.setShortcut('Ctrl+N') next_act.triggered.connect(self.__next_week) next_act.setStatusTip(self.tr('Go to Next Week')) today_act = QAction(QIcon(':/today.png'), self.tr('Today'), self) today_act.setShortcut('Ctrl+T') today_act.triggered.connect(self.__today) today_act.setStatusTip(self.tr('Go to today')) about_act = QAction(QIcon(':/info.png'), self.tr('About'), self) about_act.triggered.connect(self.__about) about_act.setStatusTip(self.tr('About this application')) about_qt_act = QAction(self.tr('About Qt'), self) about_qt_act.triggered.connect(qApp.aboutQt) about_qt_act.setStatusTip(self.tr('About Qt')) settings_act = QAction(QIcon(':/settings.png'), self.tr('Preferences'), self) settings_act.triggered.connect(self.__edit_preferences) settings_act.setStatusTip(self.tr('Edit preferences')) exit_act = QAction(QIcon(':/exit.png'), self.tr('&Quit'), self) exit_act.setShortcut('Ctrl+Q') exit_act.setStatusTip(self.tr('Quit application')) exit_act.triggered.connect(self.close) export_act = QAction(QIcon(':/export.png'), self.tr('Export'), self) export_act.setShortcut('Ctrl+E') export_act.setStatusTip(self.tr('Export week summary as html table')) export_act.triggered.connect(self.__export) toolbar_weeks.addAction(today_act) toolbar_weeks.addAction(previous_act) toolbar_weeks.addAction(next_act) toolbar_weeks.addAction(export_act) toolbar_application.addAction(exit_act) toolbar_application.addAction(settings_act) toolbar_application.addAction(about_act) menu_bar = self.menuBar() menu_bar.setNativeMenuBar(True) app_menu = menu_bar.addMenu(self.tr('Application')) app_menu.addAction(about_act) app_menu.addAction(about_qt_act) app_menu.addAction(settings_act) app_menu.addAction(exit_act) weeks_menu = menu_bar.addMenu(self.tr('Weeks')) weeks_menu.addAction(today_act) weeks_menu.addAction(previous_act) weeks_menu.addAction(next_act) weeks_menu.addAction(export_act) days_menu = menu_bar.addMenu(self.tr('Days')) for action in days_action_group.actions(): days_menu.addAction(action)
class MainWindow(QMainWindow): sizeButtonGroup = None OtherSize = QStyle.PM_CustomBase otherSpinBox = None def createImagesGroupBox(self): imagesGroupBox = QGroupBox("Images") labels = ("Images", "Mode", "State") self.imagesTable = QTableWidget() self.imagesTable.setSelectionMode(QAbstractItemView.NoSelection) self.imagesTable.setItemDelegate(ImageDelegate(self)) self.imagesTable.horizontalHeader().setDefaultSectionSize(90) self.imagesTable.setColumnCount(3) self.imagesTable.setHorizontalHeaderLabels(labels) self.imagesTable.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Stretch) self.imagesTable.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Fixed) self.imagesTable.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Fixed) self.imagesTable.verticalHeader().hide() self.imagesTable.itemChanged.connect(self.changeIcon) layout = QVBoxLayout(imagesGroupBox) layout.addWidget(self.imagesTable) return imagesGroupBox def createIconSizeGroupBox(self): iconSizeGroupBox = QGroupBox("Icon Size") self.sizeButtonGroup = QButtonGroup(iconSizeGroupBox) self.sizeButtonGroup.setExclusive(True) self.sizeButtonGroup.buttonToggled.connect(self.triggerChangeSize) smallRadioButton = QRadioButton() smallRadioButton.setChecked(True) self.sizeButtonGroup.addButton(smallRadioButton, QStyle.PM_SmallIconSize) largeRadioButton = QRadioButton() self.sizeButtonGroup.addButton(largeRadioButton, QStyle.PM_LargeIconSize) toolBarRadioButton = QRadioButton() self.sizeButtonGroup.addButton(toolBarRadioButton, QStyle.PM_ToolBarIconSize) iconViewRadioButton = QRadioButton() self.sizeButtonGroup.addButton(iconViewRadioButton, QStyle.PM_IconViewIconSize) listViewRadioButton = QRadioButton() self.sizeButtonGroup.addButton(listViewRadioButton, QStyle.PM_ListViewIconSize) tabBarRadioButton = QRadioButton() self.sizeButtonGroup.addButton(tabBarRadioButton, QStyle.PM_TabBarIconSize) otherRadioButton = QRadioButton("Other:") self.sizeButtonGroup.addButton(otherRadioButton, self.OtherSize) self.otherSpinBox = IconSizeSpinBox() self.otherSpinBox.setRange(8, 256) spinBoxToolTip = "Enter a custom size within {}..{}".format( self.otherSpinBox.minimum(), self.otherSpinBox.maximum()) self.otherSpinBox.setValue(64) self.otherSpinBox.setToolTip(spinBoxToolTip) otherRadioButton.setToolTip(spinBoxToolTip) self.otherSpinBox.valueChanged.connect(self.triggerChangeSize) otherSizeLayout = QHBoxLayout() otherSizeLayout.addWidget(otherRadioButton) otherSizeLayout.addWidget(self.otherSpinBox) otherSizeLayout.addStretch() layout = QGridLayout(iconSizeGroupBox) layout.addWidget(smallRadioButton, 0, 0) layout.addWidget(largeRadioButton, 1, 0) layout.addWidget(toolBarRadioButton, 2, 0) layout.addWidget(listViewRadioButton, 0, 1) layout.addWidget(iconViewRadioButton, 1, 1) layout.addWidget(tabBarRadioButton, 2, 1) layout.addLayout(otherSizeLayout, 3, 0, 1, 2) layout.setRowStretch(4, 1) return iconSizeGroupBox def loadImages(self, fileNames): for fileName in fileNames: row = self.imagesTable.rowCount() self.imagesTable.setRowCount(row + 1) fileInfo = QFileInfo(fileName) imageName = fileInfo.baseName() fileImage2x = fileInfo.absolutePath( ) + '/' + imageName + "@2x." + fileInfo.suffix() fileInfo2x = QFileInfo(fileImage2x) image = QImage(fileName) toolTip = "Directory: {}\nFile: {}\nFile@2x: {}\nSize: {}x{}".format( QDir.toNativeSeparators(fileInfo.absolutePath()), fileInfo.fileName(), fileInfo2x.fileName() if fileInfo2x.exists else "<None>", image.width(), image.height()) fileItem = QTableWidgetItem(imageName) fileItem.setData(Qt.UserRole, fileName) fileItem.setIcon(QIcon(QPixmap.fromImage(image))) fileItem.setFlags((fileItem.flags() | Qt.ItemIsUserCheckable) & ~Qt.ItemIsEditable) fileItem.setToolTip(toolTip) self.imagesTable.setItem(row, 0, fileItem) mode = QIcon.Normal state = QIcon.Off if self.guessModeStateAct.isChecked(): if "_act" in imageName: mode = QIcon.Active elif "_dis" in imageName: mode = QIcon.Disabled elif "_sel" in imageName: mode = QIcon.Selected if "_on" in imageName: mode = QIcon.On modeItem = QTableWidgetItem(IconPreviewArea.iconModeNames()[ IconPreviewArea.iconModes().index(mode)]) modeItem.setToolTip(toolTip) self.imagesTable.setItem(row, 1, modeItem) stateItem = QTableWidgetItem(IconPreviewArea.iconStateNames()[ IconPreviewArea.iconStates().index(state)]) stateItem.setToolTip(toolTip) self.imagesTable.setItem(row, 2, stateItem) self.imagesTable.openPersistentEditor(modeItem) self.imagesTable.openPersistentEditor(stateItem) fileItem.setCheckState(Qt.Checked) def addImages(self, directory): fileDialog = QFileDialog(self, "Open Images", directory) mimeTypeFilters = [] for mimeTypeName in QImageReader.supportedMimeTypes(): mimeTypeFilters.append(str(mimeTypeName)) mimeTypeFilters.sort() fileDialog.setMimeTypeFilters(mimeTypeFilters) fileDialog.selectMimeTypeFilter('image/png') fileDialog.setAcceptMode(QFileDialog.AcceptOpen) fileDialog.setFileMode(QFileDialog.ExistingFiles) if not self.nativeFileDialogAct.isChecked(): fileDialog.setOption(QFileDialog.DontUseNativeDialog) if fileDialog.exec_() == QDialog.Accepted: self.loadImages(fileDialog.selectedFiles()) def addSampleImages(self): self.addImages("C:/Users/hungbn/Pictures") def addOtherImages(self): pass def removeAllImages(self): self.imagesTable.setRowCount(0) self.changeIcon() def createActions(self): fileMenu = self.menuBar().addMenu("&File") addSampleImagesAct = QAction("Add &Sample Images...", self) addSampleImagesAct.triggered.connect(self.addSampleImages) fileMenu.addAction(addSampleImagesAct) addOtherImagesAct = QAction("&Add Images...", self) addOtherImagesAct.setShortcut(QKeySequence.Open) addOtherImagesAct.triggered.connect(self.addOtherImages) fileMenu.addAction(addOtherImagesAct) removeAllImagesAct = QAction("&Remove All Images", self) removeAllImagesAct.setShortcut("CTRL+R") removeAllImagesAct.triggered.connect(self.removeAllImages) fileMenu.addAction(removeAllImagesAct) fileMenu.addSeparator() exitAct = QAction("&Quit", self) exitAct.triggered.connect(QWidget.close) exitAct.setShortcuts(QKeySequence.Quit) fileMenu.addAction(exitAct) viewMenu = self.menuBar().addMenu("&View") self.styleActionGroup = QActionGroup(self) for styleName in QStyleFactory.keys(): action = QAction("{} Style".format(styleName), self.styleActionGroup) action.setData(styleName) action.setCheckable(True) action.triggered.connect(self.changeStyle) viewMenu.addAction(action) settingsMenu = self.menuBar().addMenu("&Settings") self.guessModeStateAct = QAction("&Guess Image Mode/State", self) self.guessModeStateAct.setCheckable(True) self.guessModeStateAct.setChecked(True) settingsMenu.addAction(self.guessModeStateAct) self.nativeFileDialogAct = QAction("&Use Native File Dialog", self) self.nativeFileDialogAct.setCheckable(True) self.nativeFileDialogAct.setChecked(True) settingsMenu.addAction(self.nativeFileDialogAct) def __init__(self, *args): super(QMainWindow, self).__init__(*args) centerWidget = QWidget(self) self.setCentralWidget(centerWidget) mainLayout = QGridLayout(centerWidget) previewGroupBox = QGroupBox("Preview") self.previewArea = IconPreviewArea(previewGroupBox) previewLayout = QVBoxLayout(previewGroupBox) previewLayout.addWidget(self.previewArea) mainLayout.addWidget(previewGroupBox, 0, 0, 1, 2) mainLayout.addWidget(self.createImagesGroupBox(), 1, 0) vbox = QVBoxLayout() vbox.addWidget(self.createIconSizeGroupBox()) #vbox.addWidget(self.createHighDpiIconSizeGroupBox()) vbox.addItem( QSpacerItem(0, 0, QSizePolicy.Ignored, QSizePolicy.MinimumExpanding)) mainLayout.addLayout(vbox, 1, 1) self.createActions() self.setWindowTitle("Icons") self.checkCurrentStyle() def about(self): QMessageBox.about( self, "About Icons" "The <b>Icons</b> example illustrates how Qt renders an icon in " + "different modes (active, normal, disabled, and selected) and " + "states (on and off) based on a set of images.") def checkCurrentStyle(self): for action in self.styleActionGroup.actions(): styleName = action.data() candidate = QStyleFactory.create(styleName) if candidate.metaObject().className() == QApplication.style( ).metaObject().className(): action.trigger() return def changeStyle(self, checked): if (not checked): return action = self.sender() style = QStyleFactory.create(action.data()) QApplication.setStyle(style) for button in self.sizeButtonGroup.buttons(): metric = self.sizeButtonGroup.id(button) value = style.pixelMetric(metric) { QStyle.PM_SmallIconSize: lambda: button.setText("Small ({} x {})".format(value, value)), QStyle.PM_LargeIconSize: lambda: button.setText("Large ({} x {})".format(value, value)), QStyle.PM_ToolBarIconSize: lambda: button.setText("Toolbars ({} x {})".format( value, value)), QStyle.PM_ListViewIconSize: lambda: button.setText("List views ({} x {})".format( value, value)), QStyle.PM_IconViewIconSize: lambda: button.setText("Icon views ({} x {})".format( value, value)), QStyle.PM_TabBarIconSize: lambda: button.setText("Tab bars ({} x {})".format( value, value)), }.get(metric, lambda: "")() self.triggerChangeSize() def changeIcon(self): icon = QIcon() for row in range(self.imagesTable.rowCount()): fileItem = self.imagesTable.item(row, 0) modeItem = self.imagesTable.item(row, 1) stateItem = self.imagesTable.item(row, 2) if fileItem.checkState() == Qt.Checked: modeIndex = IconPreviewArea.iconModeNames().index( modeItem.text()) stateIndex = IconPreviewArea.iconStateNames().index( stateItem.text()) mode = IconPreviewArea.iconModes()[modeIndex] state = IconPreviewArea.iconStates()[stateIndex] fileName = fileItem.data(Qt.UserRole) image = QImage(fileName) if not image.isNull(): icon.addPixmap(QPixmap.fromImage(image), mode, state) self.previewArea.setIcon(icon) def changeSize(self, id, checked): if (not checked): return other = (id == self.OtherSize) extend = self.otherSpinBox.value() if other else QApplication.style( ).pixelMetric(id) self.previewArea.setSize(QSize(extend, extend)) self.otherSpinBox.setEnabled(other) def triggerChangeSize(self): self.changeSize(self.sizeButtonGroup.checkedId(), True)
class ListWidget(QWidget): deviceSelected = pyqtSignal(TasmotaDevice) openRulesEditor = pyqtSignal() openConsole = pyqtSignal() openTelemetry = pyqtSignal() openWebUI = pyqtSignal() def __init__(self, parent, *args, **kwargs): super(ListWidget, self).__init__(*args, **kwargs) self.setWindowTitle("Devices list") self.setWindowState(Qt.WindowMaximized) self.setLayout(VLayout(margin=0, spacing=0)) self.mqtt = parent.mqtt self.env = parent.env self.device = None self.idx = None self.nam = QNetworkAccessManager() self.backup = bytes() self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat) views_order = self.settings.value("views_order", []) self.views = {} self.settings.beginGroup("Views") views = self.settings.childKeys() if views and views_order: for view in views_order.split(";"): view_list = self.settings.value(view).split(";") self.views[view] = base_view + view_list else: self.views = default_views self.settings.endGroup() self.tb = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.tb_relays = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonIconOnly) # self.tb_filter = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.tb_views = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.pwm_sliders = [] self.layout().addWidget(self.tb) self.layout().addWidget(self.tb_relays) # self.layout().addWidget(self.tb_filter) self.device_list = TableView() self.device_list.setIconSize(QSize(24, 24)) self.model = parent.device_model self.model.setupColumns(self.views["Home"]) self.sorted_device_model = QSortFilterProxyModel() self.sorted_device_model.setFilterCaseSensitivity(Qt.CaseInsensitive) self.sorted_device_model.setSourceModel(parent.device_model) self.sorted_device_model.setSortRole(Qt.InitialSortOrderRole) self.sorted_device_model.setFilterKeyColumn(-1) self.device_list.setModel(self.sorted_device_model) self.device_list.setupView(self.views["Home"]) self.device_list.setSortingEnabled(True) self.device_list.setWordWrap(True) self.device_list.setItemDelegate(DeviceDelegate()) self.device_list.sortByColumn(self.model.columnIndex("FriendlyName"), Qt.AscendingOrder) self.device_list.setContextMenuPolicy(Qt.CustomContextMenu) self.device_list.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) self.layout().addWidget(self.device_list) self.layout().addWidget(self.tb_views) self.device_list.clicked.connect(self.select_device) self.device_list.customContextMenuRequested.connect(self.show_list_ctx_menu) self.ctx_menu = QMenu() self.ctx_menu_relays = None self.create_actions() self.create_view_buttons() # self.create_view_filter() self.device_list.doubleClicked.connect(lambda: self.openConsole.emit()) def create_actions(self): self.ctx_menu_cfg = QMenu("Configure") self.ctx_menu_cfg.setIcon(QIcon("GUI/icons/settings.png")) self.ctx_menu_cfg.addAction("Module", self.configureModule) self.ctx_menu_cfg.addAction("GPIO", self.configureGPIO) self.ctx_menu_cfg.addAction("Template", self.configureTemplate) # self.ctx_menu_cfg.addAction("Wifi", self.ctx_menu_teleperiod) # self.ctx_menu_cfg.addAction("Time", self.cfgTime.emit) # self.ctx_menu_cfg.addAction("MQTT", self.ctx_menu_teleperiod) # self.ctx_menu_cfg.addAction("Logging", self.ctx_menu_teleperiod) self.ctx_menu.addMenu(self.ctx_menu_cfg) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon("GUI/icons/refresh.png"), "Refresh", self.ctx_menu_refresh) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon("GUI/icons/clear.png"), "Clear retained", self.ctx_menu_clear_retained) self.ctx_menu.addAction("Clear Backlog", self.ctx_menu_clear_backlog) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon("GUI/icons/copy.png"), "Copy", self.ctx_menu_copy) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon("GUI/icons/restart.png"), "Restart", self.ctx_menu_restart) self.ctx_menu.addAction(QIcon(), "Reset", self.ctx_menu_reset) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon("GUI/icons/delete.png"), "Delete", self.ctx_menu_delete_device) console = self.tb.addAction(QIcon("GUI/icons/console.png"), "Console", self.openConsole.emit) console.setShortcut("Ctrl+E") rules = self.tb.addAction(QIcon("GUI/icons/rules.png"), "Rules", self.openRulesEditor.emit) rules.setShortcut("Ctrl+R") self.tb.addAction(QIcon("GUI/icons/timers.png"), "Timers", self.configureTimers) buttons = self.tb.addAction(QIcon("GUI/icons/buttons.png"), "Buttons", self.configureButtons) buttons.setShortcut("Ctrl+B") switches = self.tb.addAction(QIcon("GUI/icons/switches.png"), "Switches", self.configureSwitches) switches.setShortcut("Ctrl+S") power = self.tb.addAction(QIcon("GUI/icons/power.png"), "Power", self.configurePower) power.setShortcut("Ctrl+P") # setopts = self.tb.addAction(QIcon("GUI/icons/setoptions.png"), "SetOptions", self.configureSO) # setopts.setShortcut("Ctrl+S") self.tb.addSpacer() telemetry = self.tb.addAction(QIcon("GUI/icons/telemetry.png"), "Telemetry", self.openTelemetry.emit) telemetry.setShortcut("Ctrl+T") webui = self.tb.addAction(QIcon("GUI/icons/web.png"), "WebUI", self.openWebUI.emit) webui.setShortcut("Ctrl+U") # self.tb.addAction(QIcon(), "Multi Command", self.ctx_menu_webui) self.agAllPower = QActionGroup(self) self.agAllPower.addAction(QIcon("GUI/icons/P_ON.png"), "All ON") self.agAllPower.addAction(QIcon("GUI/icons/P_OFF.png"), "All OFF") self.agAllPower.setEnabled(False) self.agAllPower.setExclusive(False) self.agAllPower.triggered.connect(self.toggle_power_all) self.tb_relays.addActions(self.agAllPower.actions()) self.agRelays = QActionGroup(self) self.agRelays.setVisible(False) self.agRelays.setExclusive(False) for a in range(1, 9): act = QAction(QIcon("GUI/icons/P{}_OFF.png".format(a)), "") act.setShortcut("F{}".format(a)) self.agRelays.addAction(act) self.agRelays.triggered.connect(self.toggle_power) self.tb_relays.addActions(self.agRelays.actions()) self.tb_relays.addSeparator() self.actColor = self.tb_relays.addAction(QIcon("GUI/icons/color.png"), "Color", self.set_color) self.actColor.setEnabled(False) self.actChannels = self.tb_relays.addAction(QIcon("GUI/icons/sliders.png"), "Channels") self.actChannels.setEnabled(False) self.mChannels = QMenu() self.actChannels.setMenu(self.mChannels) self.tb_relays.widgetForAction(self.actChannels).setPopupMode(QToolButton.InstantPopup) def create_view_buttons(self): self.tb_views.addWidget(QLabel("View mode: ")) ag_views = QActionGroup(self) ag_views.setExclusive(True) for v in self.views.keys(): a = QAction(v) a.triggered.connect(self.change_view) a.setCheckable(True) ag_views.addAction(a) self.tb_views.addActions(ag_views.actions()) ag_views.actions()[0].setChecked(True) stretch = QWidget() stretch.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) self.tb_views.addWidget(stretch) # actEditView = self.tb_views.addAction("Edit views...") # def create_view_filter(self): # # self.tb_filter.addWidget(QLabel("Show devices: ")) # # self.cbxLWT = QComboBox() # # self.cbxLWT.addItems(["All", "Online"d, "Offline"]) # # self.cbxLWT.currentTextChanged.connect(self.build_filter_regex) # # self.tb_filter.addWidget(self.cbxLWT) # # self.tb_filter.addWidget(QLabel(" Search: ")) # self.leSearch = QLineEdit() # self.leSearch.setClearButtonEnabled(True) # self.leSearch.textChanged.connect(self.build_filter_regex) # self.tb_filter.addWidget(self.leSearch) # # def build_filter_regex(self, txt): # query = self.leSearch.text() # # if self.cbxLWT.currentText() != "All": # # query = "{}|{}".format(self.cbxLWT.currentText(), query) # self.sorted_device_model.setFilterRegExp(query) def change_view(self, a=None): view = self.views[self.sender().text()] self.model.setupColumns(view) self.device_list.setupView(view) def ctx_menu_copy(self): if self.idx: string = dumps(self.model.data(self.idx)) if string.startswith('"') and string.endswith('"'): string = string[1:-1] QApplication.clipboard().setText(string) def ctx_menu_clear_retained(self): if self.device: relays = self.device.power() if relays and len(relays.keys()) > 0: for r in relays.keys(): self.mqtt.publish(self.device.cmnd_topic(r), retain=True) QMessageBox.information(self, "Clear retained", "Cleared retained messages.") def ctx_menu_clear_backlog(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("backlog"), "") QMessageBox.information(self, "Clear Backlog", "Backlog cleared.") def ctx_menu_restart(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("restart"), payload="1") for k in list(self.device.power().keys()): self.device.p.pop(k) def ctx_menu_reset(self): if self.device: reset, ok = QInputDialog.getItem(self, "Reset device and restart", "Select reset mode", resets, editable=False) if ok: self.mqtt.publish(self.device.cmnd_topic("reset"), payload=reset.split(":")[0]) for k in list(self.device.power().keys()): self.device.p.pop(k) def ctx_menu_refresh(self): if self.device: for k in list(self.device.power().keys()): self.device.p.pop(k) for c in initial_commands(): cmd, payload = c cmd = self.device.cmnd_topic(cmd) self.mqtt.publish(cmd, payload, 1) def ctx_menu_delete_device(self): if self.device: if QMessageBox.question(self, "Confirm", "Do you want to remove the following device?\n'{}' ({})" .format(self.device.p['FriendlyName1'], self.device.p['Topic'])) == QMessageBox.Yes: self.model.deleteDevice(self.idx) def ctx_menu_teleperiod(self): if self.device: teleperiod, ok = QInputDialog.getInt(self, "Set telemetry period", "Input 1 to reset to default\n[Min: 10, Max: 3600]", self.device.p['TelePeriod'], 1, 3600) if ok: if teleperiod != 1 and teleperiod < 10: teleperiod = 10 self.mqtt.publish(self.device.cmnd_topic("teleperiod"), teleperiod) def ctx_menu_config_backup(self): if self.device: self.backup = bytes() self.dl = self.nam.get(QNetworkRequest(QUrl("http://{}/dl".format(self.device.p['IPAddress'])))) self.dl.readyRead.connect(self.get_dump) self.dl.finished.connect(self.save_dump) def ctx_menu_ota_set_url(self): if self.device: url, ok = QInputDialog.getText(self, "Set OTA URL", '100 chars max. Set to "1" to reset to default.', text=self.device.p['OtaUrl']) if ok: self.mqtt.publish(self.device.cmnd_topic("otaurl"), payload=url) def ctx_menu_ota_set_upgrade(self): if self.device: if QMessageBox.question(self, "OTA Upgrade", "Are you sure to OTA upgrade from\n{}".format(self.device.p['OtaUrl']), QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: self.mqtt.publish(self.device.cmnd_topic("upgrade"), payload="1") def show_list_ctx_menu(self, at): self.select_device(self.device_list.indexAt(at)) self.ctx_menu.popup(self.device_list.viewport().mapToGlobal(at)) def select_device(self, idx): self.idx = self.sorted_device_model.mapToSource(idx) self.device = self.model.deviceAtRow(self.idx.row()) self.deviceSelected.emit(self.device) relays = self.device.power() self.agAllPower.setEnabled(len(relays) >= 1) for i, a in enumerate(self.agRelays.actions()): a.setVisible(len(relays) > 1 and i < len(relays)) color = self.device.color().get("Color", False) has_color = bool(color) self.actColor.setEnabled(has_color and not self.device.setoption(68)) self.actChannels.setEnabled(has_color) if has_color: self.actChannels.menu().clear() max_val = 100 if self.device.setoption(15) == 0: max_val = 1023 for k, v in self.device.pwm().items(): channel = SliderAction(self, k) channel.slider.setMaximum(max_val) channel.slider.setValue(int(v)) self.mChannels.addAction(channel) channel.slider.valueChanged.connect(self.set_channel) dimmer = self.device.color().get("Dimmer") if dimmer: saDimmer = SliderAction(self, "Dimmer") saDimmer.slider.setValue(int(dimmer)) self.mChannels.addAction(saDimmer) saDimmer.slider.valueChanged.connect(self.set_channel) def toggle_power(self, action): if self.device: idx = self.agRelays.actions().index(action) relay = list(self.device.power().keys())[idx] self.mqtt.publish(self.device.cmnd_topic(relay), "toggle") def toggle_power_all(self, action): if self.device: idx = self.agAllPower.actions().index(action) for r in self.device.power().keys(): self.mqtt.publish(self.device.cmnd_topic(r), str(not bool(idx))) def set_color(self): if self.device: color = self.device.color().get("Color") if color: dlg = QColorDialog() new_color = dlg.getColor(QColor("#{}".format(color))) if new_color.isValid(): new_color = new_color.name() if new_color != color: self.mqtt.publish(self.device.cmnd_topic("color"), new_color) def set_channel(self, value=0): cmd = self.sender().objectName() if self.device: self.mqtt.publish(self.device.cmnd_topic(cmd), str(value)) def configureSO(self): if self.device: dlg = SetOptionsDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureModule(self): if self.device: dlg = ModuleDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureGPIO(self): if self.device: dlg = GPIODialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureTemplate(self): if self.device: dlg = TemplateDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureTimers(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("timers")) timers = TimersDialog(self.device) self.mqtt.messageSignal.connect(timers.parseMessage) timers.sendCommand.connect(self.mqtt.publish) timers.exec_() def configureButtons(self): if self.device: backlog = [] buttons = ButtonsDialog(self.device) if buttons.exec_() == QDialog.Accepted: for c, cw in buttons.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) for so, sow in buttons.setoption_widgets.items(): current_value = self.device.setoption(so) new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if current_value != new_value: backlog.append("SetOption{} {}".format(so, new_value)) if backlog: backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def configureSwitches(self): if self.device: backlog = [] switches = SwitchesDialog(self.device) if switches.exec_() == QDialog.Accepted: for c, cw in switches.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) for so, sow in switches.setoption_widgets.items(): current_value = self.device.setoption(so) new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if current_value != new_value: backlog.append("SetOption{} {}".format(so, new_value)) for sw, sw_mode in enumerate(self.device.p['SwitchMode']): new_value = switches.sm.inputs[sw].currentIndex() if sw_mode != new_value: backlog.append("switchmode{} {}".format(sw+1, new_value)) if backlog: backlog.append("status") backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def configurePower(self): if self.device: backlog = [] power = PowerDialog(self.device) if power.exec_() == QDialog.Accepted: for c, cw in power.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) for so, sow in power.setoption_widgets.items(): new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if new_value != self.device.setoption(so): backlog.append("SetOption{} {}".format(so, new_value)) new_interlock_value = power.ci.input.currentData() new_interlock_grps = " ".join([grp.text().replace(" ", "") for grp in power.ci.groups]).rstrip() if new_interlock_value != self.device.p.get("Interlock", "OFF"): backlog.append("interlock {}".format(new_interlock_value)) if new_interlock_grps != self.device.p.get("Groups", ""): backlog.append("interlock {}".format(new_interlock_grps)) for i, pt in enumerate(power.cpt.inputs): ptime = "PulseTime{}".format(i+1) current_ptime = self.device.p.get(ptime) if current_ptime: current_value = list(current_ptime.keys())[0] new_value = str(pt.value()) if new_value != current_value: backlog.append("{} {}".format(ptime, new_value)) if backlog: backlog.append("status") backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def get_dump(self): self.backup += self.dl.readAll() def save_dump(self): fname = self.dl.header(QNetworkRequest.ContentDispositionHeader) if fname: fname = fname.split('=')[1] save_file = QFileDialog.getSaveFileName(self, "Save config backup", "{}/TDM/{}".format(QDir.homePath(), fname))[0] if save_file: with open(save_file, "wb") as f: f.write(self.backup) def check_fulltopic(self, fulltopic): fulltopic += "/" if not fulltopic.endswith('/') else '' return "%prefix%" in fulltopic and "%topic%" in fulltopic def closeEvent(self, event): event.ignore()
class KEyesWidget(QWidget): update_interval = 50 # ms faces = { "Aaron": ("keyes-aaron.png", (49, 63, 12, 8), (79, 63, 12, 8)), "Adrian": ("keyes-adrian.png", (46, 67, 11, 6), (74, 68, 11, 6)), "Cornelius": ("keyes-cornelius.png", (49, 68, 11, 6), (79, 68, 11, 6)), "Eva": ("keyes-eva.png", (51, 63, 12, 6), (83, 63, 12, 6)), "Sebastian": ("keyes-sebastian.png", (50, 58, 14, 7), (83, 58, 14, 7)), } def __init__(self): QLabel.__init__(self) self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) self.setAttribute(Qt.WA_NoSystemBackground) self.setMouseTracking(True) self.dragPosition = QPoint(0, 0) self.mousePosition = QCursor.pos() self.actionFaces = QActionGroup(self) allFaceActions = [] for name in sorted(self.faces): action = QAction(name, self.actionFaces) action.setCheckable(True) allFaceActions.append(action) self.actionFaces.triggered.connect(self.actionUpdateFace) startAction = random.choice(allFaceActions) startAction.setChecked(True) self.actionUpdateFace(startAction) self.actionQuit = QAction("Quit", self) self.actionQuit.triggered.connect(QApplication.instance().quit) self.timer = QTimer() self.timer.timeout.connect(self.updateFromMousePosition) self.timer.start(self.update_interval) self.painter = None def actionUpdateFace(self, action): self.setFace(action.text()) def setFace(self, name): self.setWindowTitle(name) self.pixmap = QPixmap(normalize_path(self.faces[name][0])) self.setWindowIcon(QIcon(self.pixmap)) self.eyes = [Eye(*self.faces[name][1]), Eye(*self.faces[name][2])] self.setMask(self.pixmap.createHeuristicMask()) if self.isVisible(): self.update() def updateFromMousePosition(self): newPosition = QCursor.pos() if newPosition == self.mousePosition: return self.mousePosition = newPosition if self.isVisible(): self.update() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.dragPosition = event.globalPos() - self.frameGeometry().topLeft() event.accept() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: self.move(event.globalPos() - self.dragPosition) event.accept() def contextMenuEvent(self, event): menu = QMenu(self) menu.addActions(self.actionFaces.actions()) menu.addSeparator() menu.addAction(self.actionQuit) menu.exec_(event.globalPos()) def paintEvent(self, event): painter = QPainter(self) self.painter = painter painter.drawPixmap(QPoint(0, 0), self.pixmap) for eye in self.eyes: eye.render(self) self.painter = None def sizeHint(self): return self.pixmap.size()
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.centralWidget = QWidget() self.setCentralWidget(self.centralWidget) self.createPreviewGroupBox() self.createImagesGroupBox() self.createIconSizeGroupBox() self.createActions() self.createMenus() self.createContextMenu() mainLayout = QGridLayout() mainLayout.addWidget(self.previewGroupBox, 0, 0, 1, 2) mainLayout.addWidget(self.imagesGroupBox, 1, 0) mainLayout.addWidget(self.iconSizeGroupBox, 1, 1) self.centralWidget.setLayout(mainLayout) self.setWindowTitle("Icons") self.checkCurrentStyle() self.otherRadioButton.click() self.resize(self.minimumSizeHint()) def about(self): QMessageBox.about(self, "About Icons", "The <b>Icons</b> example illustrates how Qt renders an icon " "in different modes (active, normal, disabled and selected) " "and states (on and off) based on a set of images.") def changeStyle(self, checked): if not checked: return action = self.sender() style = QStyleFactory.create(action.data()) if not style: return QApplication.setStyle(style) self.setButtonText(self.smallRadioButton, "Small (%d x %d)", style, QStyle.PM_SmallIconSize) self.setButtonText(self.largeRadioButton, "Large (%d x %d)", style, QStyle.PM_LargeIconSize) self.setButtonText(self.toolBarRadioButton, "Toolbars (%d x %d)", style, QStyle.PM_ToolBarIconSize) self.setButtonText(self.listViewRadioButton, "List views (%d x %d)", style, QStyle.PM_ListViewIconSize) self.setButtonText(self.iconViewRadioButton, "Icon views (%d x %d)", style, QStyle.PM_IconViewIconSize) self.setButtonText(self.tabBarRadioButton, "Tab bars (%d x %d)", style, QStyle.PM_TabBarIconSize) self.changeSize() @staticmethod def setButtonText(button, label, style, metric): metric_value = style.pixelMetric(metric) button.setText(label % (metric_value, metric_value)) def changeSize(self, checked=True): if not checked: return if self.otherRadioButton.isChecked(): extent = self.otherSpinBox.value() else: if self.smallRadioButton.isChecked(): metric = QStyle.PM_SmallIconSize elif self.largeRadioButton.isChecked(): metric = QStyle.PM_LargeIconSize elif self.toolBarRadioButton.isChecked(): metric = QStyle.PM_ToolBarIconSize elif self.listViewRadioButton.isChecked(): metric = QStyle.PM_ListViewIconSize elif self.iconViewRadioButton.isChecked(): metric = QStyle.PM_IconViewIconSize else: metric = QStyle.PM_TabBarIconSize extent = QApplication.style().pixelMetric(metric) self.previewArea.setSize(QSize(extent, extent)) self.otherSpinBox.setEnabled(self.otherRadioButton.isChecked()) def changeIcon(self): icon = QIcon() for row in range(self.imagesTable.rowCount()): item0 = self.imagesTable.item(row, 0) item1 = self.imagesTable.item(row, 1) item2 = self.imagesTable.item(row, 2) if item0.checkState() == Qt.Checked: if item1.text() == "Normal": mode = QIcon.Normal elif item1.text() == "Active": mode = QIcon.Active elif item1.text() == "Disabled": mode = QIcon.Disabled else: mode = QIcon.Selected if item2.text() == "On": state = QIcon.On else: state = QIcon.Off fileName = item0.data(Qt.UserRole) image = QImage(fileName) if not image.isNull(): icon.addPixmap(QPixmap.fromImage(image), mode, state) self.previewArea.setIcon(icon) def addImage(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Images", '', "Images (*.png *.xpm *.jpg);;All Files (*)") for fileName in fileNames: row = self.imagesTable.rowCount() self.imagesTable.setRowCount(row + 1) imageName = QFileInfo(fileName).baseName() item0 = QTableWidgetItem(imageName) item0.setData(Qt.UserRole, fileName) item0.setFlags(item0.flags() & ~Qt.ItemIsEditable) item1 = QTableWidgetItem("Normal") item2 = QTableWidgetItem("Off") if self.guessModeStateAct.isChecked(): if '_act' in fileName: item1.setText("Active") elif '_dis' in fileName: item1.setText("Disabled") elif '_sel' in fileName: item1.setText("Selected") if '_on' in fileName: item2.setText("On") self.imagesTable.setItem(row, 0, item0) self.imagesTable.setItem(row, 1, item1) self.imagesTable.setItem(row, 2, item2) self.imagesTable.openPersistentEditor(item1) self.imagesTable.openPersistentEditor(item2) item0.setCheckState(Qt.Checked) def removeAllImages(self): self.imagesTable.setRowCount(0) self.changeIcon() def createPreviewGroupBox(self): self.previewGroupBox = QGroupBox("Preview") self.previewArea = IconPreviewArea() layout = QVBoxLayout() layout.addWidget(self.previewArea) self.previewGroupBox.setLayout(layout) def createImagesGroupBox(self): self.imagesGroupBox = QGroupBox("Images") self.imagesTable = QTableWidget() self.imagesTable.setSelectionMode(QAbstractItemView.NoSelection) self.imagesTable.setItemDelegate(ImageDelegate(self)) self.imagesTable.horizontalHeader().setDefaultSectionSize(90) self.imagesTable.setColumnCount(3) self.imagesTable.setHorizontalHeaderLabels(("Image", "Mode", "State")) self.imagesTable.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.imagesTable.horizontalHeader().setSectionResizeMode(1, QHeaderView.Fixed) self.imagesTable.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) self.imagesTable.verticalHeader().hide() self.imagesTable.itemChanged.connect(self.changeIcon) layout = QVBoxLayout() layout.addWidget(self.imagesTable) self.imagesGroupBox.setLayout(layout) def createIconSizeGroupBox(self): self.iconSizeGroupBox = QGroupBox("Icon Size") self.smallRadioButton = QRadioButton() self.largeRadioButton = QRadioButton() self.toolBarRadioButton = QRadioButton() self.listViewRadioButton = QRadioButton() self.iconViewRadioButton = QRadioButton() self.tabBarRadioButton = QRadioButton() self.otherRadioButton = QRadioButton("Other:") self.otherSpinBox = IconSizeSpinBox() self.otherSpinBox.setRange(8, 128) self.otherSpinBox.setValue(64) self.smallRadioButton.toggled.connect(self.changeSize) self.largeRadioButton.toggled.connect(self.changeSize) self.toolBarRadioButton.toggled.connect(self.changeSize) self.listViewRadioButton.toggled.connect(self.changeSize) self.iconViewRadioButton.toggled.connect(self.changeSize) self.tabBarRadioButton.toggled.connect(self.changeSize) self.otherRadioButton.toggled.connect(self.changeSize) self.otherSpinBox.valueChanged.connect(self.changeSize) otherSizeLayout = QHBoxLayout() otherSizeLayout.addWidget(self.otherRadioButton) otherSizeLayout.addWidget(self.otherSpinBox) otherSizeLayout.addStretch() layout = QGridLayout() layout.addWidget(self.smallRadioButton, 0, 0) layout.addWidget(self.largeRadioButton, 1, 0) layout.addWidget(self.toolBarRadioButton, 2, 0) layout.addWidget(self.listViewRadioButton, 0, 1) layout.addWidget(self.iconViewRadioButton, 1, 1) layout.addWidget(self.tabBarRadioButton, 2, 1) layout.addLayout(otherSizeLayout, 3, 0, 1, 2) layout.setRowStretch(4, 1) self.iconSizeGroupBox.setLayout(layout) def createActions(self): self.addImagesAct = QAction("&Add Images...", self, shortcut="Ctrl+A", triggered=self.addImage) self.removeAllImagesAct = QAction("&Remove All Images", self, shortcut="Ctrl+R", triggered=self.removeAllImages) self.exitAct = QAction("&Quit", self, shortcut="Ctrl+Q", triggered=self.close) self.styleActionGroup = QActionGroup(self) for styleName in QStyleFactory.keys(): action = QAction(self.styleActionGroup, text="%s Style" % styleName, checkable=True, triggered=self.changeStyle) action.setData(styleName) self.guessModeStateAct = QAction("&Guess Image Mode/State", self, checkable=True, checked=True) self.aboutAct = QAction("&About", self, triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.addImagesAct) self.fileMenu.addAction(self.removeAllImagesAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = self.menuBar().addMenu("&View") for action in self.styleActionGroup.actions(): self.viewMenu.addAction(action) self.viewMenu.addSeparator() self.viewMenu.addAction(self.guessModeStateAct) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createContextMenu(self): self.imagesTable.setContextMenuPolicy(Qt.ActionsContextMenu) self.imagesTable.addAction(self.addImagesAct) self.imagesTable.addAction(self.removeAllImagesAct) def checkCurrentStyle(self): for action in self.styleActionGroup.actions(): styleName = action.data() candidate = QStyleFactory.create(styleName) if candidate is None: return if candidate.metaObject().className() == QApplication.style().metaObject().className(): action.trigger()
def initUI(self): # View widget stuff self.view = TextView() self.centralWidget = QWidget(self) self.layout = QVBoxLayout(self.centralWidget) self.view.statusChanged.connect(self.quickViewStatusChanged) self.view.sceneGraphError.connect(self.sceneGraphError) self.container = QWidget.createWindowContainer(self.view) self.container.setMinimumSize(self.view.size()) self.container.setFocusPolicy(Qt.TabFocus) self.layout.addWidget(self.container) self.setCentralWidget(self.centralWidget) self.statusBar().showMessage('Ready') # Menu stuff self.toolbar = self.addToolBar('Text Effects') # Set up the text effects tools actionGroup = QActionGroup(self) noneAction = QAction(QIcon("none.png"), "&Clear", self) noneAction.setStatusTip("Clear Effects") noneAction.triggered.connect(self.view.noEffect) actionGroup.addAction(noneAction) blurAction = QAction(QIcon("blur.png"), "&Blur", self) blurAction.setStatusTip("Blur Text") blurAction.triggered.connect(self.view.blur) actionGroup.addAction(blurAction) opacityAction = QAction(QIcon("opacity.png"), "&Transparency", self) opacityAction.setStatusTip("Fade Text") opacityAction.triggered.connect(self.view.opacity) actionGroup.addAction(opacityAction) shadowAction = QAction(QIcon("shadow.png"), "&Drop Shadow", self) shadowAction.setStatusTip("Drop-shadow Text") shadowAction.triggered.connect(self.view.shadow) actionGroup.addAction(shadowAction) self.toolbar.addActions(actionGroup.actions()) self.toolbar.addSeparator() # Set up the font selection tools boldAction = QAction(QIcon("bold.png"), "&Bold", self) boldAction.setStatusTip("Bold Text") boldAction.setCheckable(True) boldAction.setChecked(False) boldAction.triggered[bool].connect(self.view.bold) self.toolbar.addAction(boldAction) italicAction = QAction(QIcon("italic.png"), "&Italic", self) italicAction.setStatusTip("Italic Text") italicAction.setCheckable(True) italicAction.triggered[bool].connect(self.view.italic) self.toolbar.addAction(italicAction) self.fontBox = QFontComboBox(self) self.fontBox.setCurrentFont(self.view.font) self.fontBox.currentFontChanged.connect(self.view.fontFamily) self.toolbar.addWidget(self.fontBox) self.fontSizeBox = QComboBox(self) self.fontSizeBox.setEditable(True) strlist = [] intlist = QFontDatabase.standardSizes() for item in intlist: strlist.append(str(item)) self.fontSizeBox.addItems(strlist) self.view.fontSize('24') self.fontSizeBox.setCurrentText(str(self.view.basicFontSize)) self.fontSizeBox.currentTextChanged.connect(self.view.fontSize) self.toolbar.addWidget(self.fontSizeBox) self.setGeometry(300, 300, 600, 500) self.setWindowTitle('Renderer') self.show()
def __init__(self, launcher, parent=None): QMainWindow.__init__(self, parent) self.setupUi(self) self.central_workspace = WorkspaceWidget(self) self.setCentralWidget(self.central_workspace) self.installEventFilter(RadioKeyEventFilter(self)) self.setAttribute(Qt.WA_DeleteOnClose) self.launcher = launcher settings.controlled_tower_viewer = FlightGearTowerViewer(self) settings.session_manager = SessionManager(self) self.setWindowTitle('%s - %s (%s)' % (self.windowTitle(), env.locationName(), settings.location_code)) self.session_start_sound_lock_timer = QTimer(self) self.session_start_sound_lock_timer.setSingleShot(True) self.session_start_sound_lock_timer.timeout.connect(self.unlockSounds) ## Restore saved dock layout try: with open(dock_layout_file, 'rb') as f: # Restore saved dock arrangement self.restoreState(f.read()) except FileNotFoundError: # Fallback on default dock arrangement # left docks, top zone self.tabifyDockWidget(self.selection_info_dock, self.weather_dock) self.tabifyDockWidget(self.selection_info_dock, self.towerView_dock) self.tabifyDockWidget(self.selection_info_dock, self.navigator_dock) self.selection_info_dock.hide() # left docks, bottom zone self.tabifyDockWidget(self.instructions_dock, self.notepads_dock) self.tabifyDockWidget(self.instructions_dock, self.radio_dock) self.tabifyDockWidget(self.instructions_dock, self.FPLlist_dock) self.tabifyDockWidget(self.instructions_dock, self.CPDLC_dock) self.instructions_dock.hide() self.notepads_dock.hide() self.radio_dock.hide() self.CPDLC_dock.hide() # right docks self.rwyBoxes_dock.hide() # hiding this because bad position (user will drag 1st thing after raise) # bottom docks self.atcTextChat_dock.hide() ## Permanent tool/status bar widgets self.selectionInfo_toolbar.addWidget(SelectionInfoToolbarWidget(self)) self.METAR_statusBarLabel = QLabel() self.PTT_statusBarLabel = QLabel() self.RDF_statusBarLabel = QLabel() self.RDF_statusBarLabel.setToolTip('Current signal / last QDM') self.wind_statusBarLabel = QLabel() self.QNH_statusBarLabel = QLabel() self.QNH_statusBarLabel.setToolTip('hPa / inHg') self.clock_statusBarLabel = QLabel() self.alarmClock_statusBarButtons = [AlarmClockButton('1', self), AlarmClockButton('2', self)] self.statusbar.addWidget(self.METAR_statusBarLabel) self.statusbar.addPermanentWidget(self.PTT_statusBarLabel) self.statusbar.addPermanentWidget(self.RDF_statusBarLabel) self.statusbar.addPermanentWidget(self.wind_statusBarLabel) self.statusbar.addPermanentWidget(self.QNH_statusBarLabel) for b in self.alarmClock_statusBarButtons: self.statusbar.addPermanentWidget(b) b.alarm.connect(self.notification_pane.notifyAlarmClockTimedOut) self.statusbar.addPermanentWidget(self.clock_statusBarLabel) # Populate menus (toolbar visibility and airport viewpoints) toolbar_menu = QMenu() self.general_viewToolbar_action = self.general_toolbar.toggleViewAction() self.stripActions_viewToolbar_action = self.stripActions_toolbar.toggleViewAction() self.docks_viewToolbar_action = self.docks_toolbar.toggleViewAction() self.selectionInfo_viewToolbar_action = self.selectionInfo_toolbar.toggleViewAction() self.radarAssistance_viewToolbar_action = self.radarAssistance_toolbar.toggleViewAction() self.workspace_viewToolbar_action = self.workspace_toolbar.toggleViewAction() toolbar_menu.addAction(self.general_viewToolbar_action) toolbar_menu.addAction(self.stripActions_viewToolbar_action) toolbar_menu.addAction(self.docks_viewToolbar_action) toolbar_menu.addAction(self.selectionInfo_viewToolbar_action) toolbar_menu.addAction(self.radarAssistance_viewToolbar_action) toolbar_menu.addAction(self.workspace_viewToolbar_action) self.toolbars_view_menuAction.setMenu(toolbar_menu) if env.airport_data == None or len(env.airport_data.viewpoints) == 0: self.viewpointSelection_view_menuAction.setEnabled(False) else: viewPointSelection_menu = QMenu() viewPointSelection_actionGroup = QActionGroup(self) for vp_i, (vp_pos, vp_h, vp_name) in enumerate(env.airport_data.viewpoints): action = QAction('%s - %d ft ASFC' % (vp_name, vp_h + .5), self) action.setCheckable(True) action.triggered.connect(lambda ignore_checked, i=vp_i: self.selectIndicateViewpoint(i)) viewPointSelection_actionGroup.addAction(action) actions = viewPointSelection_actionGroup.actions() viewPointSelection_menu.addActions(actions) self.viewpointSelection_view_menuAction.setMenu(viewPointSelection_menu) try: actions[settings.selected_viewpoint].setChecked(True) except IndexError: actions[0].setChecked(True) ## Memory-persistent windows and dialogs self.solo_connect_dialog_AD = StartSoloDialog_AD(self) self.MP_connect_dialog = StartFlightGearMPdialog(self) self.start_student_session_dialog = StartStudentSessionDialog(self) self.recall_cheat_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, False), 'Sent and deleted strips') self.shelf_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, True), 'Strip shelf') self.environment_info_dialog = EnvironmentInfoDialog(self) self.about_dialog = AboutDialog(self) self.teaching_console = TeachingConsole(parent=self) self.unit_converter = UnitConversionWindow(parent=self) self.world_airport_navigator = WorldAirportNavigator(parent=self) self.quick_reference = QuickReference(parent=self) for w in self.teaching_console, self.unit_converter, self.world_airport_navigator, self.quick_reference: w.setWindowFlags(Qt.Window) w.installEventFilter(RadioKeyEventFilter(w)) # Make a few actions always visible self.addAction(self.newStrip_action) self.addAction(self.newLinkedStrip_action) self.addAction(self.newFPL_action) self.addAction(self.newLinkedFPL_action) self.addAction(self.startTimer1_action) self.addAction(self.forceStartTimer1_action) self.addAction(self.startTimer2_action) self.addAction(self.forceStartTimer2_action) self.addAction(self.notificationSounds_options_action) self.addAction(self.quickReference_help_action) self.addAction(self.saveDockLayout_view_action) self.addAction(self.recallWindowState_view_action) # Populate icons self.newStrip_action.setIcon(QIcon(IconFile.action_newStrip)) self.newLinkedStrip_action.setIcon(QIcon(IconFile.action_newLinkedStrip)) self.newFPL_action.setIcon(QIcon(IconFile.action_newFPL)) self.newLinkedFPL_action.setIcon(QIcon(IconFile.action_newLinkedFPL)) self.teachingConsole_view_action.setIcon(QIcon(IconFile.panel_teaching)) self.unitConversionTool_view_action.setIcon(QIcon(IconFile.panel_unitConv)) self.worldAirportNavigator_view_action.setIcon(QIcon(IconFile.panel_airportList)) self.environmentInfo_view_action.setIcon(QIcon(IconFile.panel_envInfo)) self.generalSettings_options_action.setIcon(QIcon(IconFile.action_generalSettings)) self.soloSessionSettings_system_action.setIcon(QIcon(IconFile.action_sessionSettings)) self.runwaysInUse_options_action.setIcon(QIcon(IconFile.action_runwayUse)) self.newLooseStripBay_view_action.setIcon(QIcon(IconFile.action_newLooseStripBay)) self.newRadarScreen_view_action.setIcon(QIcon(IconFile.action_newRadarScreen)) self.newStripRackPanel_view_action.setIcon(QIcon(IconFile.action_newRackPanel)) self.popOutCurrentWindow_view_action.setIcon(QIcon(IconFile.action_popOutWindow)) self.reclaimPoppedOutWindows_view_action.setIcon(QIcon(IconFile.action_reclaimWindows)) self.primaryRadar_options_action.setIcon(QIcon(IconFile.option_primaryRadar)) self.approachSpacingHints_options_action.setIcon(QIcon(IconFile.option_approachSpacingHints)) self.runwayOccupationWarnings_options_action.setIcon(QIcon(IconFile.option_runwayOccupationMonitor)) self.routeConflictWarnings_options_action.setIcon(QIcon(IconFile.option_routeConflictWarnings)) self.trafficIdentification_options_action.setIcon(QIcon(IconFile.option_identificationAssistant)) setDockAndActionIcon(IconFile.panel_ATCs, self.handovers_dockView_action, self.handover_dock) setDockAndActionIcon(IconFile.panel_atcChat, self.atcTextChat_dockView_action, self.atcTextChat_dock) setDockAndActionIcon(IconFile.panel_CPDLC, self.cpdlc_dockView_action, self.CPDLC_dock) setDockAndActionIcon(IconFile.panel_FPLs, self.FPLs_dockView_action, self.FPLlist_dock) setDockAndActionIcon(IconFile.panel_instructions, self.instructions_dockView_action, self.instructions_dock) setDockAndActionIcon(IconFile.panel_navigator, self.navpoints_dockView_action, self.navigator_dock) setDockAndActionIcon(IconFile.panel_notepads, self.notepads_dockView_action, self.notepads_dock) setDockAndActionIcon(IconFile.panel_notifications, self.notificationArea_dockView_action, self.notification_dock) setDockAndActionIcon(IconFile.panel_radios, self.fgcom_dockView_action, self.radio_dock) setDockAndActionIcon(IconFile.panel_runwayBox, self.runwayBoxes_dockView_action, self.rwyBoxes_dock) setDockAndActionIcon(IconFile.panel_selInfo, self.radarContactDetails_dockView_action, self.selection_info_dock) setDockAndActionIcon(IconFile.panel_racks, self.strips_dockView_action, self.strip_dock) setDockAndActionIcon(IconFile.panel_txtChat, self.radioTextChat_dockView_action, self.radioTextChat_dock) setDockAndActionIcon(IconFile.panel_twrView, self.towerView_dockView_action, self.towerView_dock) setDockAndActionIcon(IconFile.panel_weather, self.weather_dockView_action, self.weather_dock) # action TICKED STATES (set here before connections) self.windowedWorkspace_view_action.setChecked(settings.saved_workspace_windowed_view) self.verticalRwyBoxLayout_view_action.setChecked(settings.vertical_runway_box_layout) self.notificationSounds_options_action.setChecked(settings.notification_sounds_enabled) self.primaryRadar_options_action.setChecked(settings.primary_radar_active) self.routeConflictWarnings_options_action.setChecked(settings.route_conflict_warnings) self.trafficIdentification_options_action.setChecked(settings.traffic_identification_assistant) self.runwayOccupationWarnings_options_action.setChecked(settings.monitor_runway_occupation) self.approachSpacingHints_options_action.setChecked(settings.APP_spacing_hints) # action CONNECTIONS # non-menu actions self.newStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=False)) self.newLinkedStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=True)) self.newFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=None)) self.newLinkedFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=selection.strip)) self.startTimer1_action.triggered.connect(lambda: self.startTimer(0, False)) self.forceStartTimer1_action.triggered.connect(lambda: self.startTimer(0, True)) self.startTimer2_action.triggered.connect(lambda: self.startTimer(1, False)) self.forceStartTimer2_action.triggered.connect(lambda: self.startTimer(1, True)) # system menu self.soloSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_solo)) self.connectFlightGearMP_system_action.triggered.connect(lambda: self.startStopSession(self.start_FlightGearMP)) self.teacherSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_teaching)) self.studentSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_learning)) self.reloadAdditionalViewers_system_action.triggered.connect(self.reloadAdditionalViewers) self.reloadBgImages_system_action.triggered.connect(self.reloadBackgroundImages) self.reloadColourConfig_system_action.triggered.connect(self.reloadColourConfig) self.reloadRoutePresets_system_action.triggered.connect(self.reloadRoutePresets) self.reloadEntryExitPoints_system_action.triggered.connect(self.reloadEntryExitPoints) self.announceFgSession_system_action.triggered.connect(self.announceFgSession) self.fgcomEchoTest_system_action.triggered.connect(self.radio_pane.performEchoTest) self.extractSectorFile_system_action.triggered.connect(self.extractSectorFile) self.repositionBgImages_system_action.triggered.connect(self.repositionRadarBgImages) self.measuringLogsCoordinates_system_action.toggled.connect(self.switchMeasuringCoordsLog) self.airportGateway_system_action.triggered.connect(lambda: self.goToURL(airport_gateway_URL)) self.openStreetMap_system_action.triggered.connect(lambda: self.goToURL(mk_OSM_URL(env.radarPos()))) self.soloSessionSettings_system_action.triggered.connect(self.openSoloSessionSettings) self.locationSettings_system_action.triggered.connect(self.openLocalSettings) self.systemSettings_system_action.triggered.connect(self.openSystemSettings) self.changeLocation_system_action.triggered.connect(self.changeLocation) self.quit_system_action.triggered.connect(self.close) # view menu self.saveDockLayout_view_action.triggered.connect(self.saveDockLayout) self.recallWindowState_view_action.triggered.connect(self.recallWindowState) self.handovers_dockView_action.triggered.connect(lambda: self.raiseDock(self.handover_dock)) self.atcTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.atcTextChat_dock)) self.cpdlc_dockView_action.triggered.connect(lambda: self.raiseDock(self.CPDLC_dock)) self.FPLs_dockView_action.triggered.connect(lambda: self.raiseDock(self.FPLlist_dock)) self.instructions_dockView_action.triggered.connect(lambda: self.raiseDock(self.instructions_dock)) self.navpoints_dockView_action.triggered.connect(lambda: self.raiseDock(self.navigator_dock)) self.notepads_dockView_action.triggered.connect(lambda: self.raiseDock(self.notepads_dock)) self.notificationArea_dockView_action.triggered.connect(lambda: self.raiseDock(self.notification_dock)) self.fgcom_dockView_action.triggered.connect(lambda: self.raiseDock(self.radio_dock)) self.runwayBoxes_dockView_action.triggered.connect(lambda: self.raiseDock(self.rwyBoxes_dock)) self.radarContactDetails_dockView_action.triggered.connect(lambda: self.raiseDock(self.selection_info_dock)) self.strips_dockView_action.triggered.connect(lambda: self.raiseDock(self.strip_dock)) self.radioTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.radioTextChat_dock)) self.towerView_dockView_action.triggered.connect(lambda: self.raiseDock(self.towerView_dock)) self.weather_dockView_action.triggered.connect(lambda: self.raiseDock(self.weather_dock)) self.windowedWorkspace_view_action.toggled.connect(self.central_workspace.switchWindowedView) self.popOutCurrentWindow_view_action.triggered.connect(self.central_workspace.popOutCurrentWindow) self.reclaimPoppedOutWindows_view_action.triggered.connect(self.central_workspace.reclaimPoppedOutWidgets) self.newLooseStripBay_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.LOOSE_BAY)) self.newRadarScreen_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.RADAR_SCREEN)) self.newStripRackPanel_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.STRIP_PANEL)) self.verticalRwyBoxLayout_view_action.toggled.connect(self.switchVerticalRwyBoxLayout) self.towerView_view_action.triggered.connect(self.toggleTowerWindow) self.addViewer_view_action.triggered.connect(self.addView) self.listViewers_view_action.triggered.connect(self.listAdditionalViews) self.activateAdditionalViewers_view_action.toggled.connect(self.activateAdditionalViews) self.removeViewer_view_action.triggered.connect(self.removeView) self.teachingConsole_view_action.triggered.connect(self.teaching_console.show) self.unitConversionTool_view_action.triggered.connect(self.unit_converter.show) self.worldAirportNavigator_view_action.triggered.connect(self.world_airport_navigator.show) self.environmentInfo_view_action.triggered.connect(self.environment_info_dialog.exec) # options menu self.runwaysInUse_options_action.triggered.connect(self.configureRunwayUse) self.notificationSounds_options_action.toggled.connect(self.switchNotificationSounds) self.primaryRadar_options_action.toggled.connect(self.switchPrimaryRadar) self.routeConflictWarnings_options_action.toggled.connect(self.switchConflictWarnings) self.trafficIdentification_options_action.toggled.connect(self.switchTrafficIdentification) self.runwayOccupationWarnings_options_action.toggled.connect(self.switchRwyOccupationIndications) self.approachSpacingHints_options_action.toggled.connect(self.switchApproachSpacingHints) self.generalSettings_options_action.triggered.connect(self.openGeneralSettings) # cheat menu self.pauseSimulation_cheat_action.toggled.connect(self.pauseSession) self.spawnAircraft_cheat_action.triggered.connect(self.spawnAircraft) self.killSelectedAircraft_cheat_action.triggered.connect(self.killSelectedAircraft) self.popUpMsgOnRejectedInstr_cheat_action.toggled.connect(self.setRejectedInstrPopUp) self.showRecognisedVoiceStrings_cheat_action.toggled.connect(self.setShowRecognisedVoiceStrings) self.ensureClearWeather_cheat_action.toggled.connect(self.ensureClearWeather) self.ensureDayLight_cheat_action.triggered.connect(self.towerView_pane.ensureDayLight) self.changeTowerHeight_cheat_action.triggered.connect(self.changeTowerHeight) self.recallDiscardedStrip_cheat_action.triggered.connect(self.recall_cheat_dialog.exec) self.radarCheatMode_cheat_action.toggled.connect(self.setRadarCheatMode) # help menu self.quickReference_help_action.triggered.connect(self.quick_reference.show) self.videoTutorial_help_action.triggered.connect(lambda: self.goToURL(video_tutorial_URL)) self.FAQ_help_action.triggered.connect(lambda: self.goToURL(FAQ_URL)) self.about_help_action.triggered.connect(self.about_dialog.exec) ## More signal connections signals.openShelfRequest.connect(self.shelf_dialog.exec) signals.privateAtcChatRequest.connect(lambda: self.raiseDock(self.atcTextChat_dock)) signals.stripRecall.connect(recover_strip) env.radar.blip.connect(env.strips.refreshViews) env.radar.lostContact.connect(self.aircraftHasDisappeared) signals.aircraftKilled.connect(self.aircraftHasDisappeared) env.strips.rwyBoxFreed.connect(lambda box, strip: env.airport_data.physicalRunway_restartWtcTimer(box, strip.lookup(FPL.WTC))) env.rdf.signalChanged.connect(self.updateRDF) signals.statusBarMsg.connect(lambda msg: self.statusbar.showMessage(msg, status_bar_message_timeout)) signals.newWeather.connect(self.updateWeatherIfPrimary) signals.kbdPTT.connect(self.updatePTT) signals.sessionStarted.connect(self.sessionHasStarted) signals.sessionEnded.connect(self.sessionHasEnded) signals.towerViewProcessToggled.connect(self.towerView_view_action.setChecked) signals.towerViewProcessToggled.connect(self.towerView_cheat_menu.setEnabled) signals.stripInfoChanged.connect(env.strips.refreshViews) signals.fastClockTick.connect(self.updateClock) signals.fastClockTick.connect(env.cpdlc.updateAckStatuses) signals.slowClockTick.connect(strip_auto_print_check) signals.stripEditRequest.connect(lambda strip: edit_strip(self, strip)) signals.selectionChanged.connect(self.updateStripFplActions) signals.receiveStrip.connect(receive_strip) signals.handoverFailure.connect(self.recoverFailedHandover) signals.sessionPaused.connect(env.radar.stopSweeping) signals.sessionResumed.connect(env.radar.startSweeping) signals.aircraftKilled.connect(env.radar.silentlyForgetContact) signals.rackVisibilityLost.connect(self.collectClosedRacks) signals.localSettingsChanged.connect(env.rdf.clearAllSignals) signals.localSettingsChanged.connect(self.updateRDF) ## MISC GUI setup self.strip_pane.setViewRacks([default_rack_name]) # will be moved out if a rack panel's saved "visible_racks" claims it [*1] self.strip_pane.restoreState(settings.saved_strip_dock_state) # [*1] self.central_workspace.restoreWorkspaceWindows(settings.saved_workspace_windows) self.central_workspace.switchWindowedView(settings.saved_workspace_windowed_view) # keep this after restoring windows! self.subsecond_ticker = Ticker(signals.fastClockTick.emit, parent=self) self.subminute_ticker = Ticker(signals.slowClockTick.emit, parent=self) self.subsecond_ticker.start_stopOnZero(subsecond_tick_interval) self.subminute_ticker.start_stopOnZero(subminute_tick_interval) self.towerView_cheat_menu.setEnabled(False) self.solo_cheat_menu.setEnabled(False) self.updateClock() self.updateWeatherIfPrimary(settings.primary_METAR_station, None) self.updateStripFplActions() self.last_RDF_qdm = None self.updateRDF() self.updatePTT(0, False) # Disable some base airport stuff if doing CTR if env.airport_data == None: self.towerView_view_action.setEnabled(False) self.runwaysInUse_options_action.setEnabled(False) self.runwayOccupationWarnings_options_action.setEnabled(False) # Finish self.atcTextChat_pane.switchAtcChatFilter(None) # Show GUI on general chat room at start if speech_recognition_available: prepare_SR_language_files()
class Ui_MainWindow(QMainWindow, Ui_MainWindowBase): updateFrame = pyqtSignal() def __init__(self): super(Ui_MainWindow, self).__init__() self.setupUi(self) self.videoPlaybackInit() self.imgInit() self.menuInit() self.radiusSpinBox.valueChanged.connect(self.radiusSpinBoxValueChanged) self.lineWidthSpinBox.valueChanged.connect(self.lineWidthSpinBoxValueChanged) self.overlayFrameNoSpinBox.valueChanged.connect(self.overlayFrameNoSpinBoxValueChanged) self.stackedWidget.currentChanged.connect(self.stackedWidgetCurrentChanged) self.arrowCheckBox.stateChanged.connect(self.arrowCheckBoxStateChanged) self.pathCheckBox.stateChanged.connect(self.pathCheckBoxStateChanged) self.reverseArrowColorCheckBox.stateChanged.connect(self.reverseArrowColorCheckBoxStateChanged) self.opaqueCheckBox.stateChanged.connect(self.opaqueCheckBoxStateChanged) self.videoPlaybackWidget.setSignalSlotMode() self.updateFrame.connect(self.videoPlaybackWidget.videoPlayback) self.filter = None self.filterIO = None self.isInitialized = False self.item_dict = {} self.data_dict = {} self.trackingPathGroup = None self.df = {} self.inputPixmapItem = None self.cv_img = None self.filePath = None self.savedFlag = True def dragEnterEvent(self,event): event.acceptProposedAction() def dropEvent(self,event): mime = event.mimeData() if mime.hasUrls(): urls = mime.urls() if len(urls) > 0: self.processDropedFile(urls[0].toLocalFile()) event.acceptProposedAction() def closeEvent(self, event): if len(self.df.keys())==0 or self.savedFlag: return quit_msg = "Data is not saved.\nAre you sure you want to exit the program?" reply = QtWidgets.QMessageBox.question( self, 'Warning', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No ) if reply == QtWidgets.QMessageBox.Yes: event.accept() else: event.ignore() def processDropedFile(self,filePath): root,ext = os.path.splitext(filePath) if ext == ".filter": # Read Filter self.openFilterFile(filePath=filePath) return elif self.openVideoFile(filePath=filePath): return def arrowCheckBoxStateChanged(self, state): if 'arrow' not in self.item_dict.keys(): return for arrow_item in self.item_dict['arrow']: if state==Qt.Unchecked: arrow_item.hide() if state==Qt.Checked: arrow_item.show() self.updateInputGraphicsView() def pathCheckBoxStateChanged(self, state): if self.trackingPathGroup is None: return if state==Qt.Unchecked: self.trackingPathGroup.hide() if state==Qt.Checked: self.trackingPathGroup.show() self.updateInputGraphicsView() def reverseArrowColorCheckBoxStateChanged(self, state): if 'arrow' not in self.item_dict.keys(): return for arrow_item in self.item_dict['arrow']: if state==Qt.Unchecked: arrow_item.setColor([0,0,0]) if state==Qt.Checked: arrow_item.setColor([255,255,255]) self.updateInputGraphicsView() def opaqueCheckBoxStateChanged(self, state): if self.trackingPathGroup is None: return if state==Qt.Unchecked: self.trackingPathGroup.setOpacity(0.5) if state==Qt.Checked: self.trackingPathGroup.setOpacity(1.0) self.updateInputGraphicsView() def reset(self): self.videoPlaybackWidget.stop() self.videoPlaybackWidget.moveToFrame(0) def videoPlaybackInit(self): self.videoPlaybackWidget.hide() self.videoPlaybackWidget.frameChanged.connect(self.setFrame, type=Qt.QueuedConnection) def setFrame(self, frame, frameNo): if frame is not None: self.cv_img = frame self.currentFrameNo = frameNo self.updateInputGraphicsView() self.evaluate() return def imgInit(self): self.inputScene = QGraphicsScene() self.inputGraphicsView.setScene(self.inputScene) self.inputGraphicsView.resizeEvent = self.inputGraphicsViewResized def menuInit(self): self.actionOpenVideo.triggered.connect(self.openVideoFile) self.actionOpenFilterSetting.triggered.connect(self.openFilterFile) self.actionSaveCSVFile.triggered.connect(self.saveCSVFile) self.actionRunObjectTracking.triggered.connect(self.runObjectTracking) self.actionTrackingPathColor.triggered.connect(self.openTrackingPathColorSelectorDialog) self.menuAlgorithmsActionGroup = QActionGroup(self.menuAlgorithms) path_list = [[tracking_system_path, currentDirPath], ] if os.path.exists(user_defined_lib_path): path_list.append([user_defined_tracking_system_path, user_defined_lib_path]) for system_path in path_list: for module_path in get_modules(system_path[0], system_path[1]): module_str = '.'.join(module_path) try: module = importlib.import_module(module_str) if not hasattr(module, 'Widget'): continue class_def = getattr(module, "Widget") if not issubclass(class_def, QtWidgets.QWidget): continue widget = class_def(self.stackedWidget) widget.reset.connect(self.resetDataframe) widget.restart.connect(self.restartDataframe) self.stackedWidget.addWidget(widget) action = self.menuAlgorithms.addAction(widget.get_name()) action.triggered.connect(self.generateAlgorithmsMenuClicked(widget)) action.setCheckable(True) action.setActionGroup(self.menuAlgorithmsActionGroup) if len(self.menuAlgorithmsActionGroup.actions()) == 1: action.setChecked(True) self.algorithmSettingsGroupBox.setTitle(widget.get_name()) except Exception as e: if system_path[1] is user_defined_lib_path: msg = 'Tracking Lib. Load Fail: {0}\n{1}'.format(module_str, e) self.generateCriticalMessage(msg) continue def openTrackingPathColorSelectorDialog(self, activated=False): if self.trackingPathGroup is not None: self.trackingPathGroup.openColorSelectorDialog(self) def generateAlgorithmsMenuClicked(self, widget): def action_triggered(activated=False): if widget is not self.stackedWidget.currentWidget(): self.stackedWidget.setCurrentWidget(widget) else: pass return action_triggered def initializeEventDialog(self): quit_msg = "Data is not saved.\nAre you sure you want to reset?" reply = QtWidgets.QMessageBox.question( self, 'Warning', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No ) if reply == QtWidgets.QMessageBox.Yes: return True else: return False def openVideoFile(self, activated=False, filePath = None): if filePath is None: filePath, _ = QFileDialog.getOpenFileName(None, 'Open Video File', userDir) if len(filePath) is not 0: if filterOperation is not None and self.videoPlaybackWidget.isOpened(): if self.initializeEventDialog(): global filterOperation filterOperation = None self.removeTrackingGraphicsItems() self.savedFlag = True else: return self.filePath = filePath ret = self.videoPlaybackWidget.openVideo(filePath) if not ret: return False self.videoPlaybackWidget.show() self.cv_img = self.videoPlaybackWidget.getCurrentFrame() self.currentFrameNo = 0 self.videoPlaybackWidget.setMaxTickableFrameNo(0) self.initializeTrackingSystem() return True else: return False def openFilterFile(self, activated=False, filePath = None): if filePath is None: filePath, _ = QFileDialog.getOpenFileName(None, 'Open Block File', userDir, "Block files (*.filter)") if len(filePath) is not 0: if filterOperation is not None and self.videoPlaybackWidget.isOpened(): if self.initializeEventDialog(): self.videoPlaybackWidget.closeVideo() self.videoPlaybackWidget.hide() self.removeTrackingGraphicsItems() self.inputScene.removeItem(self.inputPixmapItem) self.savedFlag = True else: return logger.debug("Open Filter file: {0}".format(filePath)) self.filterIO = FilterIO(filePath) exec(self.filterIO.getFilterCode(), globals()) self.filter = None self.initializeTrackingSystem() self.evaluate() def saveCSVFile(self, activated=False, filePath = None): if len(self.df.keys())!=0: dirctory = os.path.dirname(self.filePath) base_name = os.path.splitext(os.path.basename(self.filePath))[0] path = os.path.join(dirctory, '{0}-{1}.txt'.format(base_name, "info")) filePath, _ = QFileDialog.getSaveFileName(None, 'Save Info File', path, "TXT files (*.txt)") if len(filePath) is not 0: logger.debug("Saving Info file: {0}".format(filePath)) with open(filePath, 'w') as fp: fp.write(self.videoPlaybackWidget.getVideoInfo()) for attr, df in self.df.items(): path = os.path.join(dirctory, '{0}-{1}.csv'.format(base_name, attr)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save CSV File', path, "CSV files (*.csv)") if len(filePath) is not 0: logger.debug("Saving CSV file: {0}".format(filePath)) row_max = self.currentFrameNo df = df.loc[:row_max] levels = df.columns.levels col = ['{0}{1}'.format(l,i) for i in levels[0] for l in levels[1]] df.columns = col df.to_csv(filePath) for k, v in self.data_dict.items(): path = os.path.join(dirctory, '{0}-{1}.json'.format(base_name, k)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save JSON File', path, "JSON files (*.json)") if len(filePath) is not 0: logger.debug("Saving JSON file: {0}".format(filePath)) with open(filePath, 'w') as f_p: json.dump(v, f_p) path = os.path.join(dirctory, '{0}-colors.color'.format(base_name)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save Color File', path, "Color files (*.color)") if len(filePath) is not 0: logger.debug("Saving Color file: {0}".format(filePath)) self.trackingPathGroup.saveColors(filePath) self.savedFlag = True def radiusSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setRadius(i) self.updateInputGraphicsView() def lineWidthSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setLineWidth(i) self.updateInputGraphicsView() def overlayFrameNoSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setOverlayFrameNo(i) self.updateInputGraphicsView() def stackedWidgetCurrentChanged(self, i): currentWidget = self.stackedWidget.currentWidget() currentWidget.estimator_init() self.algorithmSettingsGroupBox.setTitle(currentWidget.get_name()) self.resetDataframe() def updateInputGraphicsView(self): if self.inputPixmapItem is not None: self.inputScene.removeItem(self.inputPixmapItem) if self.filter is not None and hasattr(self.filter, "resize_flag") and self.filter.resize_flag: qimg = misc.cvMatToQImage(cv2.pyrDown(self.cv_img)) else: qimg = misc.cvMatToQImage(self.cv_img) pixmap = QPixmap.fromImage(qimg) self.inputPixmapItem = QGraphicsPixmapItem(pixmap) rect = QtCore.QRectF(pixmap.rect()) self.inputScene.setSceneRect(rect) self.inputScene.addItem(self.inputPixmapItem) self.inputGraphicsView.viewport().update() self.inputGraphicsViewResized() def inputGraphicsViewResized(self, event=None): self.inputGraphicsView.fitInView(self.inputScene.sceneRect(), QtCore.Qt.KeepAspectRatio) def updatePath(self): try: attrs = self.stackedWidget.currentWidget().get_attributes() attrs.keys() except Exception as e: msg = 'Tracking Lib. Attributes Error:\n{}'.format(e) self.generateCriticalMessage(msg) return if 'position' in attrs: self.trackingPathGroup.setPoints(self.currentFrameNo) if 'arrow' in attrs: for i, arrow_item in enumerate(self.item_dict['arrow']): begin = self.df['position'].loc[self.currentFrameNo, i].as_matrix() end = self.df['arrow'].loc[self.currentFrameNo, i].as_matrix() arrow_item.setPosition(begin, end) if 'path' in attrs: for path_item, path_data in zip(self.item_dict['path'], self.data_dict['path'][self.currentFrameNo]): poly = QPolygonF() for p in path_data: poly.append(QPointF(*p)) painter_path = QPainterPath() painter_path.addPolygon(poly) path_item.setPath(painter_path) pen = QPen(Qt.blue) pen.setWidth(2) path_item.setPen(pen) if 'polygon' in attrs: for path_item, path_data in zip(self.item_dict['polygon'], self.data_dict['polygon'][self.currentFrameNo]): poly = QPolygonF() for p in path_data: poly.append(QPointF(*p)) painter_path = QPainterPath() painter_path.addPolygon(poly) path_item.setPath(painter_path) pen = QPen(Qt.black) pen.setWidth(1) path_item.setPen(pen) if 'rect' in attrs: for rect_item, rect in zip(self.item_dict['rect'], self.data_dict['rect'][self.currentFrameNo]): rect_item.setRect(QRectF(QPointF(*rect[0]), QPointF(*rect[1]))) def resetDataframe(self): self.initializeTrackingSystem() self.evaluate() def restartDataframe(self): if len(self.df.keys())==0: return for attr in self.df.keys(): self.df[attr].loc[self.currentFrameNo+1:] = np.nan for k in self.data_dict.keys(): for kk in self.data_dict[k]: if kk>currentFrameNo: del self.data_dict[k] df = {} for attr in self.df.keys(): df[attr] = self.df[attr].loc[self.currentFrameNo] kv = {k:[] for k in self.df.keys()} for key, value in kv.items(): mul_levs = df[key].index.levels for i in mul_levs[0]: value.append(df[key][i].as_matrix()) for k, v in self.data_dict.items(): kv[key] = v for key, value in kv.items(): kv[key] = np.array(value) try: widget = self.stackedWidget.currentWidget() widget.reset_estimator(kv) except Exception as e: msg = 'Tracking Lib. Reset Fail:\n{}'.format(e) self.generateCriticalMessage(msg) def removeTrackingGraphicsItems(self): if self.trackingPathGroup is not None: self.inputScene.removeItem(self.trackingPathGroup) self.trackingPathGroup = None for k, v in self.item_dict.items(): [self.inputScene.removeItem(item) for item in v] v.clear() def initializeTrackingSystem(self): self.isInitialized = False if not (self.videoPlaybackWidget.isOpened() and filterOperation is not None): return False if self.currentFrameNo != 0: ret, frame = self.videoPlaybackWidget.readFrame(0) self.cv_img = frame self.currentFrameNo = 0 self.videoPlaybackWidget.setSliderValueWithoutSignal(0) self.filter = filterOperation(self.cv_img) self.filter.fgbg = self.filterIO.getBackgroundImg() self.filter.isInit = True try: tracking_n = self.stackedWidget.currentWidget().get_tracking_n() attrs = self.stackedWidget.currentWidget().get_attributes() attrs.keys() except Exception as e: msg = 'Tracking Lib. Tracking N or attributes Error:\n{}'.format(e) self.generateCriticalMessage(msg) return max_frame_pos = self.videoPlaybackWidget.getMaxFramePos() self.df = {} for k, t in attrs.items(): if t is None: self.data_dict[k] = {} self.data_dict[k]['name'] = k else: tuples = [] for i in range(tracking_n): for v in t: tuples.append((i, v)) col = pd.MultiIndex.from_tuples(tuples) self.df[k] = pd.DataFrame(index=range(max_frame_pos+1), columns=col, dtype=np.float64).sort_index().sort_index(axis=1) self.df[k].index.name = k self.removeTrackingGraphicsItems() if 'position' in attrs: self.trackingPathGroup = TrackingPathGroup() self.trackingPathGroup.setRect(self.inputScene.sceneRect()) if self.pathCheckBox.checkState()==Qt.Unchecked: self.trackingPathGroup.hide() self.inputScene.addItem(self.trackingPathGroup) self.trackingPathGroup.setDataFrame(self.df['position']) lw = self.trackingPathGroup.autoAdjustLineWidth(self.cv_img.shape) r = self.trackingPathGroup.autoAdjustRadius(self.cv_img.shape) self.lineWidthSpinBox.setValue(lw) self.radiusSpinBox.setValue(r) self.trackingPathGroup.setItemsAreMovable(True) if 'rect' in attrs: self.item_dict['rect'] = [QGraphicsRectItem() for i in range(tracking_n)] for rect_item in self.item_dict['rect']: rect_item.setZValue(1000) self.inputScene.addItem(rect_item) if 'arrow' in attrs: self.item_dict['arrow'] = [MovableArrow() for i in range(tracking_n)] for arrow_item in self.item_dict['arrow']: arrow_item.setZValue(900) if self.arrowCheckBox.checkState()==Qt.Unchecked: arrow_item.hide() self.inputScene.addItem(arrow_item) if 'path' in attrs: self.item_dict['path'] = [QGraphicsPathItem() for i in range(tracking_n)] for path_item in self.item_dict['path']: path_item.setZValue(900) self.inputScene.addItem(path_item) if 'polygon' in attrs: self.item_dict['polygon'] = [QGraphicsPathItem() for i in range(tracking_n)] for path_item in self.item_dict['polygon']: path_item.setZValue(900) self.inputScene.addItem(path_item) self.videoPlaybackWidget.setMaxTickableFrameNo(0) # if self.currentFrameNo != 0: # self.videoPlaybackWidget.moveToFrame(0) self.videoPlaybackWidget.setPlaybackDelta(self.playbackDeltaSpinBox.value()) self.isInitialized = True def evaluate(self, update=True): if not self.isInitialized: return if len(self.df.keys())!=0 and np.all([np.all(pd.notnull(df.loc[self.currentFrameNo])) for df in self.df.values()]): print('update') self.updatePath() self.updateInputGraphicsView() self.updateFrame.emit() return img = self.filter.filterFunc(self.cv_img.copy()) try: widget = self.stackedWidget.currentWidget() res = widget.track(self.cv_img.copy(), img) attrs = widget.get_attributes() except Exception as e: msg = 'Tracking Lib. Tracking method Fail:\n{}'.format(e) self.generateCriticalMessage(msg) return for k,v in res.items(): if k=='path' or k=='rect' or k=='polygon': self.data_dict[k][self.currentFrameNo] = ndarray_to_list(v) continue if not attrs[k]: continue for i in range(len(v)): self.df[k].loc[self.currentFrameNo, i] = v[i] self.videoPlaybackWidget.setMaxTickableFrameNo(self.currentFrameNo+self.videoPlaybackWidget.playbackDelta) self.savedFlag = False if update: self.updatePath() self.updateInputGraphicsView() self.updateFrame.emit() def runObjectTracking(self): if self.filter is None or not self.videoPlaybackWidget.isOpened(): return minFrame = self.currentFrameNo maxFrame = self.videoPlaybackWidget.getMaxFramePos() numFrames = maxFrame-minFrame progress = QProgressDialog("Running...", "Abort", 0, numFrames, self) progress.setWindowModality(Qt.WindowModal) currentFrameNo = self.currentFrameNo for i, frameNo in enumerate(range(minFrame, maxFrame+1)): progress.setValue(i) if progress.wasCanceled(): break ret, frame = self.videoPlaybackWidget.readFrame(frameNo) self.cv_img = frame self.currentFrameNo = frameNo self.evaluate(False) self.videoPlaybackWidget.moveToFrame(currentFrameNo) progress.setValue(numFrames) def eventFilter(self, obj, event): if event.type() == QEvent.KeyPress: print(event.key()) if Qt.Key_Home <= event.key() <= Qt.Key_PageDown: self.videoPlaybackWidget.playbackSlider.keyPressEvent(event) return True return False def generateCriticalMessage(self, msg): tb = sys.exc_info()[-1] f = tb.tb_frame msg = 'File name: {0}\nLine No: {1}\n'.format(f.f_code.co_filename, tb.tb_lineno) + msg reply = QtWidgets.QMessageBox.critical( self, 'Critical', msg, QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton ) return reply
def init_ui(self): self.setWindowTitle(TITLE) self.setWindowIcon(QIcon("img/icon.png")) self.ui.action_Help.setIcon(QIcon("img/help-circle.png")) self.ui.action_About.triggered.connect(self.act_about_triggered) self.ui.act_scan.triggered.connect(self.act_scan_triggered) self.ui.act_scan.setIcon(QIcon("img/scan.png")) self.ui.act_cut.setIcon(QIcon("img/cut_all.png")) self.ui.act_cut.triggered.connect(self.act_cutall_triggered) self.ui.tbl_hosts.setSelectionBehavior(QAbstractItemView.SelectRows) self.ui.tbl_hosts.verticalHeader().setVisible(False) self.ui.tbl_hosts.setColumnCount(TABLE_COLUMN_COUNT) self.ui.tbl_hosts.setHorizontalHeaderLabels([ "IP Address", "MAC Address", "Device Manufacturer", "Custom Name", "Status" ]) self.ui.tbl_hosts.setColumnWidth(0, 100) self.ui.tbl_hosts.setColumnWidth(1, 100) self.ui.tbl_hosts.setColumnWidth(2, 240) self.ui.tbl_hosts.setColumnWidth(3, 100) self.ui.tbl_hosts.setShowGrid(False) self.ui.tbl_hosts.itemChanged.connect(self.hosts_item_changed) self.ui.actionShow_Icons_Text.setData(Qt.ToolButtonTextUnderIcon) self.ui.actionShow_Icons.setData(Qt.ToolButtonIconOnly) self.ui.actionCustom_Name.setData(R_NAME) self.ui.actionIP_Address.setData(R_IP) self.ui.actionMAC_Address.setData(R_MAC) self.ui.actionDevice_Manifacturer.setData(R_MAC_MAN) self.ui.actionCustom_Name.setChecked( self.settings.value("tbl_show_{}".format(R_NAME), 1, type=int)) self.ui.actionIP_Address.setChecked( self.settings.value("tbl_show_{}".format(R_IP), 1, type=int)) self.ui.actionMAC_Address.setChecked( self.settings.value("tbl_show_{}".format(R_MAC), 1, type=int)) self.ui.actionDevice_Manifacturer.setChecked( self.settings.value("tbl_show_{}".format(R_MAC_MAN), 1, type=int)) group = QActionGroup(self) group.addAction(self.ui.actionShow_Icons) group.addAction(self.ui.actionShow_Icons_Text) group.triggered.connect(self.act_toolbar_show) group2 = QActionGroup(self) group2.setExclusive(False) group2.addAction(self.ui.actionCustom_Name) group2.addAction(self.ui.actionIP_Address) group2.addAction(self.ui.actionMAC_Address) group2.addAction(self.ui.actionDevice_Manifacturer) group2.triggered.connect(self.act_setting_show) for a in group2.actions(): if a.isChecked(): self.ui.tbl_hosts.showColumn(a.data()) else: self.ui.tbl_hosts.hideColumn(a.data()) if int(self.settings.value( "toolbar_show", Qt.ToolButtonIconOnly)) == Qt.ToolButtonIconOnly: self.ui.actionShow_Icons.setChecked(True) self.ui.toolBar.setToolButtonStyle(Qt.ToolButtonIconOnly) else: self.ui.actionShow_Icons_Text.setChecked(True) self.ui.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
class PangoToolBarWidget(QToolBar): del_labels_signal = pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) self.setIconSize(QSize(16, 16)) self.scene = None spacer_left = QWidget() spacer_left.setFixedWidth(10) spacer_middle = QWidget() spacer_middle.setFixedWidth(50) spacer_right = QWidget() spacer_right.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) # Label Related self.color_display = QLabel() self.color_display.setFixedSize(QSize(50, 20)) self.label_select = self.LabelSelect(self.color_display) self.label_select.lineEdit().returnPressed.connect(self.add) self.add_action = QAction("Add") self.add_action.triggered.connect(self.add) icon = pango_get_icon("add") self.add_action.setIcon(icon) self.del_action = QAction("Delete") self.del_action.triggered.connect(self.delete) icon = pango_get_icon("del") self.del_action.setIcon(icon) self.color_action = QAction("Palette") self.color_action.triggered.connect(self.set_color) icon = pango_get_icon("palette") self.color_action.setIcon(icon) # Tool Related self.size_select = QSpinBox() self.size_select.valueChanged.connect(self.set_tool_size) self.size_select.setSuffix("px") self.size_select.setRange(1, 99) self.size_select.setSingleStep(5) self.size_select.setValue(10) self.pan_action = QAction("Pan") self.lasso_action = QAction("Lasso") self.path_action = QAction("Path") self.bbox_action = QAction("Bbox") self.poly_action = QAction("Poly") self.action_group = QActionGroup(self) self.action_group.setExclusive(True) self.action_group.triggered.connect(self.set_tool) self.action_group.addAction(self.pan_action) self.action_group.addAction(self.lasso_action) self.action_group.addAction(self.bbox_action) self.action_group.addAction(self.poly_action) self.action_group.addAction(self.path_action) for action in self.action_group.actions(): icon = pango_get_icon(action.text()) action.setIcon(icon) action.setCheckable(True) # Other font = QFont("Arial", 10) self.info_display = QLabel() self.info_display.setFixedWidth(100) self.info_display.setFont(font) self.coord_display = QLabel() self.coord_display.setFixedWidth(40) self.coord_display.setFont(font) # Layouts self.addWidget(spacer_left) self.addWidget(self.color_display) self.addWidget(self.label_select) self.addAction(self.add_action) self.addAction(self.del_action) self.addAction(self.color_action) self.addWidget(spacer_middle) self.addActions(self.action_group.actions()) self.addWidget(self.size_select) self.addWidget(spacer_right) self.addWidget(self.info_display) self.addWidget(self.coord_display) self.size_select.setEnabled(False) self.del_action.setEnabled(False) self.action_group.setEnabled(False) self.color_action.setEnabled(False) def set_color(self): dialog = QColorDialog() dialog.setOption(QColorDialog.ShowAlphaChannel, False) color = dialog.getColor() if color == QColor(): return row = self.label_select.currentIndex() label = self.label_select.model().item(row, 0) label.color = color label.set_icon() for i in range(0, label.rowCount()): shape = label.child(i) shape.set_icon() # Refresh label (for scene reticle etc.) self.label_select.setCurrentIndex(0) self.label_select.setCurrentIndex(row) self.label_select.color_display.update() def add(self): self.del_action.setEnabled(True) self.color_action.setEnabled(True) if self.scene.fpath is not None: self.action_group.setEnabled(True) item = PangoLabelItem() root = self.label_select.model().invisibleRootItem() root.appendRow(item) item.name = "Unnamed Label " + str(item.row()) item.visible = True item.color = pango_get_palette(item.row()) item.set_icon() bottom_row = self.label_select.model().rowCount() - 1 self.label_select.setCurrentIndex(bottom_row) if bottom_row == 0: self.label_select.currentIndexChanged.emit( self.label_select.currentIndex()) def delete(self): self.reset_tool() self.del_labels_signal.emit(self.label_select.currentIndex()) if self.label_select.model().rowCount() == 0: self.del_action.setEnabled(False) self.action_group.setEnabled(False) self.color_action.setEnabled(False) def set_tool(self, action): if self.scene is None: return self.scene.tool = action.text() self.scene.reset_com() self.scene.reticle.setVisible(self.scene.tool == "Path") self.scene.views()[0].set_cursor(self.scene.tool) if action.text() == "Path" or action.text() == "Filled Path": self.size_select.setEnabled(True) else: self.size_select.setEnabled(False) def reset_tool(self): self.lasso_action.setChecked(True) self.set_tool(self.lasso_action) def set_tool_size(self, size, additive=False): if self.scene is None: return if additive: self.scene.tool_size += size else: self.scene.tool_size = size self.scene.reset_com() self.scene.reticle.setRect(-size / 2, -size / 2, size, size) if self.size_select.value() != self.scene.tool_size: self.size_select.setValue(self.scene.tool_size) if not self.scene.sceneRect().contains(self.scene.reticle.pos()): view = self.scene.views()[0] x = self.size_select.geometry().center().x() y = view.rect().top() + size / 2 self.scene.reticle.setPos(view.mapToScene(QPoint(x, y))) def set_scene(self, scene): if self.scene is not None: self.scene.clear_tool.disconnect(self.reset_tool) self.scene = scene self.scene.clear_tool.connect(self.reset_tool) self.reset_tool() class LabelSelect(QComboBox): def __init__(self, color_display, parent=None): super().__init__(parent) self.color_display = color_display self.setFixedWidth(150) self.setEditable(True) self.editTextChanged.connect(self.edit_text_changed) def paintEvent(self, event): super().paintEvent(event) item = self.model().item(self.currentIndex()) if item is not None and item.color is not None: self.color_display.setStyleSheet( "QLabel { background-color : " + item.color.name() + "}") else: self.color_display.setStyleSheet( "QLabel { background-color : rgba(255, 255, 255, 10)}") def edit_text_changed(self, text): row = self.currentIndex() self.setItemData(row, text, Qt.DisplayRole) def select_next_label(self): idx = self.currentIndex() + 1 if idx > -1 and idx < self.count(): self.setCurrentIndex(idx) def select_prev_label(self): idx = self.currentIndex() - 1 if idx > -1 and idx < self.count(): self.setCurrentIndex(idx)
class MainWindow(QMainWindow, Ui_MainWindow): dictChanged = pyqtSignal(str) # Tab indexes TabInfos = 0 TabSummary = 1 TabPersos = 2 TabPlots = 3 TabWorld = 4 TabOutline = 5 TabRedac = 6 def __init__(self): QMainWindow.__init__(self) self.setupUi(self) self.currentProject = None self.readSettings() # UI self.setupMoreUi() # Welcome self.welcome.updateValues() self.stack.setCurrentIndex(0) # Word count self.mprWordCount = QSignalMapper(self) for t, i in [(self.txtSummarySentence, 0), (self.txtSummaryPara, 1), (self.txtSummaryPage, 2), (self.txtSummaryFull, 3)]: t.textChanged.connect(self.mprWordCount.map) self.mprWordCount.setMapping(t, i) self.mprWordCount.mapped.connect(self.wordCount) # Snowflake Method Cycle self.mapperCycle = QSignalMapper(self) for t, i in [(self.btnStepTwo, 0), (self.btnStepThree, 1), (self.btnStepFour, 2), (self.btnStepFive, 3), (self.btnStepSix, 4), (self.btnStepSeven, 5), (self.btnStepEight, 6)]: t.clicked.connect(self.mapperCycle.map) self.mapperCycle.setMapping(t, i) self.mapperCycle.mapped.connect(self.clickCycle) self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged) self.cmbSummary.setCurrentIndex(0) self.cmbSummary.currentIndexChanged.emit(0) # Main Menu for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(False) self.actOpen.triggered.connect(self.welcome.openFile) self.actSave.triggered.connect(self.saveDatas) self.actSaveAs.triggered.connect(self.welcome.saveAsFile) self.actCompile.triggered.connect(self.doCompile) self.actLabels.triggered.connect(self.settingsLabel) self.actStatus.triggered.connect(self.settingsStatus) self.actSettings.triggered.connect(self.settingsWindow) self.actCloseProject.triggered.connect(self.closeProject) self.actQuit.triggered.connect(self.close) self.actToolFrequency.triggered.connect(self.frequencyAnalyzer) self.generateViewMenu() self.actModeGroup = QActionGroup(self) self.actModeSimple.setActionGroup(self.actModeGroup) self.actModeFiction.setActionGroup(self.actModeGroup) self.actModeSnowflake.setActionGroup(self.actModeGroup) self.actModeSimple.triggered.connect(self.setViewModeSimple) self.actModeFiction.triggered.connect(self.setViewModeFiction) self.actModeSnowflake.setEnabled(False) self.makeUIConnections() # self.loadProject(os.path.join(appPath(), "test_project.zip")) ############################################################################### # SUMMARY ############################################################################### def summaryPageChanged(self, index): fractalButtons = [ self.btnStepTwo, self.btnStepThree, self.btnStepFive, self.btnStepSeven, ] for b in fractalButtons: b.setVisible(fractalButtons.index(b) == index) ############################################################################### # OUTLINE ############################################################################### def outlineRemoveItemsRedac(self): self.treeRedacOutline.delete() def outlineRemoveItemsOutline(self): self.treeOutlineOutline.delete() ############################################################################### # CHARACTERS ############################################################################### def changeCurrentCharacter(self, trash=None): """ @return: """ c = self.lstCharacters.currentCharacter() if not c: self.tabPlot.setEnabled(False) return self.tabPersos.setEnabled(True) index = c.index() for w in [ self.txtPersoName, self.sldPersoImportance, self.txtPersoMotivation, self.txtPersoGoal, self.txtPersoConflict, self.txtPersoEpiphany, self.txtPersoSummarySentence, self.txtPersoSummaryPara, self.txtPersoSummaryFull, self.txtPersoNotes, ]: w.setCurrentModelIndex(index) # Button color self.updateCharacterColor(c.ID()) # Character Infos self.tblPersoInfos.setRootIndex(index) if self.mdlCharacter.rowCount(index): self.updatePersoInfoView() def updatePersoInfoView(self): self.tblPersoInfos.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeToContents) self.tblPersoInfos.horizontalHeader().setSectionResizeMode( 1, QHeaderView.Stretch) self.tblPersoInfos.verticalHeader().hide() def updateCharacterColor(self, ID): c = self.mdlCharacter.getCharacterByID(ID) color = c.color().name() self.btnPersoColor.setStyleSheet("background:{};".format(color)) ############################################################################### # PLOTS ############################################################################### def changeCurrentPlot(self): index = self.lstPlots.currentPlotIndex() if not index.isValid(): self.tabPlot.setEnabled(False) return self.tabPlot.setEnabled(True) self.txtPlotName.setCurrentModelIndex(index) self.txtPlotDescription.setCurrentModelIndex(index) self.txtPlotResult.setCurrentModelIndex(index) self.sldPlotImportance.setCurrentModelIndex(index) self.lstPlotPerso.setRootIndex( index.sibling(index.row(), Plot.characters.value)) subplotindex = index.sibling(index.row(), Plot.steps.value) self.lstSubPlots.setRootIndex(subplotindex) if self.mdlPlots.rowCount(subplotindex): self.updateSubPlotView() # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex()) self.txtSubPlotSummary.setEnabled(False) self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText("") self._updatingSubPlot = False self.lstPlotPerso.selectionModel().clear() def updateSubPlotView(self): # Hide columns for i in range(self.mdlPlots.columnCount()): self.lstSubPlots.hideColumn(i) self.lstSubPlots.showColumn(PlotStep.name.value) self.lstSubPlots.showColumn(PlotStep.meta.value) self.lstSubPlots.horizontalHeader().setSectionResizeMode( PlotStep.name.value, QHeaderView.Stretch) self.lstSubPlots.horizontalHeader().setSectionResizeMode( PlotStep.meta.value, QHeaderView.ResizeToContents) self.lstSubPlots.verticalHeader().hide() def changeCurrentSubPlot(self, index): # Got segfaults when using textEditView model system, so ad hoc stuff. index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) if not item: self.txtSubPlotSummary.setEnabled(False) return self.txtSubPlotSummary.setEnabled(True) txt = item.text() self._updatingSubPlot = True self.txtSubPlotSummary.setPlainText(txt) self._updatingSubPlot = False def updateSubPlotSummary(self): if self._updatingSubPlot: return index = self.lstSubPlots.currentIndex() if not index.isValid(): return index = index.sibling(index.row(), PlotStep.summary.value) item = self.mdlPlots.itemFromIndex(index) self._updatingSubPlot = True item.setText(self.txtSubPlotSummary.toPlainText()) self._updatingSubPlot = False def plotPersoSelectionChanged(self): "Enables or disables remove plot perso button." self.btnRmPlotPerso.setEnabled( len(self.lstPlotPerso.selectedIndexes()) != 0) ############################################################################### # WORLD ############################################################################### def changeCurrentWorld(self): index = self.mdlWorld.selectedIndex() if not index.isValid(): self.tabWorld.setEnabled(False) return self.tabWorld.setEnabled(True) self.txtWorldName.setCurrentModelIndex(index) self.txtWorldDescription.setCurrentModelIndex(index) self.txtWorldPassion.setCurrentModelIndex(index) # self.txtWorldConflict.setCurrentModelIndex(index) # # ############################################################################### # # LOAD AND SAVE # ############################################################################### def loadProject(self, project, loadFromFile=True): """Loads the project ``project``. If ``loadFromFile`` is False, then it does not load datas from file. It assumes that the datas have been populated in a different way.""" if loadFromFile and not os.path.exists(project): print( self.tr("The file {} does not exist. Try again.").format( project)) self.statusBar().showMessage( self.tr("The file {} does not exist. Try again.").format( project), 5000) return if loadFromFile: # Load empty settings imp.reload(settings) # Load data self.loadEmptyDatas() self.loadDatas(project) self.makeConnections() # Load settings for i in settings.openIndexes: idx = self.mdlOutline.getIndexByID(i) self.mainEditor.setCurrentModelIndex(idx, newTab=True) self.generateViewMenu() self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor) self.actSpellcheck.setChecked(settings.spellcheck) self.toggleSpellcheck(settings.spellcheck) self.updateMenuDict() self.setDictionary() self.mainEditor.setFolderView(settings.folderView) self.mainEditor.updateFolderViewButtons(settings.folderView) self.tabMain.setCurrentIndex(settings.lastTab) # We force to emit even if it opens on the current tab self.tabMain.currentChanged.emit(settings.lastTab) self.mainEditor.updateCorkBackground() if settings.viewMode == "simple": self.setViewModeSimple() else: self.setViewModeFiction() # Set autosave self.saveTimer = QTimer() self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000) self.saveTimer.setSingleShot(False) self.saveTimer.timeout.connect(self.saveDatas) if settings.autoSave: self.saveTimer.start() # Set autosave if no changes self.saveTimerNoChanges = QTimer() self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000) self.saveTimerNoChanges.setSingleShot(True) self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges) self.mdlOutline.dataChanged.connect(self.startTimerNoChanges) self.mdlCharacter.dataChanged.connect(self.startTimerNoChanges) self.mdlPlots.dataChanged.connect(self.startTimerNoChanges) self.mdlWorld.dataChanged.connect(self.startTimerNoChanges) # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges) self.mdlStatus.dataChanged.connect(self.startTimerNoChanges) self.mdlLabels.dataChanged.connect(self.startTimerNoChanges) self.saveTimerNoChanges.timeout.connect(self.saveDatas) self.saveTimerNoChanges.stop() # UI for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(True) # FIXME: set Window's name: project name # Stuff # self.checkPersosID() # Should'n be necessary any longer self.currentProject = project QSettings().setValue("lastProject", project) # Show main Window self.stack.setCurrentIndex(1) def closeProject(self): if not self.currentProject: return # Close open tabs in editor self.mainEditor.closeAllTabs() # Save datas self.saveDatas() self.currentProject = None QSettings().setValue("lastProject", "") # Clear datas self.loadEmptyDatas() self.saveTimer.stop() loadSave.clearSaveCache() # UI for i in [ self.actSave, self.actSaveAs, self.actCloseProject, self.menuEdit, self.menuView, self.menuTools, self.menuHelp ]: i.setEnabled(False) # Reload recent files self.welcome.updateValues() # Show welcome dialog self.stack.setCurrentIndex(0) def readSettings(self): # Load State and geometry sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) if sttgns.contains("geometry"): self.restoreGeometry(sttgns.value("geometry")) if sttgns.contains("windowState"): self.restoreState(sttgns.value("windowState")) else: self.dckCheatSheet.hide() self.dckSearch.hide() if sttgns.contains("metadataState"): state = [ False if v == "false" else True for v in sttgns.value("metadataState") ] self.redacMetadata.restoreState(state) if sttgns.contains("revisionsState"): state = [ False if v == "false" else True for v in sttgns.value("revisionsState") ] self.redacMetadata.revisions.restoreState(state) if sttgns.contains("splitterRedacH"): self.splitterRedacH.restoreState(sttgns.value("splitterRedacH")) if sttgns.contains("splitterRedacV"): self.splitterRedacV.restoreState(sttgns.value("splitterRedacV")) if sttgns.contains("toolbar"): # self.toolbar is not initialized yet, so we just store balue self._toolbarState = sttgns.value("toolbar") else: self._toolbarState = "" def closeEvent(self, event): # Save State and geometry and other things sttgns = QSettings(qApp.organizationName(), qApp.applicationName()) sttgns.setValue("geometry", self.saveGeometry()) sttgns.setValue("windowState", self.saveState()) sttgns.setValue("metadataState", self.redacMetadata.saveState()) sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState()) sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState()) sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState()) sttgns.setValue("toolbar", self.toolbar.saveState()) # Specific settings to save before quitting settings.lastTab = self.tabMain.currentIndex() if self.currentProject: # Remembering the current items (stores outlineItem's ID) sel = [] for i in range(self.mainEditor.tab.count()): sel.append( self.mdlOutline.ID( self.mainEditor.tab.widget(i).currentIndex)) settings.openIndexes = sel # Save data from models if self.currentProject and settings.saveOnQuit: self.saveDatas() # closeEvent # QMainWindow.closeEvent(self, event) # Causin segfaults? def startTimerNoChanges(self): if settings.autoSaveNoChanges: self.saveTimerNoChanges.start() def saveDatas(self, projectName=None): """Saves the current project (in self.currentProject). If ``projectName`` is given, currentProject becomes projectName. In other words, it "saves as...". """ if projectName: self.currentProject = projectName QSettings().setValue("lastProject", projectName) loadSave.saveProject() # version=0 self.saveTimerNoChanges.stop() # Giving some feedback print(self.tr("Project {} saved.").format(self.currentProject)) self.statusBar().showMessage( self.tr("Project {} saved.").format(self.currentProject), 5000) def loadEmptyDatas(self): self.mdlFlatData = QStandardItemModel(self) self.mdlCharacter = characterModel(self) # self.mdlPersosProxy = persosProxyModel(self) # self.mdlPersosInfos = QStandardItemModel(self) self.mdlLabels = QStandardItemModel(self) self.mdlStatus = QStandardItemModel(self) self.mdlPlots = plotModel(self) self.mdlOutline = outlineModel(self) self.mdlWorld = worldModel(self) def loadDatas(self, project): errors = loadSave.loadProject(project) # Giving some feedback if not errors: print(self.tr("Project {} loaded.").format(project)) self.statusBar().showMessage( self.tr("Project {} loaded.").format(project), 5000) else: print( self.tr("Project {} loaded with some errors:").format(project)) for e in errors: print(self.tr(" * {} wasn't found in project file.").format(e)) self.statusBar().showMessage( self.tr("Project {} loaded with some errors.").format(project), 5000) ############################################################################### # MAIN CONNECTIONS ############################################################################### def makeUIConnections(self): "Connections that have to be made once only, even when a new project is loaded." self.lstCharacters.currentItemChanged.connect( self.changeCurrentCharacter, AUC) self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC) self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC) self.txtSubPlotSummary.document().contentsChanged.connect( self.updateSubPlotSummary, AUC) self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC) self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC) self.btnOutlineAddFolder.clicked.connect( self.treeOutlineOutline.addFolder, AUC) self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC) self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC) self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC) self.btnOutlineRemoveItem.clicked.connect( self.outlineRemoveItemsOutline, AUC) self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup) def makeConnections(self): # Flat datas (Summary and general infos) for widget, col in [ (self.txtSummarySituation, 0), (self.txtSummarySentence, 1), (self.txtSummarySentence_2, 1), (self.txtSummaryPara, 2), (self.txtSummaryPara_2, 2), (self.txtPlotSummaryPara, 2), (self.txtSummaryPage, 3), (self.txtSummaryPage_2, 3), (self.txtPlotSummaryPage, 3), (self.txtSummaryFull, 4), (self.txtPlotSummaryFull, 4), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(1, col)) for widget, col in [ (self.txtGeneralTitle, 0), (self.txtGeneralSubtitle, 1), (self.txtGeneralSerie, 2), (self.txtGeneralVolume, 3), (self.txtGeneralGenre, 4), (self.txtGeneralLicense, 5), (self.txtGeneralAuthor, 6), (self.txtGeneralEmail, 7), ]: widget.setModel(self.mdlFlatData) widget.setColumn(col) widget.setCurrentModelIndex(self.mdlFlatData.index(0, col)) # Characters self.lstCharacters.setCharactersModel(self.mdlCharacter) self.tblPersoInfos.setModel(self.mdlCharacter) self.btnAddPerso.clicked.connect(self.mdlCharacter.addCharacter, AUC) try: self.btnRmPerso.clicked.connect(self.lstCharacters.removeCharacter, AUC) self.btnPersoColor.clicked.connect( self.lstCharacters.choseCharacterColor, AUC) self.btnPersoAddInfo.clicked.connect( self.lstCharacters.addCharacterInfo, AUC) self.btnPersoRmInfo.clicked.connect( self.lstCharacters.removeCharacterInfo, AUC) except TypeError: # Connection has already been made pass for w, c in [(self.txtPersoName, Character.name.value), (self.sldPersoImportance, Character.importance.value), (self.txtPersoMotivation, Character.motivation.value), (self.txtPersoGoal, Character.goal.value), (self.txtPersoConflict, Character.conflict.value), (self.txtPersoEpiphany, Character.epiphany.value), (self.txtPersoSummarySentence, Character.summarySentence.value), (self.txtPersoSummaryPara, Character.summaryPara.value), (self.txtPersoSummaryFull, Character.summaryFull.value), (self.txtPersoNotes, Character.notes.value)]: w.setModel(self.mdlCharacter) w.setColumn(c) self.tabPersos.setEnabled(False) # Plots self.lstPlots.setPlotModel(self.mdlPlots) self.lstPlotPerso.setModel(self.mdlPlots) self.lstSubPlots.setModel(self.mdlPlots) self._updatingSubPlot = False self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC) self.btnRmPlot.clicked.connect( lambda: self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC) self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC) self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC) self.lstPlotPerso.selectionModel().selectionChanged.connect( self.plotPersoSelectionChanged) self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC) for w, c in [ (self.txtPlotName, Plot.name.value), (self.txtPlotDescription, Plot.description.value), (self.txtPlotResult, Plot.result.value), (self.sldPlotImportance, Plot.importance.value), ]: w.setModel(self.mdlPlots) w.setColumn(c) self.tabPlot.setEnabled(False) self.mdlPlots.updatePlotPersoButton() self.mdlCharacter.dataChanged.connect( self.mdlPlots.updatePlotPersoButton) self.lstOutlinePlots.setPlotModel(self.mdlPlots) self.lstOutlinePlots.setShowSubPlot(True) self.plotCharacterDelegate = outlineCharacterDelegate( self.mdlCharacter, self) self.lstPlotPerso.setItemDelegate(self.plotCharacterDelegate) self.plotDelegate = plotDelegate(self) self.lstSubPlots.setItemDelegateForColumn(PlotStep.meta.value, self.plotDelegate) # World self.treeWorld.setModel(self.mdlWorld) for i in range(self.mdlWorld.columnCount()): self.treeWorld.hideColumn(i) self.treeWorld.showColumn(0) self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu()) self.treeWorld.selectionModel().selectionChanged.connect( self.changeCurrentWorld, AUC) self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC) self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC) for w, c in [ (self.txtWorldName, World.name.value), (self.txtWorldDescription, World.description.value), (self.txtWorldPassion, World.passion.value), (self.txtWorldConflict, World.conflict.value), ]: w.setModel(self.mdlWorld) w.setColumn(c) self.tabWorld.setEnabled(False) self.treeWorld.expandAll() # Outline self.treeRedacOutline.setModel(self.mdlOutline) self.treeOutlineOutline.setModelCharacters(self.mdlCharacter) self.treeOutlineOutline.setModelLabels(self.mdlLabels) self.treeOutlineOutline.setModelStatus(self.mdlStatus) self.redacMetadata.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.outlineItemEditor.setModels(self.mdlOutline, self.mdlCharacter, self.mdlLabels, self.mdlStatus) self.treeOutlineOutline.setModel(self.mdlOutline) # self.redacEditor.setModel(self.mdlOutline) self.storylineView.setModels(self.mdlOutline, self.mdlCharacter, self.mdlPlots) self.treeOutlineOutline.selectionModel().selectionChanged.connect( lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) self.treeOutlineOutline.clicked.connect( lambda: self.outlineItemEditor.selectionChanged( self.treeOutlineOutline), AUC) # Sync selection self.treeRedacOutline.selectionModel().selectionChanged.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.clicked.connect( lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC) self.treeRedacOutline.selectionModel().selectionChanged.connect( self.mainEditor.selectionChanged, AUC) # Cheat Sheet self.cheatSheet.setModels() # Debug self.mdlFlatData.setVerticalHeaderLabels( ["Infos générales", "Summary"]) self.tblDebugFlatData.setModel(self.mdlFlatData) self.tblDebugPersos.setModel(self.mdlCharacter) self.tblDebugPersosInfos.setModel(self.mdlCharacter) self.tblDebugPersos.selectionModel().currentChanged.connect( lambda: self.tblDebugPersosInfos.setRootIndex( self.mdlCharacter.index( self.tblDebugPersos.selectionModel().currentIndex().row(), Character.name.value)), AUC) self.tblDebugPlots.setModel(self.mdlPlots) self.tblDebugPlotsPersos.setModel(self.mdlPlots) self.tblDebugSubPlots.setModel(self.mdlPlots) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugPlotsPersos.setRootIndex( self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.characters.value)), AUC) self.tblDebugPlots.selectionModel().currentChanged.connect( lambda: self.tblDebugSubPlots.setRootIndex( self.mdlPlots.index( self.tblDebugPlots.selectionModel().currentIndex().row(), Plot.steps.value)), AUC) self.treeDebugWorld.setModel(self.mdlWorld) self.treeDebugOutline.setModel(self.mdlOutline) self.lstDebugLabels.setModel(self.mdlLabels) self.lstDebugStatus.setModel(self.mdlStatus) ############################################################################### # GENERAL AKA UNSORTED ############################################################################### def clickCycle(self, i): if i == 0: # step 2 - paragraph summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(1) if i == 1: # step 3 - characters summary self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(0) if i == 2: # step 4 - page summary self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(2) if i == 3: # step 5 - characters description self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(1) if i == 4: # step 6 - four page synopsis self.tabMain.setCurrentIndex(self.TabSummary) self.tabSummary.setCurrentIndex(3) if i == 5: # step 7 - full character charts self.tabMain.setCurrentIndex(self.TabPersos) self.tabPersos.setCurrentIndex(2) if i == 6: # step 8 - scene list self.tabMain.setCurrentIndex(self.TabPlots) def wordCount(self, i): src = { 0: self.txtSummarySentence, 1: self.txtSummaryPara, 2: self.txtSummaryPage, 3: self.txtSummaryFull }[i] lbl = { 0: self.lblSummaryWCSentence, 1: self.lblSummaryWCPara, 2: self.lblSummaryWCPage, 3: self.lblSummaryWCFull }[i] wc = wordCount(src.toPlainText()) if i in [2, 3]: pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.) else: pages = "" lbl.setText(self.tr("Words: {}{}").format(wc, pages)) def setupMoreUi(self): # Tool bar on the right self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self) self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots) self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac) self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac) if self._toolbarState: self.toolbar.restoreState(self._toolbarState) # Custom "tab" bar on the left self.lstTabs.setIconSize(QSize(48, 48)) for i in range(self.tabMain.count()): icons = [ "general-128px.png", "summary-128px.png", "characters-128px.png", "plot-128px.png", "world-128px.png", "outline-128px.png", "redaction-128px.png", "" ] self.tabMain.setTabIcon( i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i])))) item = QListWidgetItem(self.tabMain.tabIcon(i), self.tabMain.tabText(i)) item.setSizeHint(QSize(item.sizeHint().width(), 64)) item.setTextAlignment(Qt.AlignCenter) self.lstTabs.addItem(item) self.tabMain.tabBar().hide() self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex) self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow) # Splitters self.splitterPersos.setStretchFactor(0, 25) self.splitterPersos.setStretchFactor(1, 75) self.splitterPlot.setStretchFactor(0, 20) self.splitterPlot.setStretchFactor(1, 60) self.splitterPlot.setStretchFactor(2, 30) self.splitterWorld.setStretchFactor(0, 25) self.splitterWorld.setStretchFactor(1, 75) self.splitterOutlineH.setStretchFactor(0, 25) self.splitterOutlineH.setStretchFactor(1, 75) self.splitterOutlineV.setStretchFactor(0, 75) self.splitterOutlineV.setStretchFactor(1, 25) self.splitterRedacV.setStretchFactor(0, 75) self.splitterRedacV.setStretchFactor(1, 25) self.splitterRedacH.setStretchFactor(0, 30) self.splitterRedacH.setStretchFactor(1, 40) self.splitterRedacH.setStretchFactor(2, 30) # QFormLayout stretch for w in [ self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict ]: s = w.sizePolicy() s.setVerticalStretch(1) w.setSizePolicy(s) # Help box references = [ (self.lytTabOverview, self.tr("Enter infos about your book, and yourself."), 0), (self.lytSituation, self. tr("""The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous evil wizard could wasn't abled to kill a baby?' (Harry Potter)""" ), 1), (self.lytSummary, self. tr("""Take time to think about a one sentence (~50 words) summary of your book. Then expand it to a paragraph, then to a page, then to a full summary."""), 1), (self.lytTabPersos, self.tr("Create your characters."), 0), (self.lytTabPlot, self.tr("Develop plots."), 0), (self.lytTabOutline, self.tr("Create the outline of your masterpiece."), 0), (self.lytTabRedac, self.tr("Write."), 0), (self.lytTabDebug, self.tr("Debug infos. Sometimes useful."), 0) ] for widget, text, pos in references: label = helpLabel(text, self) self.actShowHelp.toggled.connect(label.setVisible, AUC) widget.layout().insertWidget(pos, label) self.actShowHelp.setChecked(False) # Spellcheck if enchant: self.menuDict = QMenu(self.tr("Dictionary")) self.menuDictGroup = QActionGroup(self) self.updateMenuDict() self.menuTools.addMenu(self.menuDict) self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC) self.dictChanged.connect(self.mainEditor.setDict, AUC) self.dictChanged.connect(self.redacMetadata.setDict, AUC) self.dictChanged.connect(self.outlineItemEditor.setDict, AUC) else: # No Spell check support self.actSpellcheck.setVisible(False) a = QAction(self.tr("Install PyEnchant to use spellcheck"), self) a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning)) a.triggered.connect(self.openPyEnchantWebPage, AUC) self.menuTools.addAction(a) ############################################################################### # SPELLCHECK ############################################################################### def updateMenuDict(self): if not enchant: return self.menuDict.clear() for i in enchant.list_dicts(): a = QAction(str(i[0]), self) a.setCheckable(True) if settings.dict is None: settings.dict = enchant.get_default_language() if str(i[0]) == settings.dict: a.setChecked(True) a.triggered.connect(self.setDictionary, AUC) self.menuDictGroup.addAction(a) self.menuDict.addAction(a) def setDictionary(self): if not enchant: return for i in self.menuDictGroup.actions(): if i.isChecked(): # self.dictChanged.emit(i.text().replace("&", "")) settings.dict = i.text().replace("&", "") # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.setDict(settings.dict) def openPyEnchantWebPage(self): from PyQt5.QtGui import QDesktopServices QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/")) def toggleSpellcheck(self, val): settings.spellcheck = val # Find all textEditView from self, and toggle spellcheck for w in self.findChildren(textEditView, QRegExp(".*"), Qt.FindChildrenRecursively): w.toggleSpellcheck(val) ############################################################################### # SETTINGS ############################################################################### def settingsLabel(self): self.settingsWindow(3) def settingsStatus(self): self.settingsWindow(4) def settingsWindow(self, tab=None): self.sw = settingsWindow(self) self.sw.hide() self.sw.setWindowModality(Qt.ApplicationModal) self.sw.setWindowFlags(Qt.Dialog) r = self.sw.geometry() r2 = self.geometry() self.sw.move(r2.center() - r.center()) if tab: self.sw.setTab(tab) self.sw.show() ############################################################################### # TOOLS ############################################################################### def frequencyAnalyzer(self): self.fw = frequencyAnalyzer(self) self.fw.show() ############################################################################### # VIEW MENU ############################################################################### def generateViewMenu(self): values = [ (self.tr("Nothing"), "Nothing"), (self.tr("POV"), "POV"), (self.tr("Label"), "Label"), (self.tr("Progress"), "Progress"), (self.tr("Compile"), "Compile"), ] menus = [(self.tr("Tree"), "Tree"), (self.tr("Index cards"), "Cork"), (self.tr("Outline"), "Outline")] submenus = { "Tree": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], "Cork": [ (self.tr("Icon"), "Icon"), (self.tr("Text"), "Text"), (self.tr("Background"), "Background"), (self.tr("Border"), "Border"), (self.tr("Corner"), "Corner"), ], "Outline": [ (self.tr("Icon color"), "Icon"), (self.tr("Text color"), "Text"), (self.tr("Background color"), "Background"), ], } self.menuView.clear() self.menuView.addMenu(self.menuMode) self.menuView.addSeparator() # print("Generating menus with", settings.viewSettings) for mnu, mnud in menus: m = QMenu(mnu, self.menuView) for s, sd in submenus[mnud]: m2 = QMenu(s, m) agp = QActionGroup(m2) for v, vd in values: a = QAction(v, m) a.setCheckable(True) a.setData("{},{},{}".format(mnud, sd, vd)) if settings.viewSettings[mnud][sd] == vd: a.setChecked(True) a.triggered.connect(self.setViewSettingsAction, AUC) agp.addAction(a) m2.addAction(a) m.addMenu(m2) self.menuView.addMenu(m) def setViewSettingsAction(self): action = self.sender() item, part, element = action.data().split(",") self.setViewSettings(item, part, element) def setViewSettings(self, item, part, element): settings.viewSettings[item][part] = element if item == "Cork": self.mainEditor.updateCorkView() if item == "Outline": self.mainEditor.updateTreeView() self.treeOutlineOutline.viewport().update() if item == "Tree": self.treeRedacOutline.viewport().update() ############################################################################### # VIEW MODES ############################################################################### def setViewModeSimple(self): settings.viewMode = "simple" self.tabMain.setCurrentIndex(self.TabRedac) self.viewModeFictionVisibilitySwitch(False) self.actModeSimple.setChecked(True) def setViewModeFiction(self): settings.viewMode = "fiction" self.viewModeFictionVisibilitySwitch(True) self.actModeFiction.setChecked(True) def viewModeFictionVisibilitySwitch(self, val): """ Swtiches the visibility of some UI components useful for fiction only @param val: sets visibility to val """ # Menu navigation & boutton in toolbar self.toolbar.setDockVisibility(self.dckNavigation, val) # POV in metadatas from manuskript.ui.views.propertiesView import propertiesView for w in findWidgetsOfClass(propertiesView): w.lblPOV.setVisible(val) w.cmbPOV.setVisible(val) # POV in outline view if Outline.POV.value in settings.outlineViewColumns: settings.outlineViewColumns.remove(Outline.POV.value) from manuskript.ui.views.outlineView import outlineView for w in findWidgetsOfClass(outlineView): w.hideColumns() # TODO: clean up all other fiction things in non-fiction view mode # Character in search widget # POV in settings / views ############################################################################### # COMPILE ############################################################################### def doCompile(self): self.compileDialog = compileDialog() self.compileDialog.show()
class Gui(QMainWindow, Ui_MainWindow): NOTEON = 0x9 NOTEOFF = 0x8 MIDICTRL = 11 GREEN = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(125,242,0);}") BLUE = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(0, 130, 240);}") RED = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(255, 21, 65);}") AMBER = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(255, 102, 0);}") PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(130, 0, 240);}") DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; " "background-color: rgb(217, 217, 217);}") RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}" "QPushButton:pressed {background-color: " "rgb(98, 98, 98);}") RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}" "QPushButton:pressed {background-color: " "rgb(98, 98, 98);}") STATE_COLORS = {Clip.STOP: RED, Clip.STARTING: GREEN, Clip.START: GREEN, Clip.STOPPING: RED, Clip.PREPARE_RECORD: AMBER, Clip.RECORDING: AMBER} STATE_BLINK = {Clip.STOP: False, Clip.STARTING: True, Clip.START: False, Clip.STOPPING: True, Clip.PREPARE_RECORD: True, Clip.RECORDING: False} BLINK_DURATION = 200 PROGRESS_PERIOD = 300 updateUi = pyqtSignal() readQueueIn = pyqtSignal() def __init__(self, song, jack_client): QObject.__init__(self) super(Gui, self).__init__() self._jack_client = jack_client self.setupUi(self) self.clip_volume.knobRadius = 3 self.is_learn_device_mode = False self.queue_out, self.queue_in = Queue(), Queue() self.updateUi.connect(self.update) self.readQueueIn.connect(self.readQueue) self.current_vol_block = 0 # Load devices self.deviceGroup = QActionGroup(self.menuDevice) self.devices = [] settings = QSettings('superboucle', 'devices') if settings.contains('devices') and settings.value('devices'): for raw_device in settings.value('devices'): self.devices.append(Device(pickle.loads(raw_device))) else: self.devices.append(Device({'name': 'No Device', })) self.updateDevices() self.deviceGroup.triggered.connect(self.onDeviceSelect) # Load song self.initUI(song) self.actionNew.triggered.connect(self.onActionNew) self.actionOpen.triggered.connect(self.onActionOpen) self.actionSave.triggered.connect(self.onActionSave) self.actionSave_As.triggered.connect(self.onActionSaveAs) self.actionAdd_Device.triggered.connect(self.onAddDevice) self.actionManage_Devices.triggered.connect(self.onManageDevice) self.actionFullScreen.triggered.connect(self.onActionFullScreen) self.master_volume.valueChanged.connect(self.onMasterVolumeChange) self.rewindButton.clicked.connect(self.onRewindClicked) self.playButton.clicked.connect(self._jack_client.transport_start) self.pauseButton.clicked.connect(self._jack_client.transport_stop) self.gotoButton.clicked.connect(self.onGotoClicked) self.recordButton.clicked.connect(self.onRecord) self.clip_name.textChanged.connect(self.onClipNameChange) self.clip_volume.valueChanged.connect(self.onClipVolumeChange) self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange) self.frame_offset.valueChanged.connect(self.onFrameOffsetChange) self.beat_offset.valueChanged.connect(self.onBeatOffsetChange) self.revertButton.clicked.connect(self.onRevertClip) self.normalizeButton.clicked.connect(self.onNormalizeClip) self.exportButton.clicked.connect(self.onExportClip) self.deleteButton.clicked.connect(self.onDeleteClipClicked) self.blktimer = QTimer() self.blktimer.state = False self.blktimer.timeout.connect(self.toogleBlinkButton) self.blktimer.start(self.BLINK_DURATION) self.disptimer = QTimer() self.disptimer.start(self.PROGRESS_PERIOD) self.disptimer.timeout.connect(self.updateProgress) self._jack_client.set_timebase_callback(self.timebase_callback) self.show() def initUI(self, song): # remove old buttons self.btn_matrix = [[None for y in range(song.height)] for x in range(song.width)] self.state_matrix = [[-1 for y in range(song.height)] for x in range(song.width)] for i in reversed(range(self.gridLayout.count())): self.gridLayout.itemAt(i).widget().close() self.gridLayout.itemAt(i).widget().setParent(None) self.song = song self.frame_clip.setEnabled(False) self.master_volume.setValue(song.volume*256) self.bpm.setValue(song.bpm) self.beat_per_bar.setValue(song.beat_per_bar) for x in range(song.width): for y in range(song.height): clip = song.clips_matrix[x][y] cell = Cell(self, clip, x, y) self.btn_matrix[x][y] = cell self.gridLayout.addWidget(cell, y, x) # send init command for init_cmd in self.device.init_command: self.queue_out.put(init_cmd) self.update() def closeEvent(self, event): settings = QSettings('superboucle', 'devices') settings.setValue('devices', [pickle.dumps(x.mapping) for x in self.devices]) def onStartStopClicked(self): clip = self.sender().parent().parent().clip self.startStop(clip.x, clip.y) def startStop(self, x, y): clip = self.btn_matrix[x][y].clip if clip is None: return if self.song.is_record: self.song.is_record = False self.updateRecordBtn() # calculate buffer size state, position = self._jack_client.transport_query() bps = position['beats_per_minute'] / 60 fps = position['frame_rate'] size = (1 / bps) * clip.beat_diviser * fps self.song.init_record_buffer(clip, 2, size, fps) # set frame offset based on jack block size clip.frame_offset = self._jack_client.blocksize clip.state = Clip.PREPARE_RECORD self.recordButton.setStyleSheet(self.RECORD_DEFAULT) else: self.song.toogle(clip.x, clip.y) self.update() def onEdit(self): self.last_clip = self.sender().parent().parent().clip if self.last_clip: self.frame_clip.setEnabled(True) self.clip_name.setText(self.last_clip.name) self.frame_offset.setValue(self.last_clip.frame_offset) self.beat_offset.setValue(self.last_clip.beat_offset) self.beat_diviser.setValue(self.last_clip.beat_diviser) self.clip_volume.setValue(self.last_clip.volume*256) state, position = self._jack_client.transport_query() fps = position['frame_rate'] bps = self.bpm.value() / 60 if self.bpm.value() and fps: size_in_beat = (bps/fps)*self.song.length(self.last_clip) else: size_in_beat = "No BPM info" clip_description = ("Size in sample : %s\nSize in beat : %s" % (self.song.length(self.last_clip), round(size_in_beat, 1))) self.clip_description.setText(clip_description) def onAddClipClicked(self): AddClipDialog(self, self.sender().parent().parent()) def onRevertClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file self.song.data[audio_file] = self.song.data[audio_file][::-1] def onNormalizeClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file absolute_val = np.absolute(self.song.data[audio_file]) current_level = np.ndarray.max(absolute_val) self.song.data[audio_file][:] *= (1 / current_level) def onExportClip(self): if self.last_clip and self.last_clip.audio_file: audio_file = self.last_clip.audio_file file_name, a = ( QFileDialog.getSaveFileName(self, 'Export Clip : %s' % self.last_clip.name, expanduser('~'), 'WAVE (*.wav)')) if file_name: file_name = verify_ext(file_name, 'wav') sf.write(self.song.data[audio_file], file_name, self.song.samplerate[audio_file], subtype=sf.default_subtype('WAV'), format='WAV') def onDeleteClipClicked(self): if self.last_clip: response = QMessageBox.question(self, "Delete Clip ?", ("Are you sure " "to delete the clip ?")) if response == QMessageBox.Yes: self.frame_clip.setEnabled(False) self.song.removeClip(self.last_clip) self.initUI(self.song) def onMasterVolumeChange(self): self.song.volume = (self.master_volume.value() / 256) def onStartClicked(self): pass self._jack_client.transport_start def onGotoClicked(self): state, position = self._jack_client.transport_query() new_position = (position['beats_per_bar'] * (self.gotoTarget.value() - 1) * position['frame_rate'] * (60 / position['beats_per_minute'])) self._jack_client.transport_locate(int(round(new_position, 0))) def onRecord(self): self.song.is_record = not self.song.is_record self.updateRecordBtn() def updateRecordBtn(self): if not self.song.is_record: self.recordButton.setStyleSheet(self.RECORD_DEFAULT) if self.device.record_btn: (msg_type, channel, pitch, velocity) = self.device.record_btn if self.song.is_record: color = self.device.blink_amber_vel else: color = self.device.black_vel self.queue_out.put(((msg_type << 4) + channel, pitch, color)) def onRewindClicked(self): self._jack_client.transport_locate(0) def onClipNameChange(self): self.last_clip.name = self.clip_name.text() tframe = self.btn_matrix[self.last_clip.x][self.last_clip.y] tframe.clip_name.setText(self.last_clip.name) def onClipVolumeChange(self): self.last_clip.volume = (self.clip_volume.value() / 256) def onBeatDiviserChange(self): self.last_clip.beat_diviser = self.beat_diviser.value() def onFrameOffsetChange(self): self.last_clip.frame_offset = self.frame_offset.value() def onBeatOffsetChange(self): self.last_clip.beat_offset = self.beat_offset.value() def onActionNew(self): NewSongDialog(self) def onActionOpen(self): file_name, a = ( QFileDialog.getOpenFileName(self, 'Open file', expanduser('~'), 'Super Boucle Song (*.sbs)')) if file_name: self.setEnabled(False) message = QMessageBox(self) message.setWindowTitle("Loading ....") message.setText("Reading Files, please wait ...") message.show() self.initUI(load_song_from_file(file_name)) message.close() self.setEnabled(True) def onActionSave(self): if self.song.file_name: self.song.save() else: self.onActionSaveAs() def onActionSaveAs(self): file_name, a = ( QFileDialog.getSaveFileName(self, 'Save As', expanduser('~'), 'Super Boucle Song (*.sbs)')) if file_name: file_name = verify_ext(file_name, 'sbs') self.song.file_name = file_name self.song.save() print("File saved to : {}".format(self.song.file_name)) def onAddDevice(self): self.learn_device = LearnDialog(self, self.addDevice) self.is_learn_device_mode = True def onManageDevice(self): ManageDialog(self) def onActionFullScreen(self): if self.isFullScreen(): self.showNormal() else: self.showFullScreen() self.show() def update(self): for x in range(len(self.song.clips_matrix)): line = self.song.clips_matrix[x] for y in range(len(line)): clp = line[y] if clp is None: state = None else: state = clp.state if state != self.state_matrix[x][y]: if clp: self.setCellColor(x, y, self.STATE_COLORS[state], self.STATE_BLINK[state]) try: self.queue_out.put(self.device.generateNote(x, y, state)) except IndexError: # print("No cell associated to %s x %s" # % (clp.x, clp.y)) pass self.state_matrix[x][y] = state def redraw(self): self.state_matrix = [[-1 for x in range(self.song.height)] for x in range(self.song.width)] self.update() def readQueue(self): try: while True: note = self.queue_in.get(block=False) if len(note) == 3: status, pitch, vel = struct.unpack('3B', note) channel = status & 0xF msg_type = status >> 4 self.processNote(msg_type, channel, pitch, vel) # else: # print("Invalid message length") except Empty: pass def processNote(self, msg_type, channel, pitch, vel): btn_id = (msg_type, channel, pitch, vel) btn_id_vel = (msg_type, channel, pitch, -1) ctrl_key = (msg_type, channel, pitch) # master volume if ctrl_key == self.device.master_volume_ctrl: self.song.master_volume = vel / 127 (self.master_volume .setValue(self.song.master_volume * 256)) elif self.device.play_btn in [btn_id, btn_id_vel]: self._jack_client.transport_start() elif self.device.pause_btn in [btn_id, btn_id_vel]: self._jack_client.transport_stop() elif self.device.rewind_btn in [btn_id, btn_id_vel]: self.onRewindClicked() elif self.device.goto_btn in [btn_id, btn_id_vel]: self.onGotoClicked() elif self.device.record_btn in [btn_id, btn_id_vel]: self.onRecord() elif ctrl_key in self.device.ctrls: try: ctrl_index = self.device.ctrls.index(ctrl_key) clip = (self.song.clips_matrix [ctrl_index] [self.current_vol_block]) if clip: clip.volume = vel / 127 if self.last_clip == clip: self.clip_volume.setValue(self.last_clip.volume * 256) except KeyError: pass elif (btn_id in self.device.block_buttons or btn_id_vel in self.device.block_buttons): try: self.current_vol_block = ( self.device.block_buttons.index(btn_id)) except ValueError: self.current_vol_block = ( self.device.block_buttons.index(btn_id_vel)) for i in range(len(self.device.block_buttons)): (a, b_channel, b_pitch, b) = self.device.block_buttons[i] if i == self.current_vol_block: color = self.device.red_vel else: color = self.device.black_vel self.queue_out.put(((self.NOTEON << 4) + b_channel, b_pitch, color)) else: x, y = -1, -1 try: x, y = self.device.getXY(btn_id) except IndexError: pass except KeyError: try: x, y = self.device.getXY(btn_id_vel) except KeyError: pass if (x >= 0 and y >= 0): self.startStop(x, y) def setCellColor(self, x, y, color, blink=False): self.btn_matrix[x][y].setStyleSheet(color) self.btn_matrix[x][y].blink = blink self.btn_matrix[x][y].color = color def toogleBlinkButton(self): for line in self.btn_matrix: for btn in line: if btn.blink: if self.blktimer.state: btn.setStyleSheet(btn.color) else: btn.setStyleSheet(self.DEFAULT) if self.song.is_record: if self.blktimer.state: self.recordButton.setStyleSheet(self.RECORD_BLINK) else: self.recordButton.setStyleSheet(self.RECORD_DEFAULT) self.blktimer.state = not self.blktimer.state def updateProgress(self): state, pos = self._jack_client.transport_query() if 'bar' in pos: bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick']) else: bbt = "-|-|-" seconds = int(pos['frame'] / pos['frame_rate']) (minutes, second) = divmod(seconds, 60) (hour, minute) = divmod(minutes, 60) time = "%d:%02d:%02d" % (hour, minute, second) self.bbtLabel.setText("%s\n%s" % (bbt, time)) for line in self.btn_matrix: for btn in line: if btn.clip and btn.clip.audio_file: value = ((btn.clip.last_offset / self.song.length(btn.clip)) * 97) btn.clip_position.setValue(value) btn.clip_position.repaint() def updateDevices(self): for action in self.deviceGroup.actions(): self.deviceGroup.removeAction(action) self.menuDevice.removeAction(action) for device in self.devices: action = QAction(device.name, self.menuDevice) action.setCheckable(True) action.setData(device) self.menuDevice.addAction(action) self.deviceGroup.addAction(action) action.setChecked(True) self.device = device def addDevice(self, device): self.devices.append(device) self.updateDevices() self.is_learn_device_mode = False def onDeviceSelect(self): self.device = self.deviceGroup.checkedAction().data() if self.device: if self.device.init_command: for note in self.device.init_command: self.queue_out.put(note) self.redraw() def timebase_callback(self, state, nframes, pos, new_pos): pos.valid = 0x10 pos.bar_start_tick = BAR_START_TICK pos.beats_per_bar = self.beat_per_bar.value() pos.beat_type = BEAT_TYPE pos.ticks_per_beat = TICKS_PER_BEAT pos.beats_per_minute = self.bpm.value() ticks = frame2bbt(pos.frame, pos.ticks_per_beat, pos.beats_per_minute, pos.frame_rate) (beats, pos.tick) = divmod(int(round(ticks, 0)), int(round(pos.ticks_per_beat, 0))) (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0))) (pos.bar, pos.beat) = (bar + 1, beat + 1) return None
class GUI(QtWidgets.QMainWindow): def __init__(self, control: "Controller") -> None: super(GUI, self).__init__() uic.loadUi( pkg_resources.resource_filename("labelCloud.resources.interfaces", "interface.ui"), self, ) self.resize(1500, 900) self.setWindowTitle("labelCloud") self.setStyleSheet( STYLESHEET.format(icons_dir=str( Path(__file__).resolve().parent.parent.joinpath( "resources").joinpath("icons")))) # MENU BAR # File self.action_setpcdfolder = self.findChild(QtWidgets.QAction, "action_setpcdfolder") self.action_setlabelfolder = self.findChild(QtWidgets.QAction, "action_setlabelfolder") # Labels self.action_deletelabels = self.findChild(QtWidgets.QAction, "action_deletealllabels") self.menu_setdefaultclass = self.findChild(QtWidgets.QMenu, "menu_setdefaultclass") self.actiongroup_defaultclass = QActionGroup(self.menu_setdefaultclass) # Settings self.action_zrotation = self.findChild(QtWidgets.QAction, "action_zrotationonly") self.action_showfloor = self.findChild(QtWidgets.QAction, "action_showfloor") self.action_showorientation = self.findChild(QtWidgets.QAction, "action_showorientation") self.action_saveperspective = self.findChild(QtWidgets.QAction, "action_saveperspective") self.action_alignpcd = self.findChild(QtWidgets.QAction, "action_alignpcd") self.action_change_settings = self.findChild(QtWidgets.QAction, "action_changesettings") # STATUS BAR self.status = self.findChild(QtWidgets.QStatusBar, "statusbar") self.mode_status = QtWidgets.QLabel("Navigation Mode") self.mode_status.setStyleSheet( "font-weight: bold; font-size: 14px; min-width: 235px;") self.mode_status.setAlignment(Qt.AlignCenter) self.status.addWidget(self.mode_status, stretch=0) self.tmp_status = QtWidgets.QLabel() self.tmp_status.setStyleSheet("font-size: 14px;") self.status.addWidget(self.tmp_status, stretch=1) # CENTRAL WIDGET self.glWidget: GLWidget = self.findChild(GLWidget, "openGLWidget") # LEFT PANEL # point cloud management self.label_curr_pcd = self.findChild(QtWidgets.QLabel, "label_pcd_current") self.button_prev_pcd = self.findChild(QtWidgets.QPushButton, "button_pcd_prev") self.button_next_pcd = self.findChild(QtWidgets.QPushButton, "button_pcd_next") self.progressbar_pcd = self.findChild(QtWidgets.QProgressBar, "progressbar_pcds") # bbox control section self.button_up = self.findChild(QtWidgets.QPushButton, "button_bbox_up") self.button_down = self.findChild(QtWidgets.QPushButton, "button_bbox_down") self.button_left = self.findChild(QtWidgets.QPushButton, "button_bbox_left") self.button_right = self.findChild(QtWidgets.QPushButton, "button_bbox_right") self.button_forward = self.findChild(QtWidgets.QPushButton, "button_bbox_forward") self.button_backward = self.findChild(QtWidgets.QPushButton, "button_bbox_backward") self.dial_zrotation = self.findChild(QtWidgets.QDial, "dial_bbox_zrotation") self.button_decr_dim = self.findChild(QtWidgets.QPushButton, "button_bbox_decr") self.button_incr_dim = self.findChild(QtWidgets.QPushButton, "button_bbox_incr") # 2d image viewer self.button_2D = self.findChild(QtWidgets.QPushButton, "button_open_2D") self.button_2D.setVisible( config.getboolean("USER_INTERFACE", "show_2d_image")) # label mode selection self.button_activate_picking = self.findChild(QtWidgets.QPushButton, "button_pick_bbox") self.button_activate_spanning = self.findChild(QtWidgets.QPushButton, "button_span_bbox") self.button_save_labels = self.findChild(QtWidgets.QPushButton, "button_save_label") # RIGHT PANEL self.label_list = self.findChild(QtWidgets.QListWidget, "label_list") self.curr_class_edit = self.findChild(QtWidgets.QLineEdit, "current_class_lineedit") # self.curr_bbox_stats = self.findChild(QtWidgets.QLabel, "current_bbox_stats") self.button_deselect_label = self.findChild(QtWidgets.QPushButton, "button_label_deselect") self.button_delete_label = self.findChild(QtWidgets.QPushButton, "button_label_delete") # BOUNDING BOX PARAMETER EDITS self.pos_x_edit = self.findChild(QtWidgets.QLineEdit, "pos_x_edit") self.pos_y_edit = self.findChild(QtWidgets.QLineEdit, "pos_y_edit") self.pos_z_edit = self.findChild(QtWidgets.QLineEdit, "pos_z_edit") self.length_edit = self.findChild(QtWidgets.QLineEdit, "length_edit") self.width_edit = self.findChild(QtWidgets.QLineEdit, "width_edit") self.height_edit = self.findChild(QtWidgets.QLineEdit, "height_edit") self.rot_x_edit = self.findChild(QtWidgets.QLineEdit, "rot_x_edit") self.rot_y_edit = self.findChild(QtWidgets.QLineEdit, "rot_y_edit") self.rot_z_edit = self.findChild(QtWidgets.QLineEdit, "rot_z_edit") self.all_line_edits = [ self.curr_class_edit, self.pos_x_edit, self.pos_y_edit, self.pos_z_edit, self.length_edit, self.width_edit, self.height_edit, self.rot_x_edit, self.rot_y_edit, self.rot_z_edit, ] self.volume_label = self.findChild(QtWidgets.QLabel, "volume_value_label") # Connect with controller self.controller = control self.controller.startup(self) # Connect all events to functions self.connect_events() self.set_checkbox_states() # tick in menu self.update_label_completer( ) # initialize label completer with classes in config self.update_default_object_class_menu() # Start event cycle self.timer = QtCore.QTimer(self) self.timer.setInterval(20) # period, in milliseconds self.timer.timeout.connect(self.controller.loop_gui) self.timer.start() # Event connectors def connect_events(self) -> None: # POINTCLOUD CONTROL self.button_next_pcd.clicked.connect( lambda: self.controller.next_pcd(save=True)) self.button_prev_pcd.clicked.connect(self.controller.prev_pcd) # BBOX CONTROL self.button_up.pressed.connect( lambda: self.controller.bbox_controller.translate_along_z()) self.button_down.pressed.connect( lambda: self.controller.bbox_controller.translate_along_z(down=True )) self.button_left.pressed.connect( lambda: self.controller.bbox_controller.translate_along_x(left=True )) self.button_right.pressed.connect( self.controller.bbox_controller.translate_along_x) self.button_forward.pressed.connect( lambda: self.controller.bbox_controller.translate_along_y(forward= True)) self.button_backward.pressed.connect( lambda: self.controller.bbox_controller.translate_along_y()) self.dial_zrotation.valueChanged.connect( lambda x: self.controller.bbox_controller.rotate_around_z( x, absolute=True)) self.button_decr_dim.clicked.connect( lambda: self.controller.bbox_controller.scale(decrease=True)) self.button_incr_dim.clicked.connect( lambda: self.controller.bbox_controller.scale()) # LABELING CONTROL self.curr_class_edit.textChanged.connect( self.controller.bbox_controller.set_classname) self.button_deselect_label.clicked.connect( self.controller.bbox_controller.deselect_bbox) self.button_delete_label.clicked.connect( self.controller.bbox_controller.delete_current_bbox) self.label_list.currentRowChanged.connect( self.controller.bbox_controller.set_active_bbox) # open_2D_img self.button_2D.pressed.connect(lambda: self.show_2d_image()) # LABEL CONTROL self.button_activate_picking.clicked.connect( lambda: self.controller.drawing_mode.set_drawing_strategy( PickingStrategy(self))) self.button_activate_spanning.clicked.connect( lambda: self.controller.drawing_mode.set_drawing_strategy( SpanningStrategy(self))) self.button_save_labels.clicked.connect(self.controller.save) # BOUNDING BOX PARAMETER self.pos_x_edit.editingFinished.connect( lambda: self.update_bbox_parameter("pos_x")) self.pos_y_edit.editingFinished.connect( lambda: self.update_bbox_parameter("pos_y")) self.pos_z_edit.editingFinished.connect( lambda: self.update_bbox_parameter("pos_z")) self.length_edit.editingFinished.connect( lambda: self.update_bbox_parameter("length")) self.width_edit.editingFinished.connect( lambda: self.update_bbox_parameter("width")) self.height_edit.editingFinished.connect( lambda: self.update_bbox_parameter("height")) self.rot_x_edit.editingFinished.connect( lambda: self.update_bbox_parameter("rot_x")) self.rot_y_edit.editingFinished.connect( lambda: self.update_bbox_parameter("rot_y")) self.rot_z_edit.editingFinished.connect( lambda: self.update_bbox_parameter("rot_z")) # MENU BAR self.action_setpcdfolder.triggered.connect( self.change_pointcloud_folder) self.action_setlabelfolder.triggered.connect(self.change_label_folder) self.actiongroup_defaultclass.triggered.connect( self.change_default_object_class) self.action_deletelabels.triggered.connect( self.controller.bbox_controller.reset) self.action_zrotation.toggled.connect(set_zrotation_only) self.action_showfloor.toggled.connect(set_floor_visibility) self.action_showorientation.toggled.connect(set_orientation_visibility) self.action_saveperspective.toggled.connect( lambda state: self.controller.pcd_manager.save_current_perspective( state)) self.action_alignpcd.toggled.connect( self.controller.align_mode.change_activation) self.action_change_settings.triggered.connect( self.show_settings_dialog) def set_checkbox_states(self) -> None: self.action_showfloor.setChecked( config.getboolean("USER_INTERFACE", "show_floor")) self.action_showorientation.setChecked( config.getboolean("USER_INTERFACE", "show_orientation")) self.action_zrotation.setChecked( config.getboolean("USER_INTERFACE", "z_rotation_only")) # Collect, filter and forward events to viewer def eventFilter(self, event_object, event) -> bool: # Keyboard Events # if (event.type() == QEvent.KeyPress) and (not self.line_edited_activated()): if (event.type() == QEvent.KeyPress) and (event_object == self): # TODO: Cleanup old filter self.controller.key_press_event(event) self.update_bbox_stats( self.controller.bbox_controller.get_active_bbox()) return True # TODO: Recheck pyqt behaviour elif event.type() == QEvent.KeyRelease: self.controller.key_release_event(event) # Mouse Events elif (event.type() == QEvent.MouseMove) and (event_object == self.glWidget): self.controller.mouse_move_event(event) self.update_bbox_stats( self.controller.bbox_controller.get_active_bbox()) elif (event.type() == QEvent.Wheel) and (event_object == self.glWidget): self.controller.mouse_scroll_event(event) self.update_bbox_stats( self.controller.bbox_controller.get_active_bbox()) elif event.type() == QEvent.MouseButtonDblClick and (event_object == self.glWidget): self.controller.mouse_double_clicked(event) return True elif (event.type() == QEvent.MouseButtonPress) and (event_object == self.glWidget): self.controller.mouse_clicked(event) self.update_bbox_stats( self.controller.bbox_controller.get_active_bbox()) elif (event.type() == QEvent.MouseButtonPress) and ( event_object != self.curr_class_edit): self.curr_class_edit.clearFocus() self.update_bbox_stats( self.controller.bbox_controller.get_active_bbox()) return False def closeEvent(self, a0: QtGui.QCloseEvent) -> None: logging.info("Closing window after saving ...") self.controller.save() self.timer.stop() a0.accept() def show_settings_dialog(self) -> None: dialog = SettingsDialog(self) dialog.exec() def show_2d_image(self): """Searches for a 2D image with the point cloud name and displays it in a new window.""" image_folder = config.getpath("FILE", "image_folder") # Look for image files with the name of the point cloud files_in_image_folder = sorted(image_folder.iterdir()) pcd_name = self.controller.pcd_manager.pcd_path.stem image_file_pattern = re.compile( f"{pcd_name}+(\.(?i:(jpe?g|png|gif|bmp|tiff)))") try: image_name = next( filter(image_file_pattern.search, files_in_image_folder)) except StopIteration: QMessageBox.information( self, "No 2d image File", (f"Could not find a related image in the image folder ({image_folder}).\n" "Check your path to the folder or if an image for this point cloud exists." ), QMessageBox.Ok, ) else: image_path = image_folder.joinpath(image_name) image = QtGui.QImage(QtGui.QImageReader(str(image_path)).read()) self.imageLabel = QLabel() self.imageLabel.setWindowTitle(f"2D Image ({image_name})") self.imageLabel.setPixmap(QPixmap.fromImage(image)) self.imageLabel.show() def show_no_pointcloud_dialog(self, pcd_folder: Path, pcd_extensions: List[str]) -> None: msg = QMessageBox(self) msg.setIcon(QMessageBox.Warning) msg.setText( "<b>labelCloud could not find any valid point cloud files inside the " "specified folder.</b>") msg.setInformativeText( f"Please copy all your point clouds into <em>{pcd_folder}</em> or change " "the point cloud folder location. labelCloud supports the following point " f"cloud file formats:\n {', '.join(pcd_extensions)}.") msg.setWindowTitle("No Point Clouds Found") msg.exec_() # VISUALIZATION METHODS def set_pcd_label(self, pcd_name: str) -> None: self.label_curr_pcd.setText("Current: <em>%s</em>" % pcd_name) def init_progress(self, min_value, max_value): self.progressbar_pcd.setMinimum(min_value) self.progressbar_pcd.setMaximum(max_value) def update_progress(self, value) -> None: self.progressbar_pcd.setValue(value) def update_curr_class_edit(self, force: str = None) -> None: if force is not None: self.curr_class_edit.setText(force) else: self.curr_class_edit.setText(self.controller.bbox_controller. get_active_bbox().get_classname()) def update_label_completer(self, classnames=None) -> None: if classnames is None: classnames = set() classnames.update(config.getlist("LABEL", "object_classes")) self.curr_class_edit.setCompleter(QCompleter(classnames)) def update_bbox_stats(self, bbox) -> None: viewing_precision = config.getint("USER_INTERFACE", "viewing_precision") if bbox and not self.line_edited_activated(): self.pos_x_edit.setText( str(round(bbox.get_center()[0], viewing_precision))) self.pos_y_edit.setText( str(round(bbox.get_center()[1], viewing_precision))) self.pos_z_edit.setText( str(round(bbox.get_center()[2], viewing_precision))) self.length_edit.setText( str(round(bbox.get_dimensions()[0], viewing_precision))) self.width_edit.setText( str(round(bbox.get_dimensions()[1], viewing_precision))) self.height_edit.setText( str(round(bbox.get_dimensions()[2], viewing_precision))) self.rot_x_edit.setText(str(round(bbox.get_x_rotation(), 1))) self.rot_y_edit.setText(str(round(bbox.get_y_rotation(), 1))) self.rot_z_edit.setText(str(round(bbox.get_z_rotation(), 1))) self.volume_label.setText( str(round(bbox.get_volume(), viewing_precision))) def update_bbox_parameter(self, parameter: str) -> None: str_value = None self.setFocus() # Changes the focus from QLineEdit to the window if parameter == "pos_x": str_value = self.pos_x_edit.text() if parameter == "pos_y": str_value = self.pos_y_edit.text() if parameter == "pos_z": str_value = self.pos_z_edit.text() if str_value and string_is_float(str_value): self.controller.bbox_controller.update_position( parameter, float(str_value)) return True if parameter == "length": str_value = self.length_edit.text() if parameter == "width": str_value = self.width_edit.text() if parameter == "height": str_value = self.height_edit.text() if str_value and string_is_float(str_value, recect_negative=True): self.controller.bbox_controller.update_dimension( parameter, float(str_value)) return True if parameter == "rot_x": str_value = self.rot_x_edit.text() if parameter == "rot_y": str_value = self.rot_y_edit.text() if parameter == "rot_z": str_value = self.rot_z_edit.text() if str_value and string_is_float(str_value): self.controller.bbox_controller.update_rotation( parameter, float(str_value)) return True # Enables, disables the draw mode def activate_draw_modes(self, state: bool) -> None: self.button_activate_picking.setEnabled(state) self.button_activate_spanning.setEnabled(state) def update_status(self, message: str, mode: str = None) -> None: self.tmp_status.setText(message) if mode: self.update_mode_status(mode) def line_edited_activated(self) -> bool: for line_edit in self.all_line_edits: if line_edit.hasFocus(): return True return False def update_mode_status(self, mode: str) -> None: self.action_alignpcd.setEnabled(True) if mode == "drawing": text = "Drawing Mode" self.action_alignpcd.setEnabled(False) elif mode == "correction": text = "Correction Mode" elif mode == "alignment": text = "Alignment Mode" else: text = "Navigation Mode" self.mode_status.setText(text) def change_pointcloud_folder(self) -> None: path_to_folder = Path( QFileDialog.getExistingDirectory( self, "Change Point Cloud Folder", directory=config.get("FILE", "pointcloud_folder"), )) if not path_to_folder.is_dir(): logging.warning("Please specify a valid folder path.") else: self.controller.pcd_manager.pcd_folder = path_to_folder self.controller.pcd_manager.read_pointcloud_folder() self.controller.pcd_manager.get_next_pcd() logging.info("Changed point cloud folder to %s!" % path_to_folder) def change_label_folder(self) -> None: path_to_folder = Path( QFileDialog.getExistingDirectory( self, "Change Label Folder", directory=config.get("FILE", "label_folder"), )) if not path_to_folder.is_dir(): logging.warning("Please specify a valid folder path.") else: self.controller.pcd_manager.label_manager.label_folder = path_to_folder self.controller.pcd_manager.label_manager.label_strategy.update_label_folder( path_to_folder) logging.info("Changed label folder to %s!" % path_to_folder) def update_default_object_class_menu(self, new_classes: Set[str] = None) -> None: object_classes = set(config.getlist("LABEL", "object_classes")) object_classes.update(new_classes or []) existing_classes = { action.text() for action in self.actiongroup_defaultclass.actions() } for object_class in object_classes.difference(existing_classes): action = self.actiongroup_defaultclass.addAction( object_class) # TODO: Add limiter for number of classes action.setCheckable(True) if object_class == config.get("LABEL", "std_object_class"): action.setChecked(True) self.menu_setdefaultclass.addActions( self.actiongroup_defaultclass.actions()) def change_default_object_class(self, action: QAction) -> None: config.set("LABEL", "std_object_class", action.text()) logging.info("Changed default object class to %s.", action.text())
def _setupMenu(self): """Initializes menu bar and menu actions. Menu bar contains two menus: File and View. File menu contains Open (opens file dialog for importing data file) and Exit (closes application) actions. View menu contains 3 groups: one for line style actions (points/lines), one for line type of the fitted data (step vs. smooth curve), and one for plot type (MVF, intensity, or trend test on tab 1). """ self.menu = self.menuBar() # initialize menu bar # ---- File menu fileMenu = self.menu.addMenu("File") # open openFile = QAction("Open", self) openFile.setShortcut("Ctrl+O") openFile.setStatusTip("Import data file") openFile.triggered.connect(self.fileOpened) # export table (tab 2) exportTable2 = QAction("Export Table (Tab 2)", self) # exportTable.setShortcut("Ctrl+E") exportTable2.setStatusTip("Export tab 2 table to csv") exportTable2.triggered.connect(self.exportTable2) # export table (tab 3) exportTable3 = QAction("Export Table (Tab 3)", self) # exportTable3.setShortcut("Ctrl+E") exportTable3.setStatusTip("Export tab 3 table to csv") exportTable3.triggered.connect(self.exportTable3) # exit exitApp = QAction("Exit", self) exitApp.setShortcut("Ctrl+Q") exitApp.setStatusTip("Close application") exitApp.triggered.connect(self.closeEvent) # add actions to file menu fileMenu.addAction(openFile) fileMenu.addSeparator() fileMenu.addAction(exportTable2) fileMenu.addAction(exportTable3) fileMenu.addSeparator() fileMenu.addAction(exitApp) # ---- View menu viewMenu = self.menu.addMenu("View") # -- plotting style # submenu viewStyle = QActionGroup(viewMenu) # points viewPoints = QAction("Show Points", self, checkable=True) viewPoints.setShortcut("Ctrl+P") viewPoints.setStatusTip("Data shown as points on graphs") viewPoints.triggered.connect(self.setPointsView) viewStyle.addAction(viewPoints) # lines viewLines = QAction("Show Lines", self, checkable=True) viewLines.setShortcut("Ctrl+L") viewLines.setStatusTip("Data shown as lines on graphs") viewLines.triggered.connect(self.setLineView) viewStyle.addAction(viewLines) # points and lines viewBoth = QAction("Show Points and Lines", self, checkable=True) viewBoth.setShortcut("Ctrl+B") viewBoth.setStatusTip("Data shown as points and lines on graphs") viewBoth.setChecked(True) viewBoth.triggered.connect(self.setLineAndPointsView) viewStyle.addAction(viewBoth) # add actions to view menu viewMenu.addActions(viewStyle.actions()) # -- line style (step vs smooth) lineStyle = QActionGroup(viewMenu) # smooth smooth = QAction("Smooth Plot (Fitted Models)", self, checkable=True) smooth.setShortcut("Ctrl+F") smooth.setStatusTip("Fitted model plot shows smooth curves") smooth.setChecked(True) smooth.triggered.connect(self.setSmoothPlot) lineStyle.addAction(smooth) # step step = QAction("Step Plot (Fitted Models)", self, checkable=True) step.setShortcut("Ctrl+D") step.setStatusTip("Fitted model plot shown as step") step.triggered.connect(self.setStepPlot) lineStyle.addAction(step) # add actions to view menu viewMenu.addSeparator() viewMenu.addActions(lineStyle.actions()) # -- graph display graphStyle = QActionGroup(viewMenu) # MVF self.mvf = QAction("MVF Graph", self, checkable=True) self.mvf.setShortcut("Ctrl+M") self.mvf.setStatusTip("Graphs display MVF of data") self.mvf.setChecked(True) self.mvf.triggered.connect(self.setMVFView) graphStyle.addAction(self.mvf) # intensity self.intensity = QAction("Intensity Graph", self, checkable=True) self.intensity.setShortcut("Ctrl+I") self.intensity.setStatusTip("Graphs display failure intensity") self.intensity.triggered.connect(self.setIntensityView) graphStyle.addAction(self.intensity) # add actions to view menu viewMenu.addSeparator() viewMenu.addActions(graphStyle.actions())
class ToolManager(QObject): selectEnabledToolSignal = pyqtSignal() selectedToolChanged = pyqtSignal(list) ## # Emitted when the status information of the current tool changed. # @see AbstractTool.setStatusInfo() ## statusInfoChanged = pyqtSignal(str) def __init__(self, parent = None): super().__init__(parent) self.mActionGroup = QActionGroup(self) self.mSelectedTool = None self.mPreviouslyDisabledTool = None self.mMapDocument = None self.mActionGroup.setExclusive(True) self.mActionGroup.triggered.connect(self.actionTriggered) self.selectEnabledToolSignal.connect(self.selectEnabledTool) def __del__(self): pass ## # Sets the MapDocument on which the registered tools will operate. ## def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return self.mMapDocument = mapDocument for action in self.mActionGroup.actions(): tool = action.data() tool.setMapDocument(mapDocument) ## # Registers a new tool. The tool manager does not take ownership over the # tool. # # @return The action for activating the tool. ## def registerTool(self, tool): tool.setMapDocument(self.mMapDocument) toolAction = QAction(tool.icon, tool.name, self) toolAction.setShortcut(tool.shortcut) toolAction.setData(QVariant(tool)) toolAction.setCheckable(True) toolAction.setToolTip("%s (%s)"%(tool.name, tool.shortcut.toString())) toolAction.setEnabled(tool.isEnabled()) self.mActionGroup.addAction(toolAction) tool.enabledChanged.connect(self.toolEnabledChanged) # Select the first added tool if (not self.mSelectedTool and tool.isEnabled()): self.setSelectedTool(tool) toolAction.setChecked(True) return toolAction ## # Selects the given tool. It should be previously added using # registerTool(). ## def selectTool(self, tool): if (tool and not tool.isEnabled()): # Refuse to select disabled tools return for action in self.mActionGroup.actions(): if (action.data() == tool): action.trigger() return # The given tool was not found. Don't select any tool. for action in self.mActionGroup.actions(): action.setChecked(False) self.setSelectedTool(None) ## # Returns the selected tool. ## def selectedTool(self): return self.mSelectedTool def retranslateTools(self): # Allow the tools to adapt to the new language for action in self.mActionGroup.actions(): tool = action.data() tool.languageChanged() # Update the text, shortcut and tooltip of the action action.setText(tool.name) action.setShortcut(tool.shortcut) action.setToolTip("%s (%s)"%(tool.name, tool.shortcut.toString())) def actionTriggered(self, action): self.setSelectedTool(action.data()) def toolEnabledChanged(self, enabled): tool = self.sender() for action in self.mActionGroup.actions(): if (action.data() == tool): action.setEnabled(enabled) break # Switch to another tool when the current tool gets disabled. This is done # with a delayed call since we first want all the tools to update their # enabled state. if ((not enabled and tool == self.mSelectedTool) or (enabled and not self.mSelectedTool)): self.selectEnabledToolSignal.emit() def selectEnabledTool(self): # Avoid changing tools when it's no longer necessary if (self.mSelectedTool and self.mSelectedTool.isEnabled()): return currentTool = self.mSelectedTool # Prefer the tool we switched away from last time if (self.mPreviouslyDisabledTool and self.mPreviouslyDisabledTool.isEnabled()): self.selectTool(self.mPreviouslyDisabledTool) else: self.selectTool(self.firstEnabledTool()) self.mPreviouslyDisabledTool = currentTool def firstEnabledTool(self): for action in self.mActionGroup.actions(): tool = action.data() if tool: if (tool.isEnabled()): return tool return None def setSelectedTool(self, tool): if (self.mSelectedTool == tool): return if (self.mSelectedTool): self.mSelectedTool.statusInfoChanged.disconnect(self.statusInfoChanged) self.mSelectedTool = tool self.selectedToolChanged.emit([self.mSelectedTool]) if (self.mSelectedTool): self.statusInfoChanged.emit(self.mSelectedTool.statusInfo) self.mSelectedTool.statusInfoChanged.connect(self.statusInfoChanged)
def __init__(self, supported_exts, parent=None): super().__init__(parent) self._diasshowRunning = False # a dummy widget to center actions spacer1 = QWidget() spacer1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.addWidget(spacer1) self.supportedExts = supported_exts self._fromFile = self.addAction(QIcon("icons/image-outline.svg"), "", self.chooseFile) # load images from file self._fromFile.setToolTip("Load image") self._fromFolder = self.addAction(QIcon("icons/folder-open.svg"), "", self.chooseFolder) # load images from folder self._fromFolder.setToolTip("Load from directory") # View in native size, fit width, fit height or fit image self._imageMode = QToolButton(self) self._imageMode.setIcon(QIcon("icons/eye-outline.svg")) self._imageMode.setToolTip("Image view mode") self._imageMode.setPopupMode(QToolButton.InstantPopup) self.addWidget(self._imageMode) # imageMode menu imageModeMenu = QMenu(self) imageModeActions = QActionGroup(imageModeMenu) imModeAct1 = imageModeActions.addAction("Native size") imModeAct1.setCheckable(True) imModeAct1.triggered.connect(lambda: self.imageModeChanged.emit(0)) imModeAct2 = imageModeActions.addAction("Fit in view") imModeAct2.setCheckable(True) imModeAct2.triggered.connect(lambda: self.imageModeChanged.emit(1)) imModeAct3 = imageModeActions.addAction("Fit width") imModeAct3.setCheckable(True) imModeAct3.triggered.connect(lambda: self.imageModeChanged.emit(2)) imModeAct4 = imageModeActions.addAction("Fit height") imModeAct4.setCheckable(True) imModeAct4.triggered.connect(lambda: self.imageModeChanged.emit(3)) imageModeActions.setExclusive(True) imageModeMenu.addActions(imageModeActions.actions()) self._imageMode.setMenu(imageModeMenu) self._imgDirection = self.addAction(QIcon("icons/arrow-move-outline.svg"), "", self.imageDirectionChanged.emit) # Horizontal or Vertical self._imgDirection.setToolTip("Toggle image direction") # start or stop diasshow self._playDias = self.addAction(QIcon("icons/media-play-outline.svg"), "", self.diasshowState) self._playDias.setToolTip("Start/stop diasshow") #diasshow menu self._diasMenu = QMenu(self) self._diasMenu.addAction("5 seconds", lambda: self.diasshowState(5)) self._diasMenu.addAction("10 seconds", lambda: self.diasshowState(10)) self._diasMenu.addAction("30 seconds", lambda: self.diasshowState(30)) self._diasMenu.addAction("5 minutes", lambda: self.diasshowState(60*5)) self._diasMenu.addAction("10 minutes", lambda: self.diasshowState(600)) self._playDias.setMenu(self._diasMenu) self._zoomIn = self.addAction(QIcon("icons/zoom-in-outline.svg"), "", lambda: self.zoomChanged.emit(True)) self._zoomOut = self.addAction(QIcon("icons/zoom-out-outline.svg"), "", lambda: self.zoomChanged.emit(False)) self._rotateCW = self.addAction(QIcon("icons/rotate-cw-outline.svg"), "", self.rotateChanged.emit) # Clockwise self._rotateCW.setToolTip("Rotate Clockwise") #self._rotateCCW = self.addAction("Rotate Left") # Counter clockwise # a dummy widget to center actions spacer2 = QWidget() spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.addWidget(spacer2)
class MainWindow(QMainWindow): sig_chatmessage_added = pyqtSignal(object) sig_avatar_loaded = pyqtSignal(str, object) def __init__(self, path_to_logs, trayicon): """ systems = list of system-objects creted by dotlan.py """ QMainWindow.__init__(self) uic.loadUi(resource_path('vi/ui/MainWindow.ui'), self) self.setWindowTitle("Vintel " + VERSION) self.setWindowIcon( QtGui.QIcon(resource_path("vi/ui/res/logo_small.png"))) self.path_to_logs = path_to_logs self.trayicon = trayicon self.trayicon.activated.connect(self.systray_activated) c = Cache() regionname = c.get_from_cache("regionname") if not regionname: regionname = "Providence" # is it a local map? svg = None try: with open( resource_path("vi/ui/res/mapdata/{0}.svg".format( regionname))) as svg_file: svg = svg_file.read() except Exception as e: pass try: self.dotlan = dotlan.Map(regionname, svg) except dotlan.DotlanException as e: QMessageBox.critical(None, "Error getting map", str(e), QMessageBox.Close) sys.exit(1) if self.dotlan.outdated_cache_error: e = self.dotlan.outdated_cache_error diatext = "I tried to get and process the data for the map "\ "but something went wrong. To proceed I use the data I "\ "have in my cache. This could be outdated.\nIf this problem "\ "is permanent, there might be a change in the dotlan data "\ "and VINTEL must be modified. Check for a newer version "\ "and inform the maintainer.\n\nWhat went wrong: {0} {1}"\ .format(type(e), str(e)) QMessageBox.warning(None, "Using map from my cache", diatext, QMessageBox.Ok) jumpbridge_url = c.get_from_cache("jumpbridge_url") self.set_jumpbridges(jumpbridge_url) self.init_map_position = None # we read this after first rendering # self.systems = self.dotlan.systems self.chatentries = [] self.kos_request_thread = KOSCheckerThread() self.kos_request_thread.kos_result.connect(self.show_kos_result) self.kos_request_thread.start() self.avatar_find_thread = AvatarFindThread() self.avatar_find_thread.avatar_update.connect( self.update_avatar_on_chatentry) self.avatar_find_thread.start() self.clipboard = QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) self.old_clipboard_content = (0, "") self.clipboard.changed.connect(self.clipboard_changed) self.zoomin.clicked.connect(self.zoomMapIn) self.zoomout.clicked.connect(self.zoomMapOut) self.actionStatistics.clicked.connect( self.dotlan.change_statistics_visibility) self.chat_large.clicked.connect(self.chat_larger) self.chat_small.clicked.connect(self.chat_smaller) self.jumpBridgesButton.clicked.connect(self.change_jumpbridge_view) self.sound_button.clicked.connect(self.show_sound_setup) self.actionInfo.triggered.connect(self.show_info) self.actionShow_Chat_Avatars.triggered.connect( self.change_show_avatars) self.actionAlways_on_top.triggered.connect(self.change_always_on_top) self.choose_chatrooms_button.triggered.connect( self.show_chatroom_chooser) self.choose_region_button.triggered.connect(self.show_region_chooser) self.action_show_chat.triggered.connect(self.change_chat_visibility) self.actionSound_Setup.triggered.connect(self.show_sound_setup) self.opacity_group = QActionGroup(self.menu) for i in (100, 80, 60, 40, 20): action = QAction("Opacity {0}%".format(i), None, checkable=True) if i == 100: action.setChecked(True) action.opacity = i / 100.0 action.triggered.connect(self.change_opacity) self.opacity_group.addAction(action) self.menuTransparency.addAction(action) # map with menu ======================================================= self.custom_content_page = MainWindowPage() self.custom_content_page.sig_link_clicked.connect( self.map_link_clicked) self.map.setPage(self.custom_content_page) self.map.page().set_svg(self.dotlan.svg_clean) self.map.contextmenu = TrayContextMenu(self.trayicon) def map_contextmenu_event(event): self.map.contextmenu.exec_( self.mapToGlobal(QPoint(event.x(), event.y()))) self.map.contextMenuEvent = map_contextmenu_event # self.map.connect(self.map, Qt.SIGNAL("linkClicked(const QUrl&)"), self.map_link_clicked) # self.map.page().linkClicked.connect(self.map_link_clicked) # http://stackoverflow.com/questions/40747827/qwebenginepage-disable-links # end map ============================================================= self.filewatcher_thread = filewatcher.FileWatcher( self.path_to_logs, 60 * 60 * 24) # self.connect(self.filewatcher_thread, QtCore.SIGNAL("fchange"), self.logfile_changed) self.filewatcher_thread.fchange.connect(self.logfile_changed) self.filewatcher_thread.start() if False: self.last_statistics_update = 0 self.maptimer = QtCore.QTimer(self) # self.connect(self.maptimer, QtCore.SIGNAL("timeout()"), self.update_map) self.maptimer.timeout.connect(self.update_map) self.maptimer.start(1000) self.evetimer = QtCore.QTimer(self) # self.connect(self.maptimer, QtCore.SIGNAL("timeout()"), self.update_map) self.evetimer.timeout.connect(self.update_evetime) self.evetimer.start(1000) self.trayicon.sig_alarm_distance.connect(self.change_alarm_distance) self.trayicon.sig_change_frameless.connect(self.change_frameless) self.frameButton.clicked.connect(self.change_frameless) self.frameButton.setVisible(False) self.btn_night_mode.clicked.connect(self.toggle_nightmode) self.btn_night_mode.setCheckable(True) # self.btn_night_mode.setChecked(True) self.actionFrameless_Window.triggered.connect(self.change_frameless) self.is_frameless = None # we need this because 2 places to change self.alarm_distance = 0 self.actionActivate_Sound.triggered.connect(self.change_sound) if not sound.SOUND_AVAILABLE: self.change_sound(disable=True) else: self.change_sound() self.jumpbridgedata_button.triggered.connect( self.show_jumbridge_chooser) # load something from cache ===================================== self.known_playernames = c.get_from_cache("known_playernames") if self.known_playernames: self.known_playernames = set(self.known_playernames.split(",")) else: self.known_playernames = set() roomnames = c.get_from_cache("roomnames") if roomnames: roomnames = roomnames.split(",") else: roomnames = ("TheCitadel", "North Provi Intel") c.put_into_cache("roomnames", ",".join(roomnames), 60 * 60 * 24 * 365 * 5) self.set_sound_volume( 75) # default - maybe overwritten by the settings try: settings = c.get_from_cache("settings") if settings: settings = pickle.loads(base64.b64decode(settings)) for setting in settings: try: if not setting[0]: obj = self else: obj = getattr(self, setting[0]) getattr(obj, setting[1])(setting[2]) except Exception as e: log.error(str(e)) except Exception as e: self.trayicon.showMessage( "Can't remember", "Something went wrong when I load my last state:\n{0}".format( str(e)), 1) # load cache ends =============================================== self.actionQuit.triggered.connect(self.close) self.trayicon.sig_quit.connect(self.close) self.chatparser = ChatParser(self.path_to_logs, roomnames, self.dotlan.systems) version_check_thread = drachenjaeger.NotifyNewVersionThread() version_check_thread.newer_version.connect(self.notify_newer_version) version_check_thread.run() def toggle_nightmode(self, pressed): if pressed: self.set_style_dark() else: self.set_style_light() def set_style_dark(self): self.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) self.map.page().set_style('dark') def set_style_light(self): self.setStyleSheet('') self.map.page().set_style('light') def update_evetime(self): self.evetime_label.setText( datetime.datetime.utcnow().strftime('Current EVE Time: %X')) def notify_newer_version(self, newest_version): self.trayicon.showMessage("Newer Version", ("A newer Version of VINTEL is available.\n" "Find the URL in the info!"), 1) def change_chat_visibility(self, new_value=None): if new_value is not None: self.action_show_chat.setChecked(new_value) self.chatbox.setVisible(self.action_show_chat.isChecked()) def change_opacity(self, lvl=None): if isinstance(lvl, float): opacityVal = lvl elif self.sender(): opacityVal = self.sender().opacity else: opacityVal = 1.0 if opacityVal: for action in self.opacity_group.actions(): if action.opacity == opacityVal: action.setChecked(True) action = self.opacity_group.checkedAction() self.setWindowOpacity(action.opacity) def change_sound(self, new_value=None, disable=False): if disable: self.actionActivate_Sound.setChecked(False) self.actionActivate_Sound.setEnabled(False) self.actionSound_Setup.setEnabled(False) self.sound_button.setEnabled(False) QMessageBox.warning( None, "Sound disabled", "I can't find the lib 'pygame' which I use to play sounds, so I have to disable the soundsystem.\n" "If you want sound, please install the 'pygame' library.", QMessageBox.Ok) else: if new_value is not None: self.actionActivate_Sound.setChecked(new_value) sound.sound_active = self.actionActivate_Sound.isChecked() def add_message_to_intelchat(self, message): scroll_to_bottom = False if (self.chatListWidget.verticalScrollBar().value() == self.chatListWidget.verticalScrollBar().maximum()): scroll_to_bottom = True entry = ChatEntry(message) listWidgetItem = QtWidgets.QListWidgetItem(self.chatListWidget) listWidgetItem.setSizeHint(entry.sizeHint()) self.chatListWidget.addItem(listWidgetItem) self.chatListWidget.setItemWidget(listWidgetItem, entry) if ChatEntry.SHOW_AVATAR: # log.debug('requesting "{0}" avatar'.format(entry.message.user)) self.avatar_find_thread.add_chatentry(entry) # else: # log.debug('requesting "{0}" avatar disabled in options'.format(entry.message.user)) self.chatentries.append(entry) entry.mark_system.connect(self.mark_system_on_map) self.sig_chatmessage_added.emit(entry) if scroll_to_bottom: self.chatListWidget.scrollToBottom() def change_always_on_top(self, new_value=None): self.hide() if new_value is not None: self.actionAlways_on_top.setChecked(new_value) always_on_top = self.actionAlways_on_top.isChecked() if always_on_top: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.WindowStaysOnTopHint)) self.show() def change_frameless(self, new_value=None): self.hide() if new_value is None: if self.is_frameless is None: self.is_frameless = False new_value = not self.is_frameless if new_value: self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.menubar.setVisible(False) self.frameButton.setVisible(True) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.FramelessWindowHint)) self.menubar.setVisible(True) self.frameButton.setVisible(False) self.change_always_on_top(new_value) self.is_frameless = new_value self.actionFrameless_Window.setChecked(new_value) for cm in TrayContextMenu.INSTANCES: cm.frameless_check.setChecked(new_value) self.show() def change_show_avatars(self, new_value=None): if new_value is not None: self.actionShow_Chat_Avatars.setChecked(new_value) show = self.actionShow_Chat_Avatars.isChecked() ChatEntry.SHOW_AVATAR = show for entry in self.chatentries: entry.avatar_label.setVisible(show) def chat_smaller(self): new_size = ChatEntry.TEXTSIZE - 1 ChatEntry.TEXTSIZE = new_size for entry in self.chatentries: entry.change_fontsize(new_size) def chat_larger(self): new_size = ChatEntry.TEXTSIZE + 1 ChatEntry.TEXTSIZE = new_size for entry in self.chatentries: entry.change_fontsize(new_size) def change_alarm_distance(self, distance): self.alarm_distance = distance for cm in TrayContextMenu.INSTANCES: for action in cm.distance_group.actions(): if action.alarm_distance_value == distance: action.setChecked(True) self.trayicon.alarm_distance_value = distance def change_jumpbridge_view(self): self.dotlan.change_jumpbrigde_visibility() self.update_map() def clipboard_changed(self, mode): if mode == 0 and self.action_kos_clipboard_active.isChecked(): content = str(self.clipboard.text()) last_modified, old_content = self.old_clipboard_content if content == old_content and time.time() - last_modified < 3: parts = content.split("\n") for part in parts: if part in self.known_playernames: self.trayicon.setIcon( QtGui.QIcon( resource_path( "vi/ui/res/logo_small_green.png"))) self.kos_request_thread.add_request( parts, "clipboard", True) self.old_clipboard_content = (0, "") break else: self.old_clipboard_content = (time.time(), content) def closeEvent(self, event): """ writing the cache before closing the window """ c = Cache() # known playernames if self.known_playernames: value = ",".join(self.known_playernames) c.put_into_cache("known_playernames", value, 60 * 60 * 24 * 365) # program state to cache (to read it on next startup) settings = ( (None, "restoreGeometry", self.saveGeometry()), (None, "restoreState", self.saveState()), (None, "change_opacity", self.opacity_group.checkedAction().opacity), (None, "change_always_on_top", self.actionAlways_on_top.isChecked()), ("splitter", "restoreGeometry", self.splitter.saveGeometry()), ("splitter", "restoreState", self.splitter.saveState()), (None, "change_show_avatars", self.actionShow_Chat_Avatars.isChecked()), (None, "change_alarm_distance", self.alarm_distance), ("action_kos_clipboard_active", "setChecked", self.action_kos_clipboard_active.isChecked()), (None, "change_sound", self.actionActivate_Sound.isChecked()), (None, "change_chat_visibility", self.action_show_chat.isChecked()), ("map", "setZoomFactor", self.map.zoomFactor()), (None, "set_init_map_scrollposition", (self.map.page().scrollPosition().x(), self.map.page().scrollPosition().y())), (None, "set_sound_volume", self.sound_volume), (None, "change_frameless", self.actionFrameless_Window.isChecked()), ) settings = pickle.dumps(settings, protocol=pickle.HIGHEST_PROTOCOL) c.put_into_cache("settings", base64.b64encode(settings), 60 * 60 * 24 * 365) event.accept() def map_link_clicked(self, url): systemname = str(url.path().split("/")[-1]).upper() system = self.dotlan.systems[str(systemname)] sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatentries, self.known_playernames) self.sig_chatmessage_added.connect(sc.add_chatentry) self.sig_avatar_loaded.connect(sc.new_avatar_available) sc.sig_location_set.connect(self.set_location) sc.show() def mark_system_on_map(self, systemname): self.map.page().mark_system(systemname) # self.dotlan.systems[str(systemname)].mark() # self.update_map() def set_location(self, char, new_system): self.map.page().mark_player(char, new_system) """ for system in self.dotlan.systems.values(): system.remove_located_character(char) if not new_system == "?" and new_system in self.dotlan.systems: self.dotlan.systems[new_system].add_located_character(char) self.set_map_content(self.dotlan.svg) """ def set_init_map_scrollposition(self, xy): self.init_map_position = QPoint(xy[0], xy[1]) def show_chatroom_chooser(self): chooser = ChatroomsChooser(self) chooser.rooms_changed.connect(self.changed_roomnames) chooser.show() def show_jumbridge_chooser(self): cache = Cache() url = cache.get_from_cache("jumpbridge_url") chooser = JumpBridgeChooser(self, url) chooser.set_jb_url.connect(self.set_jumpbridges) chooser.show() def set_sound_volume(self, value): if value < 0: value = 0 elif value > 100: value = 100 self.sound_volume = value sound.set_sound_volume(float(value) / 100.0) def set_jumpbridges(self, url): if url is None: url = "" try: data = [] if url != "": content = requests.get(url).text for line in content.split("\n"): parts = line.strip().split() if len(parts) == 3: data.append(parts) else: data = drachenjaeger.get_jumpbridge_data( self.dotlan.region.lower()) self.dotlan.set_jumpbridges(data) c = Cache() c.put_into_cache("jumpbridge_url", url, 60 * 60 * 24 * 365 * 8) except Exception as e: QMessageBox.warning(None, "Loading jumpbridges failed!", "Error: {0}".format(str(e)), QMessageBox.Ok) def show_region_chooser(self): chooser = RegionChooser(self) chooser.show() def show_kos_result(self, state, text, request_type, has_kos): if has_kos: sound.play_sound("beep") self.trayicon.setIcon( QtGui.QIcon(resource_path("vi/ui/res/logo_small.png"))) if state == "ok": if request_type == "xxx": # a xxx request out of the chat self.trayicon.showMessage("A xxx KOS-Check", text, 1) elif request_type == "clipboard": # request from clipboard-change if len(text) <= 0: text = "Noone KOS" self.trayicon.showMessage("Your KOS-Check", text, 1) text = text.replace("\n\n", "<br>") message = chatparser.chatparser.Message("Vintel KOS-Check", text, evegate.current_eve_time(), "VINTEL", [], states.NOT_CHANGE, text.upper(), text) self.add_message_to_intelchat(message) elif state == "error": self.trayicon.showMessage("KOS Failure", text, 3) def changed_roomnames(self, new_roomnames): cache = Cache() cache.put_into_cache("roomnames", u",".join(new_roomnames), 60 * 60 * 24 * 365 * 5) self.chatparser.rooms = new_roomnames def show_info(self): info_dialog = QDialog(self) uic.loadUi(resource_path("vi/ui/Info.ui"), info_dialog) info_dialog.version_label.setText(u"Version: {0}".format(VERSION)) info_dialog.logo_label.setPixmap( QtGui.QPixmap(resource_path("vi/ui/res/logo.png"))) info_dialog.close_button.clicked.connect(info_dialog.accept) info_dialog.show() def show_sound_setup(self): dialog = QDialog(self) uic.loadUi(resource_path("vi/ui/SoundSetup.ui"), dialog) dialog.volumeSlider.setValue(self.sound_volume) dialog.volumeSlider.valueChanged.connect(self.set_sound_volume) dialog.testsound_button.clicked.connect(sound.play_sound) dialog.close_button.clicked.connect(dialog.accept) dialog.show() def systray_activated(self, reason): if reason == QtWidgets.QSystemTrayIcon.Trigger: if self.isMinimized(): self.showNormal() self.activateWindow() elif not self.isActiveWindow(): self.activateWindow() else: self.showMinimized() def update_avatar_on_chatentry(self, chatentry, avatar_data): updated = chatentry.update_avatar(avatar_data) if not updated: self.avatar_find_thread.add_chatentry(chatentry, clear_cache=True) else: self.avatar_loaded.emit(chatentry.message.user, avatar_data) """ def update_map(self): # log.debug('check') def update_statistics_on_map(data): if data["result"] == "ok": self.dotlan.add_system_statistics(data["statistics"]) elif data["result"] == "error": text = data["text"] self.trayicon.showMessage("Loading statstics failed", text, 3) if self.last_statistics_update < (time.time() - 5 * 60): self.last_statistics_update = time.time() statistic_thread = MapStatisticsThread() # self.connect(statistic_thread, Qt.SIGNAL("statistic_data_update"), update_statistics_on_map) statistic_thread.statistic_data_update.connect(update_statistics_on_map) statistic_thread.start() self.set_map_content(self.dotlan.svg) def set_map_content(self, content): if self.init_map_position is None: scrollposition = self.map.page().scrollPosition() # scrollposition = self.map.scrollPosition() else: scrollposition = self.init_map_position self.init_map_position = None if True: # array = QByteArray().append(content) # self.map.page().setContent(array, mimeType=str('image/svg+xml')) if not self.set_map_content_done: self.map.page().set_svg(content) self.set_map_content_done = True else: # self.map.setContent(content) self.map.page().setHtml(content) if isinstance(scrollposition, QtCore.QPointF): script_str = "window.scrollTo({0}, {1});".format( scrollposition.toPoint().x(), scrollposition.toPoint().y() ) elif isinstance(scrollposition, QtCore.QPoint): script_str = "window.scrollTo({0}, {1});".format(scrollposition.x(), scrollposition.y()) else: script_str = None log.warning('unknown object type {0}'.format(type(scrollposition))) # script_str = "window.scrollTop(340);" # log.debug('current scroll is {0}x{1}'.format(scrollposition.toPoint().x(), scrollposition.toPoint().y())) # log.debug(script_str) # self.map.page().setScrollPosition(scrollposition) # self.map.page().scrollPosition = scrollposition; self.map.page().runJavaScript(script_str) # time.sleep(1) # self.map.page().setLinkDelegationPolicy(QWebEnginePage.DelegateAllLinks) """ def zoomMapIn(self): self.map.setZoomFactor(self.map.zoomFactor() + 0.1) def zoomMapOut(self): self.map.setZoomFactor(self.map.zoomFactor() - 0.1) def logfile_changed(self, path, roomname): messages = self.chatparser.file_modified(path, roomname) for message in messages: # if players location changed if message.status == states.LOCATION: self.known_playernames.add(message.user) self.set_location(message.user, message.systems[0]) # soundtest special elif message.status == states.SOUNDTEST and message.user in self.known_playernames: words = message.message.split() if len(words) > 1: sound.play_sound(words[1]) # KOS request elif message.status == states.KOS_STATUS_REQUEST: text = message.message[4:] text = text.replace(" ", ",") parts = (name.strip() for name in text.split(",")) self.trayicon.setIcon( QtGui.QIcon( resource_path("vi/ui/res/logo_small_green.png"))) self.kos_request_thread.add_request(parts, "xxx", False) # if it is a 'normal' chat message elif message.user not in ("EVE-System", "EVE System" ) and message.status != states.IGNORE: self.add_message_to_intelchat(message) if message.systems: for system in message.systems: systemname = system.name # self.dotlan.set_system_status(systemname, message.status) self.map.page().set_system_status(system, message) if message.status in (states.REQUEST, states.ALARM) \ and message.user not in self.known_playernames: if message.status == states.ALARM: alarm_distance = self.alarm_distance else: alarm_distance = 0 for nsystem, data in system.get_neighbours( alarm_distance).items(): distance = data["distance"] chars = nsystem.get_located_characters() if len(chars ) > 0 and message.user not in chars: self.trayicon.show_notification( message, system.name, ", ".join(chars), distance)
class MainWindow(base_class, ui_class): 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='BlinkSessionTransferNewOutgoing') notification_center.add_observer(self, name='BlinkFileTransferNewIncoming') notification_center.add_observer(self, name='BlinkFileTransferNewOutgoing') 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 range(0, 11)] self.mwi_icons.append(QIcon(Resources.get('icons/mwi-many.png'))) with Resources.directory: self.setupUi() self.setWindowTitle('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("Show", self._AH_SystemTrayShowWindow) menu.addAction(QIcon(Resources.get('icons/application-exit.png')), "Quit", 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.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('http://icanblink.com/help/'))) 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('http://icanblink.com/changelog/'))) 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) self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered) # 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.screen_sharing_button.addAction(QAction('Request screen', self.screen_sharing_button, triggered=self._AH_RequestScreenActionTriggered)) self.screen_sharing_button.addAction(QAction('Share my screen', self.screen_sharing_button, triggered=self._AH_ShareMyScreenActionTriggered)) # 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 = QStyleOptionFrame() 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.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): # todo: review this 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_map = {} action = action_map['system_default'] = self.output_device_menu.addAction('System default') action.setData('system_default') action.setCheckable(True) self.output_devices_group.addAction(action) self.output_device_menu.addSeparator() for device in SIPApplication.engine.output_devices: action = action_map[device] = self.output_device_menu.addAction(device) action.setData(device) action.setCheckable(True) self.output_devices_group.addAction(action) action = action_map[None] = self.output_device_menu.addAction('None') action.setData(None) action.setCheckable(True) self.output_devices_group.addAction(action) active_action = action_map.get(settings.audio.output_device, Null) active_action.setChecked(True) action_map = {} action = action_map['system_default'] = self.input_device_menu.addAction('System default') action.setData('system_default') action.setCheckable(True) self.input_devices_group.addAction(action) self.input_device_menu.addSeparator() for device in SIPApplication.engine.input_devices: action = action_map[device] = self.input_device_menu.addAction(device) action.setData(device) action.setCheckable(True) self.input_devices_group.addAction(action) action = action_map[None] = self.input_device_menu.addAction('None') action.setData(None) action.setCheckable(True) self.input_devices_group.addAction(action) active_action = action_map.get(settings.audio.input_device, Null) active_action.setChecked(True) action_map = {} action = action_map['system_default'] = self.alert_device_menu.addAction('System default') action.setData('system_default') action.setCheckable(True) self.alert_devices_group.addAction(action) self.alert_device_menu.addSeparator() for device in SIPApplication.engine.output_devices: action = action_map[device] = self.alert_device_menu.addAction(device) action.setData(device) action.setCheckable(True) self.alert_devices_group.addAction(action) action = action_map[None] = self.alert_device_menu.addAction('None') action.setData(None) action.setCheckable(True) self.alert_devices_group.addAction(action) active_action = action_map.get(settings.audio.alert_device, Null) active_action.setChecked(True) def load_video_devices(self): settings = SIPSimpleSettings() action_map = {} action = action_map['system_default'] = self.video_camera_menu.addAction('System default') action.setData('system_default') action.setCheckable(True) self.video_devices_group.addAction(action) self.video_camera_menu.addSeparator() for device in SIPApplication.engine.video_devices: action = action_map[device] = self.video_camera_menu.addAction(device) action.setData(device) action.setCheckable(True) self.video_devices_group.addAction(action) action = action_map[None] = self.video_camera_menu.addAction('None') action.setData(None) action.setCheckable(True) self.video_devices_group.addAction(action) active_action = action_map.get(settings.video.device, Null) active_action.setChecked(True) def _AH_AccountActionTriggered(self, enabled): account = self.sender().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() settings.google_contacts.enabled = not settings.google_contacts.enabled settings.save() 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_VoicemailActionTriggered(self, checked): account = self.sender().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): self.show() self.raise_() self.activateWindow() def _AH_QuitActionTriggered(self): 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 = '(invisible)' self.activity_note.setEnabled(False) else: if not self.activity_note.isEnabled(): self.activity_note.inactiveText = '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, 'Select Icon', self.last_icon_directory, "Images (*.png *.tiff *.jpg *.xmp *.svg)")[0] 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 '') 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('There is 1 active call' if len(active_sessions) == 1 else '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='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.enabled: self.google_contacts_action.setText('Disable &Google Contacts') else: self.google_contacts_action.setText('Enable &Google Contacts...') 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') notification.center.add_observer(self, name='VideoDevicesDidChange') 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): self.output_device_menu.clear() # because actions are owned by the menu and only referenced by their corresponding action groups, self.input_device_menu.clear() # clearing the menus will result in the actions automatically disappearing from the corresponding self.alert_device_menu.clear() # action groups as well if self.session_model.active_sessions: added_devices = set(notification.data.new_devices).difference(notification.data.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_VideoDevicesDidChange(self, notification): self.video_camera_menu.clear() # actions will be removed automatically from the action group because they are owned by the menu and only referenced in the action group if self.session_model.active_sessions: added_devices = set(notification.data.new_devices).difference(notification.data.old_devices) if added_devices: settings = SIPSimpleSettings() settings.video.device = added_devices.pop() settings.save() self.load_video_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 = next(action for action in self.output_devices_group.actions() if action.data() == settings.audio.output_device) action.setChecked(True) if 'audio.input_device' in notification.data.modified: action = next(action for action in self.input_devices_group.actions() if action.data() == settings.audio.input_device) action.setChecked(True) if 'audio.alert_device' in notification.data.modified: action = next(action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device) action.setChecked(True) if 'video.device' in notification.data.modified: action = next(action for action in self.video_devices_group.actions() if action.data() == settings.video.device) 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.enabled' in notification.data.modified: if notification.sender.google_contacts.enabled: self.google_contacts_action.setText('Disable &Google Contacts') else: self.google_contacts_action.setText('Enable &Google Contacts...') 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 = next(action for action in self.accounts_menu.actions() if action.data() is account) 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 '') if {'enabled', 'message_summary.enabled', 'message_summary.voicemail_uri'}.intersection(notification.data.modified): action = next(action for action in self.voicemail_menu.actions() if action.data() is account) 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 = self.accounts_menu.addAction(account.id if account is not BonjourAccount() else 'Bonjour') 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(self._AH_AccountActionTriggered) action = self.voicemail_menu.addAction(self.mwi_icons[0], account.id) 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(self._AH_VoicemailActionTriggered) def _NH_SIPAccountManagerDidRemoveAccount(self, notification): account = notification.data.account action = next(action for action in self.accounts_menu.actions() if action.data() is account) self.accounts_menu.removeAction(action) action = next(action for action in self.voicemail_menu.actions() if action.data() is account) 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 = next(action for action in self.voicemail_menu.actions() if action.data() is account) 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_BlinkSessionTransferNewOutgoing(self, notification): self.search_box.clear() self.switch_view_button.view = SwitchViewButton.SessionView 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 Ui_MainWindow(QMainWindow, Ui_MainWindowBase): updateFrame = pyqtSignal() def __init__(self): super(Ui_MainWindow, self).__init__() self.setupUi(self) self.videoPlaybackInit() self.imgInit() self.menuInit() self.radiusSpinBox.valueChanged.connect(self.radiusSpinBoxValueChanged) self.lineWidthSpinBox.valueChanged.connect(self.lineWidthSpinBoxValueChanged) self.overlayFrameNoSpinBox.valueChanged.connect(self.overlayFrameNoSpinBoxValueChanged) self.stackedWidget.currentChanged.connect(self.stackedWidgetCurrentChanged) self.arrowCheckBox.stateChanged.connect(self.arrowCheckBoxStateChanged) self.pathCheckBox.stateChanged.connect(self.pathCheckBoxStateChanged) self.reverseArrowColorCheckBox.stateChanged.connect(self.reverseArrowColorCheckBoxStateChanged) self.opaqueCheckBox.stateChanged.connect(self.opaqueCheckBoxStateChanged) self.videoPlaybackWidget.setSignalSlotMode() self.updateFrame.connect(self.videoPlaybackWidget.videoPlayback) self.filter = None self.filterIO = None self.isInitialized = False self.item_dict = {} self.data_dict = {} self.trackingPathGroup = None self.df = {} self.inputPixmapItem = None self.cv_img = None self.filePath = None self.savedFlag = True QShortcut(QKeySequence("Ctrl+R"), self, self.restartDataframe) QShortcut(QKeySequence("Ctrl+S"), self, self.saveCSVFile) def dragEnterEvent(self,event): event.acceptProposedAction() def dropEvent(self,event): mime = event.mimeData() if mime.hasUrls(): urls = mime.urls() if len(urls) > 0: self.processDropedFile(urls[0].toLocalFile()) event.acceptProposedAction() def closeEvent(self, event): if len(self.df.keys())==0 or self.savedFlag: return quit_msg = "Data is not saved.\nAre you sure you want to exit the program?" reply = QtWidgets.QMessageBox.question( self, 'Warning', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No ) if reply == QtWidgets.QMessageBox.Yes: event.accept() else: event.ignore() def processDropedFile(self,filePath): root,ext = os.path.splitext(filePath) if ext == ".filter": # Read Filter self.openFilterFile(filePath=filePath) return elif self.openVideoFile(filePath=filePath): return def arrowCheckBoxStateChanged(self, state): if 'arrow' not in self.item_dict.keys(): return for arrow_item in self.item_dict['arrow']: if state==Qt.Unchecked: arrow_item.hide() if state==Qt.Checked: arrow_item.show() self.updateInputGraphicsView() def pathCheckBoxStateChanged(self, state): if self.trackingPathGroup is None: return if state==Qt.Unchecked: self.trackingPathGroup.hide() if state==Qt.Checked: self.trackingPathGroup.show() self.updateInputGraphicsView() def reverseArrowColorCheckBoxStateChanged(self, state): if 'arrow' not in self.item_dict.keys(): return for arrow_item in self.item_dict['arrow']: if state==Qt.Unchecked: arrow_item.setColor([0,0,0]) if state==Qt.Checked: arrow_item.setColor([255,255,255]) self.updateInputGraphicsView() def opaqueCheckBoxStateChanged(self, state): if self.trackingPathGroup is None: return if state==Qt.Unchecked: self.trackingPathGroup.setOpacity(0.5) if state==Qt.Checked: self.trackingPathGroup.setOpacity(1.0) self.updateInputGraphicsView() def reset(self): self.videoPlaybackWidget.stop() self.videoPlaybackWidget.moveToFrame(0) def videoPlaybackInit(self): self.videoPlaybackWidget.hide() self.videoPlaybackWidget.frameChanged.connect(self.setFrame, type=Qt.QueuedConnection) def setFrame(self, frame, frameNo): if frame is not None: self.cv_img = frame self.currentFrameNo = frameNo self.updateInputGraphicsView() self.evaluate() return def imgInit(self): self.inputScene = QGraphicsScene() self.inputGraphicsView.setScene(self.inputScene) self.inputGraphicsView.resizeEvent = self.inputGraphicsViewResized def menuInit(self): self.actionOpenVideo.triggered.connect(self.openVideoFile) self.actionOpenFilterSetting.triggered.connect(self.openFilterFile) self.actionSaveCSVFile.triggered.connect(self.saveCSVFile) self.actionRunObjectTracking.triggered.connect(self.runObjectTracking) self.actionTrackingPathColor.triggered.connect(self.openTrackingPathColorSelectorDialog) self.menuAlgorithmsActionGroup = QActionGroup(self.menuAlgorithms) path_list = [[tracking_system_path, currentDirPath], ] if os.path.exists(user_defined_lib_path): path_list.append([user_defined_tracking_system_path, user_defined_lib_path]) for system_path in path_list: for module_path in get_modules(system_path[0], system_path[1]): module_str = '.'.join(module_path) try: module = importlib.import_module(module_str) if not hasattr(module, 'Widget'): continue class_def = getattr(module, "Widget") if not issubclass(class_def, QtWidgets.QWidget): continue widget = class_def(self.stackedWidget) widget.reset.connect(self.resetDataframe) widget.restart.connect(self.restartDataframe) self.stackedWidget.addWidget(widget) action = self.menuAlgorithms.addAction(widget.get_name()) action.triggered.connect(self.generateAlgorithmsMenuClicked(widget)) action.setCheckable(True) action.setActionGroup(self.menuAlgorithmsActionGroup) if len(self.menuAlgorithmsActionGroup.actions()) == 1: action.setChecked(True) self.algorithmSettingsGroupBox.setTitle(widget.get_name()) except Exception as e: if system_path[1] is user_defined_lib_path: msg = 'Tracking Lib. Load Fail: {0}\n{1}'.format(module_str, e) self.generateCriticalMessage(msg) continue def openTrackingPathColorSelectorDialog(self, activated=False): if self.trackingPathGroup is not None: self.trackingPathGroup.openColorSelectorDialog(self) def generateAlgorithmsMenuClicked(self, widget): def action_triggered(activated=False): if widget is not self.stackedWidget.currentWidget(): self.stackedWidget.setCurrentWidget(widget) else: pass return action_triggered def initializeEventDialog(self): quit_msg = "Data is not saved.\nAre you sure you want to reset?" reply = QtWidgets.QMessageBox.question( self, 'Warning', quit_msg, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No ) if reply == QtWidgets.QMessageBox.Yes: return True else: return False def openVideoFile(self, activated=False, filePath = None): if filePath is None: filePath, _ = QFileDialog.getOpenFileName(None, 'Open Video File', userDir) global filterOperation if len(filePath) is not 0: if filterOperation is not None and self.videoPlaybackWidget.isOpened(): if self.initializeEventDialog(): filterOperation = None self.removeTrackingGraphicsItems() self.savedFlag = True else: return self.filePath = filePath ret = self.videoPlaybackWidget.openVideo(filePath) if not ret: return False self.videoPlaybackWidget.show() self.cv_img = self.videoPlaybackWidget.getCurrentFrame() self.currentFrameNo = 0 self.videoPlaybackWidget.setMaxTickableFrameNo(0) self.initializeTrackingSystem() return True else: return False def openFilterFile(self, activated=False, filePath = None): if filePath is None: filePath, _ = QFileDialog.getOpenFileName(None, 'Open Block File', userDir, "Block files (*.filter)") if len(filePath) is not 0: if filterOperation is not None and self.videoPlaybackWidget.isOpened(): if self.initializeEventDialog(): self.videoPlaybackWidget.closeVideo() self.videoPlaybackWidget.hide() self.removeTrackingGraphicsItems() self.inputScene.removeItem(self.inputPixmapItem) self.savedFlag = True else: return logger.debug("Open Filter file: {0}".format(filePath)) self.filterIO = FilterIO(filePath) exec(self.filterIO.getFilterCode(), globals()) self.filter = None self.initializeTrackingSystem() self.evaluate() def saveCSVFile(self, activated=False, filePath = None): if len(self.df.keys())!=0: dirctory = os.path.dirname(self.filePath) base_name = os.path.splitext(os.path.basename(self.filePath))[0] path = os.path.join(dirctory, '{0}-{1}.txt'.format(base_name, "info")) filePath, _ = QFileDialog.getSaveFileName(None, 'Save Info File', path, "TXT files (*.txt)") if len(filePath) is not 0: logger.debug("Saving Info file: {0}".format(filePath)) with open(filePath, 'w') as fp: fp.write(self.videoPlaybackWidget.getVideoInfo()) for attr, df in self.df.items(): path = os.path.join(dirctory, '{0}-{1}.csv'.format(base_name, attr)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save CSV File', path, "CSV files (*.csv)") if len(filePath) is not 0: logger.debug("Saving CSV file: {0}".format(filePath)) df = df.copy().dropna() levels = df.columns.levels col = ['{0}{1}'.format(l,i) for i in levels[0] for l in levels[1]] df.columns = col df.to_csv(filePath) for k, v in self.data_dict.items(): path = os.path.join(dirctory, '{0}-{1}.json'.format(base_name, k)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save JSON File', path, "JSON files (*.json)") if len(filePath) is not 0: logger.debug("Saving JSON file: {0}".format(filePath)) with open(filePath, 'w') as f_p: json.dump(v, f_p) path = os.path.join(dirctory, '{0}-colors.color'.format(base_name)) filePath, _ = QFileDialog.getSaveFileName(None, 'Save Color File', path, "Color files (*.color)") if len(filePath) is not 0: logger.debug("Saving Color file: {0}".format(filePath)) self.trackingPathGroup.saveColors(filePath) self.savedFlag = True def radiusSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setRadius(i) self.updateInputGraphicsView() def lineWidthSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setLineWidth(i) self.updateInputGraphicsView() def overlayFrameNoSpinBoxValueChanged(self, i): if self.trackingPathGroup is not None: self.trackingPathGroup.setOverlayFrameNo(i) self.updateInputGraphicsView() def stackedWidgetCurrentChanged(self, i): currentWidget = self.stackedWidget.currentWidget() currentWidget.estimator_init() self.algorithmSettingsGroupBox.setTitle(currentWidget.get_name()) self.resetDataframe() def updateInputGraphicsView(self): if self.inputPixmapItem is not None: self.inputScene.removeItem(self.inputPixmapItem) if self.filter is not None and hasattr(self.filter, "resize_flag") and self.filter.resize_flag: qimg = misc.cvMatToQImage(cv2.pyrDown(self.cv_img)) else: qimg = misc.cvMatToQImage(self.cv_img) pixmap = QPixmap.fromImage(qimg) self.inputPixmapItem = QGraphicsPixmapItem(pixmap) rect = QtCore.QRectF(pixmap.rect()) self.inputScene.setSceneRect(rect) self.inputScene.addItem(self.inputPixmapItem) self.inputGraphicsView.viewport().update() self.inputGraphicsViewResized() def inputGraphicsViewResized(self, event=None): self.inputGraphicsView.fitInView(self.inputScene.sceneRect(), QtCore.Qt.KeepAspectRatio) def updatePath(self): try: attrs = self.stackedWidget.currentWidget().get_attributes() attrs.keys() except Exception as e: msg = 'Tracking Lib. Attributes Error:\n{}'.format(e) self.generateCriticalMessage(msg) return if 'position' in attrs: self.trackingPathGroup.setPoints(self.currentFrameNo) if 'arrow' in attrs: for i, arrow_item in enumerate(self.item_dict['arrow']): begin = self.df['position'].loc[self.currentFrameNo, i].values end = self.df['arrow'].loc[self.currentFrameNo, i].values arrow_item.setPosition(begin, end) if 'path' in attrs: for path_item, path_data in zip(self.item_dict['path'], self.data_dict['path'][self.currentFrameNo]): poly = QPolygonF() for p in path_data: poly.append(QPointF(*p)) painter_path = QPainterPath() painter_path.addPolygon(poly) path_item.setPath(painter_path) pen = QPen(Qt.blue) pen.setWidth(2) path_item.setPen(pen) if 'polygon' in attrs: for path_item, path_data in zip(self.item_dict['polygon'], self.data_dict['polygon'][self.currentFrameNo]): poly = QPolygonF() for p in path_data: poly.append(QPointF(*p)) painter_path = QPainterPath() painter_path.addPolygon(poly) path_item.setPath(painter_path) pen = QPen(Qt.black) pen.setWidth(1) path_item.setPen(pen) if 'rect' in attrs: for rect_item, rect in zip(self.item_dict['rect'], self.data_dict['rect'][self.currentFrameNo]): rect_item.setRect(QRectF(QPointF(*rect[0]), QPointF(*rect[1]))) def resetDataframe(self): self.initializeTrackingSystem() self.evaluate() def restartDataframe(self): if len(self.df.keys()) == 0: return for attr in self.df.keys(): self.df[attr].loc[self.currentFrameNo+1:] = np.nan for k in list(self.data_dict.keys()): for kk in list(self.data_dict[k].keys()): if kk == 'name': continue elif int(kk) > self.currentFrameNo: del self.data_dict[k][kk] df = {} for attr in self.df.keys(): df[attr] = self.df[attr].loc[self.currentFrameNo] kv = {k: [] for k in self.df.keys()} for key, value in kv.items(): mul_levs = df[key].index.levels for i in mul_levs[0]: value.append(df[key][i].values) kv[key] = np.array(value) for key, value in self.data_dict.items(): kv[key] = [np.array(v) for v in value[self.currentFrameNo]] self.videoPlaybackWidget.setMaxTickableFrameNo( self.currentFrameNo + self.videoPlaybackWidget.playbackDelta ) try: widget = self.stackedWidget.currentWidget() widget.reset_estimator(kv) except Exception as e: msg = 'Tracking Lib. Reset Fail:\n{}'.format(e) self.generateCriticalMessage(msg) def removeTrackingGraphicsItems(self): if self.trackingPathGroup is not None: self.inputScene.removeItem(self.trackingPathGroup) self.trackingPathGroup = None for k, v in self.item_dict.items(): [self.inputScene.removeItem(item) for item in v] v.clear() def initializeTrackingSystem(self): self.isInitialized = False self.removeTrackingGraphicsItems() if hasattr(self, 'currentFrameNo') and self.currentFrameNo != 0: ret, frame = self.videoPlaybackWidget.readFrame(0) self.cv_img = frame self.updateInputGraphicsView() self.currentFrameNo = 0 self.videoPlaybackWidget.setSliderValueWithoutSignal(0) self.videoPlaybackWidget.setMaxTickableFrameNo(0) try: tracking_n = self.stackedWidget.currentWidget().get_tracking_n() attrs = self.stackedWidget.currentWidget().get_attributes() is_filter_required = self.stackedWidget.currentWidget().is_filter_required() attrs.keys() except Exception as e: msg = 'Tracking Lib. Tracking N or attributes Error:\n{}'.format(e) self.generateCriticalMessage(msg) return if not (self.videoPlaybackWidget.isOpened() and (filterOperation is not None or not is_filter_required)): return False if is_filter_required: self.filter = filterOperation(self.cv_img) self.filter.fgbg = self.filterIO.getBackgroundImg() self.filter.isInit = True else: self.filter = None max_frame_pos = self.videoPlaybackWidget.getMaxFramePos() self.df = {} for k, t in attrs.items(): if t is None: self.data_dict[k] = {} self.data_dict[k]['name'] = k else: tuples = [] for i in range(tracking_n): for v in t: tuples.append((i, v)) col = pd.MultiIndex.from_tuples(tuples) self.df[k] = pd.DataFrame( index=range(0, max_frame_pos+1, self.playbackDeltaSpinBox.value()), columns=col, dtype=np.float64 ).sort_index().sort_index(axis=1) self.df[k].index.name = k if 'position' in attrs: self.trackingPathGroup = TrackingPathGroup() self.trackingPathGroup.setRect(self.inputScene.sceneRect()) if self.pathCheckBox.checkState()==Qt.Unchecked: self.trackingPathGroup.hide() self.inputScene.addItem(self.trackingPathGroup) self.trackingPathGroup.setDataFrame(self.df['position']) lw = self.trackingPathGroup.autoAdjustLineWidth(self.cv_img.shape) r = self.trackingPathGroup.autoAdjustRadius(self.cv_img.shape) self.trackingPathGroup.setOverlayFrameNo(self.overlayFrameNoSpinBox.value()) self.lineWidthSpinBox.setValue(lw) self.radiusSpinBox.setValue(r) self.trackingPathGroup.setItemsAreMovable(True) if 'rect' in attrs: self.item_dict['rect'] = [QGraphicsRectItem() for i in range(tracking_n)] for rect_item in self.item_dict['rect']: rect_item.setZValue(1000) self.inputScene.addItem(rect_item) if 'arrow' in attrs: self.item_dict['arrow'] = [MovableArrow() for i in range(tracking_n)] for arrow_item in self.item_dict['arrow']: arrow_item.setZValue(900) if self.arrowCheckBox.checkState()==Qt.Unchecked: arrow_item.hide() self.inputScene.addItem(arrow_item) if 'path' in attrs: self.item_dict['path'] = [QGraphicsPathItem() for i in range(tracking_n)] for path_item in self.item_dict['path']: path_item.setZValue(900) self.inputScene.addItem(path_item) if 'polygon' in attrs: self.item_dict['polygon'] = [QGraphicsPathItem() for i in range(tracking_n)] for path_item in self.item_dict['polygon']: path_item.setZValue(900) self.inputScene.addItem(path_item) # if self.currentFrameNo != 0: # self.videoPlaybackWidget.moveToFrame(0) self.videoPlaybackWidget.setPlaybackDelta(self.playbackDeltaSpinBox.value()) self.isInitialized = True def evaluate(self, update=True): if not self.isInitialized: return if self.currentFrameNo + 1 < self.videoPlaybackWidget.getMaxTickableFrameNo(): print('update') self.updatePath() self.updateInputGraphicsView() self.updateFrame.emit() return if self.filter is not None: img = self.filter.filterFunc(self.cv_img.copy()) else: img = None try: widget = self.stackedWidget.currentWidget() prev_pos = self.videoPlaybackWidget.getPrevFramePos() attrs = widget.get_attributes() if prev_pos >= 0: prev_data = { k: self.data_dict[k][prev_pos] for k in self.data_dict.keys() } for k in self.df.keys(): df = self.df[k] prev_data[k] = [ np.copy(df.loc[prev_pos, i].values) for i in df.columns.levels[0] ] else: prev_data = {k: None for k in attrs.keys()} prev_data['ignore_error'] = ( self.ignoreMisDetectionErrorCheckBox.checkState() == Qt.Checked ) res = widget.track( self.cv_img.copy(), img, prev_data ) except Exception as e: self.videoPlaybackWidget.stop() self.videoPlaybackWidget.moveToFrame( max(0, self.currentFrameNo - self.videoPlaybackWidget.playbackDelta) ) msg = 'Tracking Lib. Tracking method Fail:\n{}'.format(e) self.generateCriticalMessage(msg) return for k, v in res.items(): if k == 'path' or k == 'rect' or k == 'polygon': self.data_dict[k][self.currentFrameNo] = ndarray_to_list(v) continue if not attrs[k]: continue for i in range(len(v)): self.df[k].loc[self.currentFrameNo, i] = v[i] maxTickableFrameNo = \ self.currentFrameNo + self.videoPlaybackWidget.playbackDelta if maxTickableFrameNo > self.videoPlaybackWidget.getMaxFramePos(): maxTickableFrameNo = self.currentFrameNo self.videoPlaybackWidget.setMaxTickableFrameNo(maxTickableFrameNo) self.savedFlag = False if update: self.updatePath() self.updateInputGraphicsView() self.updateFrame.emit() def runObjectTracking(self): if self.filter is None or not self.videoPlaybackWidget.isOpened(): return minFrame = self.currentFrameNo maxFrame = self.videoPlaybackWidget.getMaxFramePos() numFrames = maxFrame-minFrame progress = QProgressDialog("Running...", "Abort", 0, numFrames, self) progress.setWindowModality(Qt.WindowModal) currentFrameNo = self.currentFrameNo for i, frameNo in enumerate(range(minFrame, maxFrame+1)): progress.setValue(i) if progress.wasCanceled(): break ret, frame = self.videoPlaybackWidget.readFrame(frameNo) self.cv_img = frame self.currentFrameNo = frameNo self.evaluate(False) self.videoPlaybackWidget.moveToFrame(currentFrameNo) progress.setValue(numFrames) def eventFilter(self, obj, event): if event.type() == QEvent.Wheel: self.videoPlaybackWidget.wheelEvent(event) return True if event.type() == QEvent.KeyPress: qwop = [Qt.Key_Q, Qt.Key_W, Qt.Key_O, Qt.Key_P] is_qwop = (True in map(lambda x: x == event.key(), qwop)) is_arrow = (Qt.Key_Home <= event.key() <= Qt.Key_PageDown) if is_arrow or is_qwop: self.videoPlaybackWidget.keyPressEvent(event) return True return False def generateCriticalMessage(self, msg): tb = sys.exc_info()[-1] f = tb.tb_frame msg = 'File name: {0}\nLine No: {1}\n'.format(f.f_code.co_filename, tb.tb_lineno) + msg reply = QtWidgets.QMessageBox.critical( self, 'Critical', msg, QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton ) return reply
def setupTextActions(self): tb = QToolBar(self) tb.setWindowTitle("Format Actions") self.addToolBar(tb) menu = QMenu("F&ormat", self) self.menuBar().addMenu(menu) self.actionTextBold = QAction(QIcon.fromTheme( 'format-text-bold', QIcon(rsrcPath + '/textbold.png')), "&Bold", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_B, triggered=self.textBold, checkable=True) bold = QFont() bold.setBold(True) self.actionTextBold.setFont(bold) tb.addAction(self.actionTextBold) menu.addAction(self.actionTextBold) self.actionTextItalic = QAction(QIcon.fromTheme( 'format-text-italic', QIcon(rsrcPath + '/textitalic.png')), "&Italic", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_I, triggered=self.textItalic, checkable=True) italic = QFont() italic.setItalic(True) self.actionTextItalic.setFont(italic) tb.addAction(self.actionTextItalic) menu.addAction(self.actionTextItalic) self.actionTextUnderline = QAction(QIcon.fromTheme( 'format-text-underline', QIcon(rsrcPath + '/textunder.png')), "&Underline", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_U, triggered=self.textUnderline, checkable=True) underline = QFont() underline.setUnderline(True) self.actionTextUnderline.setFont(underline) tb.addAction(self.actionTextUnderline) menu.addAction(self.actionTextUnderline) menu.addSeparator() grp = QActionGroup(self, triggered=self.textAlign) # Make sure the alignLeft is always left of the alignRight. if QApplication.isLeftToRight(): self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) else: self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignJustify = QAction( QIcon.fromTheme('format-justify-fill', QIcon(rsrcPath + '/textjustify.png')), "&Justify", grp) self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L) self.actionAlignLeft.setCheckable(True) self.actionAlignLeft.setPriority(QAction.LowPriority) self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E) self.actionAlignCenter.setCheckable(True) self.actionAlignCenter.setPriority(QAction.LowPriority) self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R) self.actionAlignRight.setCheckable(True) self.actionAlignRight.setPriority(QAction.LowPriority) self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J) self.actionAlignJustify.setCheckable(True) self.actionAlignJustify.setPriority(QAction.LowPriority) tb.addActions(grp.actions()) menu.addActions(grp.actions()) menu.addSeparator() pix = QPixmap(16, 16) pix.fill(Qt.black) self.actionTextColor = QAction(QIcon(pix), "&Color...", self, triggered=self.textColor) tb.addAction(self.actionTextColor) menu.addAction(self.actionTextColor) tb = QToolBar(self) tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) tb.setWindowTitle("Format Actions") self.addToolBarBreak(Qt.TopToolBarArea) self.addToolBar(tb) comboStyle = QComboBox(tb) tb.addWidget(comboStyle) comboStyle.addItem("Standard") comboStyle.addItem("Bullet List (Disc)") comboStyle.addItem("Bullet List (Circle)") comboStyle.addItem("Bullet List (Square)") comboStyle.addItem("Ordered List (Decimal)") comboStyle.addItem("Ordered List (Alpha lower)") comboStyle.addItem("Ordered List (Alpha upper)") comboStyle.addItem("Ordered List (Roman lower)") comboStyle.addItem("Ordered List (Roman upper)") comboStyle.activated.connect(self.textStyle) self.comboFont = QFontComboBox(tb) tb.addWidget(self.comboFont) self.comboFont.activated[str].connect(self.textFamily) self.comboSize = QComboBox(tb) self.comboSize.setObjectName("comboSize") tb.addWidget(self.comboSize) self.comboSize.setEditable(True) db = QFontDatabase() for size in db.standardSizes(): self.comboSize.addItem("%s" % (size)) self.comboSize.activated[str].connect(self.textSize) self.comboSize.setCurrentIndex( self.comboSize.findText("%s" % (QApplication.font().pointSize())))
def init_menu(self): layout_load_act = QAction(tr("MenuFile", "Load saved layout..."), self) layout_load_act.setShortcut("Ctrl+O") layout_load_act.triggered.connect(self.on_layout_load) layout_save_act = QAction(tr("MenuFile", "Save current layout..."), self) layout_save_act.setShortcut("Ctrl+S") layout_save_act.triggered.connect(self.on_layout_save) sideload_json_act = QAction(tr("MenuFile", "Sideload VIA JSON..."), self) sideload_json_act.triggered.connect(self.on_sideload_json) download_via_stack_act = QAction( tr("MenuFile", "Download VIA definitions"), self) download_via_stack_act.triggered.connect(self.load_via_stack_json) load_dummy_act = QAction(tr("MenuFile", "Load dummy JSON..."), self) load_dummy_act.triggered.connect(self.on_load_dummy) exit_act = QAction(tr("MenuFile", "Exit"), self) exit_act.setShortcut("Ctrl+Q") exit_act.triggered.connect(qApp.exit) file_menu = self.menuBar().addMenu(tr("Menu", "File")) file_menu.addAction(layout_load_act) file_menu.addAction(layout_save_act) file_menu.addSeparator() file_menu.addAction(sideload_json_act) file_menu.addAction(download_via_stack_act) file_menu.addAction(load_dummy_act) file_menu.addSeparator() file_menu.addAction(exit_act) keyboard_unlock_act = QAction(tr("MenuSecurity", "Unlock"), self) keyboard_unlock_act.triggered.connect(self.unlock_keyboard) keyboard_lock_act = QAction(tr("MenuSecurity", "Lock"), self) keyboard_lock_act.triggered.connect(self.lock_keyboard) keyboard_reset_act = QAction( tr("MenuSecurity", "Reboot to bootloader"), self) keyboard_reset_act.triggered.connect(self.reboot_to_bootloader) keyboard_layout_menu = self.menuBar().addMenu( tr("Menu", "Keyboard layout")) keymap_group = QActionGroup(self) selected_keymap = self.settings.value("keymap") for idx, keymap in enumerate(KEYMAPS): act = QAction(tr("KeyboardLayout", keymap[0]), self) act.triggered.connect( lambda checked, x=idx: self.change_keyboard_layout(x)) act.setCheckable(True) if selected_keymap == keymap[0]: self.change_keyboard_layout(idx) act.setChecked(True) keymap_group.addAction(act) keyboard_layout_menu.addAction(act) # check "QWERTY" if nothing else is selected if keymap_group.checkedAction() is None: keymap_group.actions()[0].setChecked(True) self.security_menu = self.menuBar().addMenu(tr("Menu", "Security")) self.security_menu.addAction(keyboard_unlock_act) self.security_menu.addAction(keyboard_lock_act) self.security_menu.addSeparator() self.security_menu.addAction(keyboard_reset_act) self.theme_menu = self.menuBar().addMenu(tr("Menu", "Theme")) theme_group = QActionGroup(self) selected_theme = self.get_theme() for name, _ in [("System", None)] + themes.themes: act = QAction(tr("MenuTheme", name), self) act.triggered.connect(lambda x, name=name: self.set_theme(name)) act.setCheckable(True) act.setChecked(selected_theme == name) theme_group.addAction(act) self.theme_menu.addAction(act) # check "System" if nothing else is selected if theme_group.checkedAction() is None: theme_group.actions()[0].setChecked(True)
def setupTextActions(self): tb = QToolBar(self) tb.setWindowTitle("Format Actions") self.addToolBar(tb) menu = QMenu("F&ormat", self) self.menuBar().addMenu(menu) self.actionTextBold = QAction( QIcon.fromTheme('format-text-bold', QIcon(rsrcPath + '/textbold.png')), "&Bold", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_B, triggered=self.textBold, checkable=True) bold = QFont() bold.setBold(True) self.actionTextBold.setFont(bold) tb.addAction(self.actionTextBold) menu.addAction(self.actionTextBold) self.actionTextItalic = QAction( QIcon.fromTheme('format-text-italic', QIcon(rsrcPath + '/textitalic.png')), "&Italic", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_I, triggered=self.textItalic, checkable=True) italic = QFont() italic.setItalic(True) self.actionTextItalic.setFont(italic) tb.addAction(self.actionTextItalic) menu.addAction(self.actionTextItalic) self.actionTextUnderline = QAction( QIcon.fromTheme('format-text-underline', QIcon(rsrcPath + '/textunder.png')), "&Underline", self, priority=QAction.LowPriority, shortcut=Qt.CTRL + Qt.Key_U, triggered=self.textUnderline, checkable=True) underline = QFont() underline.setUnderline(True) self.actionTextUnderline.setFont(underline) tb.addAction(self.actionTextUnderline) menu.addAction(self.actionTextUnderline) menu.addSeparator() grp = QActionGroup(self, triggered=self.textAlign) # Make sure the alignLeft is always left of the alignRight. if QApplication.isLeftToRight(): self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) else: self.actionAlignRight = QAction( QIcon.fromTheme('format-justify-right', QIcon(rsrcPath + '/textright.png')), "&Right", grp) self.actionAlignCenter = QAction( QIcon.fromTheme('format-justify-center', QIcon(rsrcPath + '/textcenter.png')), "C&enter", grp) self.actionAlignLeft = QAction( QIcon.fromTheme('format-justify-left', QIcon(rsrcPath + '/textleft.png')), "&Left", grp) self.actionAlignJustify = QAction( QIcon.fromTheme('format-justify-fill', QIcon(rsrcPath + '/textjustify.png')), "&Justify", grp) self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L) self.actionAlignLeft.setCheckable(True) self.actionAlignLeft.setPriority(QAction.LowPriority) self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E) self.actionAlignCenter.setCheckable(True) self.actionAlignCenter.setPriority(QAction.LowPriority) self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R) self.actionAlignRight.setCheckable(True) self.actionAlignRight.setPriority(QAction.LowPriority) self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J) self.actionAlignJustify.setCheckable(True) self.actionAlignJustify.setPriority(QAction.LowPriority) tb.addActions(grp.actions()) menu.addActions(grp.actions()) menu.addSeparator() pix = QPixmap(16, 16) pix.fill(Qt.black) self.actionTextColor = QAction(QIcon(pix), "&Color...", self, triggered=self.textColor) tb.addAction(self.actionTextColor) menu.addAction(self.actionTextColor) tb = QToolBar(self) tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea) tb.setWindowTitle("Format Actions") self.addToolBarBreak(Qt.TopToolBarArea) self.addToolBar(tb) comboStyle = QComboBox(tb) tb.addWidget(comboStyle) comboStyle.addItem("Standard") comboStyle.addItem("Bullet List (Disc)") comboStyle.addItem("Bullet List (Circle)") comboStyle.addItem("Bullet List (Square)") comboStyle.addItem("Ordered List (Decimal)") comboStyle.addItem("Ordered List (Alpha lower)") comboStyle.addItem("Ordered List (Alpha upper)") comboStyle.addItem("Ordered List (Roman lower)") comboStyle.addItem("Ordered List (Roman upper)") comboStyle.activated.connect(self.textStyle) self.comboFont = QFontComboBox(tb) tb.addWidget(self.comboFont) self.comboFont.activated[str].connect(self.textFamily) self.comboSize = QComboBox(tb) self.comboSize.setObjectName("comboSize") tb.addWidget(self.comboSize) self.comboSize.setEditable(True) db = QFontDatabase() for size in db.standardSizes(): self.comboSize.addItem("%s" % (size)) self.comboSize.activated[str].connect(self.textSize) self.comboSize.setCurrentIndex( self.comboSize.findText( "%s" % (QApplication.font().pointSize())))