class FileWatcher(QObject): pathChanged = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self._watcher = QFileSystemWatcher(self) self._watcher.directoryChanged.connect(self.onChanged) self._watcher.fileChanged.connect(self.onChanged) self._events = {} @property def watched(self): return self._watcher.directories() + self._watcher.files() def clear(self): self._watcher.removePaths(self.watched) def watch(self, path): self._watcher.addPath(path) def watchWalk(self, path: Path): try: for root, dirs, files in os.walk(str(path)): for dir in dirs: r = Path(root).joinpath(dir) self._watcher.addPath(str(r)) except Exception: pass def onChanged(self, path): self.pathChanged.emit(path)
class FileWatcher(QObject): pathChanged = pyqtSignal(str) def __init__(self): super().__init__() self._watcher = QFileSystemWatcher(self) self._watcher.directoryChanged.connect(self.onChanged) self._watcher.fileChanged.connect(self.onChanged) self._events = {} @property def watched(self): return self._watcher.directories() + self._watcher.files() def watch(self, path): self._watcher.addPath(path) def onChanged(self, path): self.pathChanged.emit(path)
def main(argv): signal.signal(signal.SIGINT, signal.SIG_DFL) app = QCoreApplication([]) watcher = QFileSystemWatcher() print("Watching /tmp/") watcher.addPath("/tmp/") watcher.addPath("/tmp/foo") # Files have to be watched specifically for this to trigger. # Deleting and recreating a file makes this no longer trigger. watcher.fileChanged.connect(file_changed) # This triggers on file creation and deletion watcher.directoryChanged.connect(directory_changed) print("files:", watcher.files()) print("directories:", watcher.directories()) sys.exit(app.exec())
def main(argv): signal.signal(signal.SIGINT, signal.SIG_DFL) app = QCoreApplication([]) watcher = QFileSystemWatcher() print("Watching /tmp/") watcher.addPath("/tmp/") watcher.addPath("/tmp/foo") # Files have to be watched specifically for this to trigger. # Deleting and recreating a file makes this no longer trigger. watcher.fileChanged.connect(file_changed) # This triggers on file creation and deletion watcher.directoryChanged.connect(directory_changed) print("files:", watcher.files()) print("directories:", watcher.directories()) sys.exit(app.exec())
class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.ui.toolBox.setCurrentIndex(0) self.fileWatcher = QFileSystemWatcher() self.fileWatcher.directoryChanged.connect(self.do_directoryChanged) self.fileWatcher.fileChanged.connect(self.do_fileChanged) ## ==============自定义功能函数======================== def __showBtnInfo(self, btn): ##显示按钮的text()和toolTip() self.ui.textEdit.appendPlainText("====" + btn.text()) self.ui.textEdit.appendPlainText(btn.toolTip() + "\n") ## ==============event处理函数========================== ## ==========由connectSlotsByName()自动连接的槽函数============ @pyqtSlot() ##"选择文件"按钮 def on_btnOpenFile_clicked(self): curDir = QDir.currentPath() #获取当前路径 aFile, filt = QFileDialog.getOpenFileName(self, "打开文件", curDir, "所有文件(*.*)") self.ui.editFile.setText(aFile) @pyqtSlot() ##"选择目录"按钮 def on_btnOpenDir_clicked(self): curDir = QDir.currentPath() aDir = QFileDialog.getExistingDirectory(self, "选择一个目录", curDir, QFileDialog.ShowDirsOnly) self.ui.editDir.setText(aDir) @pyqtSlot() ##"清空"按钮 def on_btnClear_clicked(self): self.ui.textEdit.clear() ## =========QFile类 的静态函数=========== @pyqtSlot() ##类函数copy() def on_btnFile_copy_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text().strip() #源文件 if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return fileInfo = QFileInfo(sous) newFile = fileInfo.path() + "/" + fileInfo.baseName( ) + "--副本." + fileInfo.suffix() if QFile.copy(sous, newFile): self.ui.textEdit.appendPlainText("源文件:" + sous) self.ui.textEdit.appendPlainText("复制为文件:" + newFile + "\n") else: self.ui.textEdit.appendPlainText("复制文件失败") @pyqtSlot() ##类函数exists() def on_btnFile_exists_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text().strip() #源文件 if QFile.exists(sous): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##类函数remove() def on_btnFile_remove_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text().strip() #源文件 if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return ret = QMessageBox.question(self, "确认删除", "确定要删除这个文件吗\n\n" + sous) if (ret != QMessageBox.Yes): return if QFile.remove(sous): self.ui.textEdit.appendPlainText("成功删除文件:" + sous + "\n") else: self.ui.textEdit.appendPlainText("删除文件失败:" + sous + "\n") @pyqtSlot() ##类函数rename() def on_btnFile_rename_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text().strip() #源文件 if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return fileInfo = QFileInfo(sous) newFile = fileInfo.path() + "/" + fileInfo.baseName( ) + ".XZY" #更改文件后缀为".XYZ" if QFile.rename(sous, newFile): self.ui.textEdit.appendPlainText("源文件:" + sous) self.ui.textEdit.appendPlainText("重命名为:" + newFile + "\n") else: self.ui.textEdit.appendPlainText("重命名文件失败\n") ## =========QFileInfo类=========== @pyqtSlot() ##absoluteFilePath() def on_btnInfo_absFilePath_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.absoluteFilePath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##absolutePath() def on_btnInfo_absPath_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.absolutePath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##fileName() def on_btnInfo_fileName_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.fileName() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##filePath() def on_btnInfo_filePath_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.filePath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##size() def on_btnInfo_size_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) btCount = fileInfo.size() #字节数 text = "%d Bytes" % btCount self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##path() def on_btnInfo_path_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.path() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##baseName() def on_btnInfo_baseName_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.baseName() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##completeBaseName() def on_btnInfo_baseName2_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.completeBaseName() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##suffix() def on_btnInfo_suffix_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.suffix() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##completeSuffix() def on_btnInfo_suffix2_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) text = fileInfo.completeSuffix() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##isDir() def on_btnInfo_isDir_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editDir.text()) if fileInfo.isDir(): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##isFile() def on_btnInfo_isFile_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) if fileInfo.isFile(): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##isExecutable() def on_btnInfo_isExec_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) if fileInfo.isExecutable(): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##birthTime() ,替代了过时的created()函数 def on_btnInfo_birthTime_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) dt = fileInfo.birthTime() # QDateTime text = dt.toString("yyyy-MM-dd hh:mm:ss") self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##lastModified() def on_btnInfo_lastModified_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) dt = fileInfo.lastModified() # QDateTime text = dt.toString("yyyy-MM-dd hh:mm:ss") self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##lastRead() def on_btnInfo_lastRead_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) dt = fileInfo.lastRead() # QDateTime text = dt.toString("yyyy-MM-dd hh:mm:ss") self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##类函数exists() def on_btnInfo_exists_clicked(self): self.__showBtnInfo(self.sender()) if QFileInfo.exists(self.ui.editFile.text()): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##接口函数exists() def on_btnInfo_exists2_clicked(self): self.__showBtnInfo(self.sender()) fileInfo = QFileInfo(self.ui.editFile.text()) if fileInfo.exists(): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") ## ==================QDir类======================== @pyqtSlot() ##tempPath() def on_btnDir_tempPath_clicked(self): self.__showBtnInfo(self.sender()) text = QDir.tempPath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##rootPath() def on_btnDir_rootPath_clicked(self): self.__showBtnInfo(self.sender()) text = QDir.rootPath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##homePath() def on_btnDir_homePath_clicked(self): self.__showBtnInfo(self.sender()) text = QDir.homePath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##drives() def on_btnDir_drives_clicked(self): self.__showBtnInfo(self.sender()) strList = QDir.drives() #QFileInfoList for line in strList: #line 是QFileInfo类型 self.ui.textEdit.appendPlainText(line.path()) self.ui.textEdit.appendPlainText("") @pyqtSlot() ##currentPath() def on_btnDir_curPath_clicked(self): self.__showBtnInfo(self.sender()) text = QDir.currentPath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##setCurrent() def on_btnDir_setCurPath_clicked(self): self.__showBtnInfo(self.sender()) curDir = QDir.currentPath() text = QFileDialog.getExistingDirectory(self, "选择一个目录", curDir, QFileDialog.ShowDirsOnly) QDir.setCurrent(text) self.ui.textEdit.appendPlainText("选择了一个目录作为当前目录:\n" + text + "\n") @pyqtSlot() ##mkdir() def on_btnDir_mkdir_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text().strip() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return subDir = "subdir1" dirObj = QDir(sous) if dirObj.mkdir(subDir): self.ui.textEdit.appendPlainText("新建一个子目录: " + subDir + "\n") else: self.ui.textEdit.appendPlainText("创建目录失败\n") @pyqtSlot() ##rmdir() def on_btnDir_rmdir_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text().strip() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(sous) if dirObj.rmdir(sous): self.ui.textEdit.appendPlainText("成功删除所选目录\n" + sous + "\n") else: self.ui.textEdit.appendPlainText("删除目录失败,目录下必须为空\n") @pyqtSlot() ##remove() def on_btnDir_remove_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text().strip() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return parDir = self.ui.editDir.text().strip() if parDir == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(parDir) if dirObj.remove(sous): self.ui.textEdit.appendPlainText("成功删除文件:\n" + sous + "\n") else: self.ui.textEdit.appendPlainText("删除文件失败\n") @pyqtSlot() ##rename() def on_btnDir_rename_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return parDir = self.ui.editDir.text().strip() if parDir == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(parDir) fileInfo = QFileInfo(sous) newFile = fileInfo.path() + "/" + fileInfo.baseName() + ".XYZ" if dirObj.rename(sous, newFile): self.ui.textEdit.appendPlainText("源文件:" + sous) self.ui.textEdit.appendPlainText("重命名为:" + newFile + "\n") else: self.ui.textEdit.appendPlainText("重命名文件失败\n") @pyqtSlot() ##setPath(),改换QDir所指的目录 def on_btnDir_setPath_clicked(self): self.__showBtnInfo(self.sender()) curDir = QDir.currentPath() lastDir = QDir(curDir) self.ui.textEdit.appendPlainText("选择目录之前,QDir所指目录是:" + lastDir.absolutePath()) aDir = QFileDialog.getExistingDirectory(self, "选择一个目录", curDir, QFileDialog.ShowDirsOnly) if aDir == "": return lastDir.setPath(aDir) self.ui.textEdit.appendPlainText("\n选择目录之后,QDir所指目录是:" + lastDir.absolutePath() + "\n") @pyqtSlot() ##removeRecursively() def on_btnDir_removeALL_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text().strip() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(sous) ret = QMessageBox.question(self, "确认删除", "确认删除目录下的所有文件及目录吗?\n" + sous) if ret != QMessageBox.Yes: return if dirObj.removeRecursively(): self.ui.textEdit.appendPlainText("删除目录及文件成功\n") else: self.ui.textEdit.appendPlainText("删除目录及文件失败\n") @pyqtSlot() ##absoluteFilePath() def on_btnDir_absFilePath_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return parDir = QDir.currentPath() dirObj = QDir(parDir) text = dirObj.absoluteFilePath(sous) self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##absolutePath() def on_btnDir_absPath_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(sous) text = dirObj.absolutePath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##canonicalPath() def on_btnDir_canonPath_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个目录") return dirObj = QDir(sous) text = dirObj.canonicalPath() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##filePath() def on_btnDir_filePath_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editFile.text() if sous == "": self.ui.textEdit.appendPlainText("请先选择一个文件") return parDir = QDir.currentPath() dirObj = QDir(parDir) text = dirObj.filePath(sous) self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##exists() def on_btnDir_exists_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() ## if sous=="": ## self.ui.textEdit.appendPlainText("请先选择一个目录") ## return dirObj = QDir(sous) #若sous为空,则使用其当前目录 self.ui.textEdit.appendPlainText(dirObj.absolutePath() + "\n") if dirObj.exists(): self.ui.textEdit.appendPlainText("True \n") else: self.ui.textEdit.appendPlainText("False \n") @pyqtSlot() ##dirName() def on_btnDir_dirName_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() ## if sous=="": ## self.ui.textEdit.appendPlainText("请先选择一个目录") ## return dirObj = QDir(sous) #若sous为空,则使用其当前目录 text = dirObj.dirName() self.ui.textEdit.appendPlainText(text + "\n") @pyqtSlot() ##entryList()dirs def on_btnDir_listDir_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() dirObj = QDir(sous) #若sous为空,则使用其当前目录 strList = dirObj.entryList(QDir.Dirs | QDir.NoDotAndDotDot) self.ui.textEdit.appendPlainText("所选目录下的所有目录:") for line in strList: self.ui.textEdit.appendPlainText(line) self.ui.textEdit.appendPlainText("\n") @pyqtSlot() ##entryList()files def on_btnDir_listFile_clicked(self): self.__showBtnInfo(self.sender()) sous = self.ui.editDir.text() dirObj = QDir(sous) #若sous为空,则使用其当前目录 strList = dirObj.entryList(QDir.Files) self.ui.textEdit.appendPlainText("所选目录下的所有文件:") for line in strList: self.ui.textEdit.appendPlainText(line) self.ui.textEdit.appendPlainText("\n") ## ==========QFileSystemWatcher类=================== @pyqtSlot() ##addPath()添加监听目录 def on_btnWatch_addDir_clicked(self): self.__showBtnInfo(self.sender()) curDir = QDir.currentPath() aDir = QFileDialog.getExistingDirectory(self, "选择一个需要监听的目录", curDir, QFileDialog.ShowDirsOnly) self.fileWatcher.addPath(aDir) #添加监听目录 self.ui.textEdit.appendPlainText("添加的监听目录:") self.ui.textEdit.appendPlainText(aDir + "\n") @pyqtSlot() ##addPaths()添加监听文件 def on_btnWatch_addFiles_clicked(self): self.__showBtnInfo(self.sender()) curDir = QDir.currentPath() fileList, flt = QFileDialog.getOpenFileNames(self, "选择需要监听的文件", curDir, "所有文件 (*.*)") self.fileWatcher.addPaths(fileList) #添加监听文件列表 self.ui.textEdit.appendPlainText("添加的监听文件:") for lineStr in fileList: self.ui.textEdit.appendPlainText(lineStr) self.ui.textEdit.appendPlainText("") @pyqtSlot() ##removePaths()移除所有监听的文件和目录 def on_btnWatch_remove_clicked(self): self.__showBtnInfo(self.sender()) self.ui.textEdit.appendPlainText("移除所有监听的目录和文件\n") dirList = self.fileWatcher.directories() self.fileWatcher.removePaths(dirList) fileList = self.fileWatcher.files() self.fileWatcher.removePaths(fileList) @pyqtSlot() ##显示监听目录,directories() def on_btnWatch_dirs_clicked(self): self.__showBtnInfo(self.sender()) strList = self.fileWatcher.directories() self.ui.textEdit.appendPlainText("正在监听的目录:") for line in strList: self.ui.textEdit.appendPlainText(line) self.ui.textEdit.appendPlainText("\n") @pyqtSlot() ##显示监听文件,files() def on_btnWatch_files_clicked(self): self.__showBtnInfo(self.sender()) strList = self.fileWatcher.files() self.ui.textEdit.appendPlainText("正在监听的文件:") for line in strList: self.ui.textEdit.appendPlainText(line) self.ui.textEdit.appendPlainText("\n") ## =============自定义槽函数=============================== def do_directoryChanged(self, path): ##目录发生变化 self.ui.textEdit.appendPlainText(path) self.ui.textEdit.appendPlainText("目录发生了变化\n") def do_fileChanged(self, path): ##文件发生变化 self.ui.textEdit.appendPlainText(path) self.ui.textEdit.appendPlainText("文件发生了变化\n")
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.project = ProjectManager() self.modules_manager = ModuleManager(self) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.modules_manager.init_modules([ CompilerModule, LevelWidget, AssetViewWidget, AssetBrowser, LogWidget, ProfilerWidget, ScriptEditorManager ]) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed) def open_project(self, name, dir): self.project.open_project(name, dir) self.modules_manager.open_project(self.project) self.watch_project_dir() def reload_all(self): self.modules_manager['compiler'].compile_all() for k, v in self.project.instances.items(): v.console_api.reload_all() def watch_project_dir(self): files = self.file_watch.files() directories = self.file_watch.directories() if len(files): self.file_watch.removePaths(files) if len(directories): self.file_watch.removePaths(directories) files = self.build_file_watch.files() directories = self.build_file_watch.directories() if len(files): self.build_file_watch.removePaths(files) if len(directories): self.build_file_watch.removePaths(directories) files = [] it = QDirIterator(self.project.source_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.file_watch.addPaths(files) files = [] it = QDirIterator(self.project.build_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.build_file_watch.addPaths(files) def file_changed(self, path): self.modules_manager['compiler'].compile_all() def dir_changed(self, path): self.watch_project_dir() def build_file_changed(self, path): pass def build_dir_changed(self, path): pass def open_script_editor(self): self.script_editor_dock_widget.show() def open_recorded_events(self): self.modules_manager['profiler'].dock.show() def run_standalone(self): self.project.run_release("Standalone") def run_level(self): self.project.run_develop("Level", compile_=True, continue_=True, port=5566) def closeEvent(self, evnt): self.modules_manager.close_project() self.project.killall() evnt.accept()
class ProjectBrowserModel(BrowserModel): """ Class implementing the project browser model. @signal vcsStateChanged(str) emitted after the VCS state has changed """ vcsStateChanged = pyqtSignal(str) def __init__(self, parent): """ Constructor @param parent reference to parent object (Project.Project) """ super(ProjectBrowserModel, self).__init__(parent, nopopulate=True) rootData = self.tr("Name") self.rootItem = BrowserItem(None, rootData) self.rootItem.itemData.append(self.tr("VCS Status")) self.progDir = None self.project = parent self.watchedItems = {} self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.directoryChanged) self.inRefresh = False self.projectBrowserTypes = { "SOURCES": ProjectBrowserSourceType, "FORMS": ProjectBrowserFormType, "RESOURCES": ProjectBrowserResourceType, "INTERFACES": ProjectBrowserInterfaceType, "TRANSLATIONS": ProjectBrowserTranslationType, "OTHERS": ProjectBrowserOthersType, } self.colorNames = { "A": "VcsAdded", "M": "VcsModified", "O": "VcsRemoved", "R": "VcsReplaced", "U": "VcsUpdate", "Z": "VcsConflict", } self.itemBackgroundColors = { " ": QColor(), "A": Preferences.getProjectBrowserColour(self.colorNames["A"]), "M": Preferences.getProjectBrowserColour(self.colorNames["M"]), "O": Preferences.getProjectBrowserColour(self.colorNames["O"]), "R": Preferences.getProjectBrowserColour(self.colorNames["R"]), "U": Preferences.getProjectBrowserColour(self.colorNames["U"]), "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]), } self.highLightColor = \ Preferences.getProjectBrowserColour("Highlighted") # needed by preferencesChanged() self.vcsStatusReport = {} def data(self, index, role): """ Public method to get data of an item. @param index index of the data to retrieve (QModelIndex) @param role role of data (Qt.ItemDataRole) @return requested data """ if not index.isValid(): return None if role == Qt.TextColorRole: if index.column() == 0: try: return index.internalPointer().getTextColor() except AttributeError: return None elif role == Qt.BackgroundColorRole: try: col = self.itemBackgroundColors[ index.internalPointer().vcsState] if col.isValid(): return col else: return None except AttributeError: return None except KeyError: return None return BrowserModel.data(self, index, role) def populateItem(self, parentItem, repopulate=False): """ Public method to populate an item's subtree. @param parentItem reference to the item to be populated @param repopulate flag indicating a repopulation (boolean) """ if parentItem.type() == ProjectBrowserItemSimpleDirectory: return # nothing to do elif parentItem.type() == ProjectBrowserItemDirectory: self.populateProjectDirectoryItem(parentItem, repopulate) elif parentItem.type() == ProjectBrowserItemFile: self.populateFileItem(parentItem, repopulate) else: BrowserModel.populateItem(self, parentItem, repopulate) def populateProjectDirectoryItem(self, parentItem, repopulate=False): """ Public method to populate a directory item's subtree. @param parentItem reference to the directory item to be populated @param repopulate flag indicating a repopulation (boolean) """ self._addWatchedItem(parentItem) qdir = QDir(parentItem.dirName()) if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) entryInfoList = qdir.entryInfoList(filter) if len(entryInfoList) > 0: if repopulate: self.beginInsertRows( self.createIndex(parentItem.row(), 0, parentItem), 0, len(entryInfoList) - 1) states = {} if self.project.vcs is not None: for f in entryInfoList: fname = f.absoluteFilePath() states[os.path.normcase(fname)] = 0 dname = parentItem.dirName() self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates(states, dname) for f in entryInfoList: if f.isDir(): node = ProjectBrowserDirectoryItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0]) if self.project.vcs is not None: fname = f.absoluteFilePath() if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) self.project.clearStatusMonitorCachedState( f.absoluteFilePath()) else: node.addVcsStatus(self.tr("local")) self._addItem(node, parentItem) if repopulate: self.endInsertRows() def projectClosed(self): """ Public method called after a project has been closed. """ self.__vcsStatus = {} self.watchedItems = {} watchedDirs = self.watcher.directories() if watchedDirs: self.watcher.removePaths(watchedDirs) self.rootItem.removeChildren() self.beginResetModel() self.endResetModel() # reset the module parser cache Utilities.ModuleParser.resetParsedModules() def projectOpened(self): """ Public method used to populate the model after a project has been opened. """ self.__vcsStatus = {} states = {} keys = list(self.projectBrowserTypes.keys())[:] if self.project.vcs is not None: for key in keys: for fn in self.project.pdata[key]: states[os.path.normcase( os.path.join(self.project.ppath, fn))] = 0 self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates( states, self.project.ppath) self.inRefresh = True for key in keys: # Show the entry in bold in the others browser to make it more # distinguishable if key == "OTHERS": bold = True else: bold = False if key == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" for fn in self.project.pdata[key]: fname = os.path.join(self.project.ppath, fn) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[key], fn) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[key], False, bold) else: itm = ProjectBrowserFileItem(parentItem, fname, self.projectBrowserTypes[key], False, bold, sourceLanguage=sourceLanguage) self._addItem(itm, parentItem) if self.project.vcs is not None: if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: itm.addVcsStatus(self.project.vcs.vcsName()) else: itm.addVcsStatus(self.tr("local")) else: itm.addVcsStatus("") self.inRefresh = False self.beginResetModel() self.endResetModel() def findParentItemByName(self, type_, name, dontSplit=False): """ Public method to find an item given its name. <b>Note</b>: This method creates all necessary parent items, if they don't exist. @param type_ type of the item @param name name of the item (string) @param dontSplit flag indicating the name should not be split (boolean) @return reference to the item found and the new display name (string) """ if dontSplit: pathlist = [] pathlist.append(name) pathlist.append("ignore_me") else: pathlist = re.split(r'/|\\', name) if len(pathlist) > 1: olditem = self.rootItem path = self.project.ppath for p in pathlist[:-1]: itm = self.findChildItem(p, 0, olditem) path = os.path.join(path, p) if itm is None: itm = ProjectBrowserSimpleDirectoryItem( olditem, type_, p, path) self.__addVCSStatus(itm, path) if self.inRefresh: self._addItem(itm, olditem) else: if olditem == self.rootItem: oldindex = QModelIndex() else: oldindex = self.createIndex( olditem.row(), 0, olditem) self.addItem(itm, oldindex) else: if type_ and type_ not in itm.getProjectTypes(): itm.addProjectType(type_) index = self.createIndex(itm.row(), 0, itm) self.dataChanged.emit(index, index) olditem = itm return (itm, pathlist[-1]) else: return (self.rootItem, name) def findChildItem(self, text, column, parentItem=None): """ Public method to find a child item given some text. @param text text to search for (string) @param column column to search in (integer) @param parentItem reference to parent item @return reference to the item found """ if parentItem is None: parentItem = self.rootItem for itm in parentItem.children(): if itm.data(column) == text: return itm return None def addNewItem(self, typeString, name, additionalTypeStrings=[]): """ Public method to add a new item to the model. @param typeString string denoting the type of the new item (string) @param name name of the new item (string) @param additionalTypeStrings names of additional types (list of string) """ # Show the entry in bold in the others browser to make it more # distinguishable if typeString == "OTHERS": bold = True else: bold = False fname = os.path.join(self.project.ppath, name) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[typeString], name) if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[typeString], False, bold) else: if typeString == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" itm = ProjectBrowserFileItem(parentItem, fname, self.projectBrowserTypes[typeString], False, bold, sourceLanguage=sourceLanguage) self.__addVCSStatus(itm, fname) if additionalTypeStrings: for additionalTypeString in additionalTypeStrings: type_ = self.projectBrowserTypes[additionalTypeString] itm.addProjectType(type_) self.addItem(itm, parentIndex) def renameItem(self, name, newFilename): """ Public method to rename an item. @param name the old display name (string) @param newFilename new filename of the item (string) """ itm = self.findItem(name) if itm is None: return index = self.createIndex(itm.row(), 0, itm) itm.setName(newFilename) self.dataChanged.emit(index, index) self.repopulateItem(newFilename) def findItem(self, name): """ Public method to find an item given its name. @param name name of the item (string) @return reference to the item found """ if QDir.isAbsolutePath(name): name = self.project.getRelativePath(name) pathlist = re.split(r'/|\\', name) if len(pathlist) > 0: olditem = self.rootItem for p in pathlist: itm = self.findChildItem(p, 0, olditem) if itm is None: return None olditem = itm return itm else: return None def itemIndexByName(self, name): """ Public method to find an item's index given its name. @param name name of the item (string) @return index of the item found (QModelIndex) """ itm = self.findItem(name) if itm is None: index = QModelIndex() else: index = self.createIndex(itm.row(), 0, itm) return index def itemIndexByNameAndLine(self, name, lineno): """ Public method to find an item's index given its name. @param name name of the item (string) @param lineno one based line number of the item (integer) @return index of the item found (QModelIndex) """ index = QModelIndex() itm = self.findItem(name) if itm is not None and \ isinstance(itm, ProjectBrowserFileItem): olditem = itm autoPopulate = Preferences.getProject("AutoPopulateItems") while itm is not None: if not itm.isPopulated(): if itm.isLazyPopulated() and autoPopulate: self.populateItem(itm) else: break for child in itm.children(): try: start, end = child.boundaries() if end == -1: end = 1000000 # assume end of file if start <= lineno <= end: itm = child break except AttributeError: pass else: itm = None if itm: olditem = itm index = self.createIndex(olditem.row(), 0, olditem) return index def directoryChanged(self, path): """ Public slot to handle the directoryChanged signal of the watcher. @param path path of the directory (string) """ if path not in self.watchedItems: # just ignore the situation we don't have a reference to the item return if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) for itm in self.watchedItems[path]: oldCnt = itm.childCount() qdir = QDir(itm.dirName()) entryInfoList = qdir.entryInfoList(filter) # step 1: check for new entries children = itm.children() for f in entryInfoList: fpath = Utilities.toNativeSeparators(f.absoluteFilePath()) childFound = False for child in children: if child.name() == fpath: childFound = True children.remove(child) break if childFound: continue cnt = itm.childCount() self.beginInsertRows(self.createIndex(itm.row(), 0, itm), cnt, cnt) if f.isDir(): node = ProjectBrowserDirectoryItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0]) self._addItem(node, itm) if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(node.name()) if state == self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) else: node.addVcsStatus(self.tr("local")) self.endInsertRows() # step 2: check for removed entries if len(entryInfoList) != itm.childCount(): for row in range(oldCnt - 1, -1, -1): child = itm.child(row) childname = Utilities.fromNativeSeparators(child.name()) entryFound = False for f in entryInfoList: if f.absoluteFilePath() == childname: entryFound = True entryInfoList.remove(f) break if entryFound: continue self._removeWatchedItem(child) self.beginRemoveRows(self.createIndex(itm.row(), 0, itm), row, row) itm.removeChild(child) self.endRemoveRows() def __addVCSStatus(self, item, name): """ Private method used to set the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) """ if self.project.vcs is not None: state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.addVcsStatus(self.project.vcs.vcsName()) else: item.addVcsStatus(self.tr("local")) else: item.addVcsStatus("") def __updateVCSStatus(self, item, name, recursive=True): """ Private method used to update the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) @keyparam recursive flag indicating a recursive update (boolean) """ if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.setVcsStatus(self.project.vcs.vcsName()) else: item.setVcsStatus(self.tr("local")) if recursive: name = os.path.dirname(name) parentItem = item.parent() if name and parentItem is not self.rootItem: self.__updateVCSStatus(parentItem, name, recursive) else: item.setVcsStatus("") index = self.createIndex(item.row(), 0, item) self.dataChanged.emit(index, index) def updateVCSStatus(self, name, recursive=True): """ Public method used to update the vcs status of a node. @param name filename belonging to this item (string) @param recursive flag indicating a recursive update (boolean) """ item = self.findItem(name) if item: self.__updateVCSStatus(item, name, recursive) def removeItem(self, name): """ Public method to remove a named item. @param name file or directory name of the item (string). """ fname = os.path.basename(name) parentItem = self.findParentItemByName(0, name)[0] if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) childItem = self.findChildItem(fname, 0, parentItem) if childItem is not None: self.beginRemoveRows(parentIndex, childItem.row(), childItem.row()) parentItem.removeChild(childItem) self.endRemoveRows() def repopulateItem(self, name): """ Public method to repopulate an item. @param name name of the file relative to the project root (string) """ itm = self.findItem(name) if itm is None: return if itm.isLazyPopulated(): if not itm.isPopulated(): # item is not populated yet, nothing to do return if itm.childCount(): index = self.createIndex(itm.row(), 0, itm) self.beginRemoveRows(index, 0, itm.childCount() - 1) itm.removeChildren() self.endRemoveRows() Utilities.ModuleParser.resetParsedModule( os.path.join(self.project.ppath, name)) self.populateItem(itm, True) def projectPropertiesChanged(self): """ Public method to react on a change of the project properties. """ # nothing to do for now return def changeVCSStates(self, statesList): """ Public slot to record the (non normal) VCS states. @param statesList list of VCS state entries (list of strings) giving the states in the first column and the path relative to the project directory starting with the third column. The allowed status flags are: <ul> <li>"A" path was added but not yet comitted</li> <li>"M" path has local changes</li> <li>"O" path was removed</li> <li>"R" path was deleted and then re-added</li> <li>"U" path needs an update</li> <li>"Z" path contains a conflict</li> <li>" " path is back at normal</li> </ul> """ statesList.sort() lastHead = "" itemCache = {} if len(statesList) == 1 and statesList[0] == '--RESET--': statesList = [] for name in list(self.__vcsStatus.keys()): statesList.append(" {0}".format(name)) for name in statesList: state = name[0] name = name[1:].strip() if state == ' ': if name in self.__vcsStatus: del self.__vcsStatus[name] else: self.__vcsStatus[name] = state try: itm = itemCache[name] except KeyError: itm = self.findItem(name) if itm: itemCache[name] = itm if itm: itm.setVcsState(state) itm.setVcsStatus(self.project.vcs.vcsName()) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex(itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) head, tail = os.path.split(name) if head != lastHead: if lastHead: self.__changeParentsVCSState(lastHead, itemCache) lastHead = head if lastHead: self.__changeParentsVCSState(lastHead, itemCache) try: globalVcsStatus = sorted(self.__vcsStatus.values())[-1] except IndexError: globalVcsStatus = ' ' self.vcsStateChanged.emit(globalVcsStatus) def __changeParentsVCSState(self, path, itemCache): """ Private method to recursively change the parents VCS state. @param path pathname of parent item (string) @param itemCache reference to the item cache used to store references to named items """ while path: try: itm = itemCache[path] except KeyError: itm = self.findItem(path) if itm: itemCache[path] = itm if itm: state = " " for id_ in itm.children(): if state < id_.vcsState: state = id_.vcsState if state != itm.vcsState: itm.setVcsState(state) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex(itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) path, tail = os.path.split(path) def preferencesChanged(self): """ Public method used to handle a change in preferences. """ for code in list(self.colorNames.keys()): color = Preferences.getProjectBrowserColour(self.colorNames[code]) if color.name() == self.itemBackgroundColors[code].name(): continue self.itemBackgroundColors[code] = color color = Preferences.getProjectBrowserColour("Highlighted") if self.highLightColor.name() != color.name(): self.highLightColor = color
class ProjectBrowserModel(BrowserModel): """ Class implementing the project browser model. @signal vcsStateChanged(str) emitted after the VCS state has changed """ vcsStateChanged = pyqtSignal(str) def __init__(self, parent): """ Constructor @param parent reference to parent object (Project.Project) """ super(ProjectBrowserModel, self).__init__(parent, nopopulate=True) rootData = self.tr("Name") self.rootItem = BrowserItem(None, rootData) self.rootItem.itemData.append(self.tr("VCS Status")) self.progDir = None self.project = parent self.watchedItems = {} self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.directoryChanged) self.inRefresh = False self.projectBrowserTypes = { "SOURCES": ProjectBrowserSourceType, "FORMS": ProjectBrowserFormType, "RESOURCES": ProjectBrowserResourceType, "INTERFACES": ProjectBrowserInterfaceType, "TRANSLATIONS": ProjectBrowserTranslationType, "OTHERS": ProjectBrowserOthersType, } self.colorNames = { "A": "VcsAdded", "M": "VcsModified", "O": "VcsRemoved", "R": "VcsReplaced", "U": "VcsUpdate", "Z": "VcsConflict", } self.itemBackgroundColors = { " ": QColor(), "A": Preferences.getProjectBrowserColour(self.colorNames["A"]), "M": Preferences.getProjectBrowserColour(self.colorNames["M"]), "O": Preferences.getProjectBrowserColour(self.colorNames["O"]), "R": Preferences.getProjectBrowserColour(self.colorNames["R"]), "U": Preferences.getProjectBrowserColour(self.colorNames["U"]), "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]), } self.highLightColor = \ Preferences.getProjectBrowserColour("Highlighted") # needed by preferencesChanged() self.vcsStatusReport = {} def data(self, index, role): """ Public method to get data of an item. @param index index of the data to retrieve (QModelIndex) @param role role of data (Qt.ItemDataRole) @return requested data """ if not index.isValid(): return None if role == Qt.TextColorRole: if index.column() == 0: try: return index.internalPointer().getTextColor() except AttributeError: return None elif role == Qt.BackgroundColorRole: try: col = self.itemBackgroundColors[ index.internalPointer().vcsState] if col.isValid(): return col else: return None except AttributeError: return None except KeyError: return None return BrowserModel.data(self, index, role) def populateItem(self, parentItem, repopulate=False): """ Public method to populate an item's subtree. @param parentItem reference to the item to be populated @param repopulate flag indicating a repopulation (boolean) """ if parentItem.type() == ProjectBrowserItemSimpleDirectory: return # nothing to do elif parentItem.type() == ProjectBrowserItemDirectory: self.populateProjectDirectoryItem(parentItem, repopulate) elif parentItem.type() == ProjectBrowserItemFile: self.populateFileItem(parentItem, repopulate) else: BrowserModel.populateItem(self, parentItem, repopulate) def populateProjectDirectoryItem(self, parentItem, repopulate=False): """ Public method to populate a directory item's subtree. @param parentItem reference to the directory item to be populated @param repopulate flag indicating a repopulation (boolean) """ self._addWatchedItem(parentItem) qdir = QDir(parentItem.dirName()) if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) entryInfoList = qdir.entryInfoList(filter) if len(entryInfoList) > 0: if repopulate: self.beginInsertRows(self.createIndex( parentItem.row(), 0, parentItem), 0, len(entryInfoList) - 1) states = {} if self.project.vcs is not None: for f in entryInfoList: fname = f.absoluteFilePath() states[os.path.normcase(fname)] = 0 dname = parentItem.dirName() self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates(states, dname) for f in entryInfoList: if f.isDir(): node = ProjectBrowserDirectoryItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( parentItem, Utilities.toNativeSeparators(f.absoluteFilePath()), parentItem.getProjectTypes()[0]) if self.project.vcs is not None: fname = f.absoluteFilePath() if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) self.project.clearStatusMonitorCachedState( f.absoluteFilePath()) else: node.addVcsStatus(self.tr("local")) self._addItem(node, parentItem) if repopulate: self.endInsertRows() def projectClosed(self): """ Public method called after a project has been closed. """ self.__vcsStatus = {} self.watchedItems = {} watchedDirs = self.watcher.directories() if watchedDirs: self.watcher.removePaths(watchedDirs) self.rootItem.removeChildren() self.beginResetModel() self.endResetModel() # reset the module parser cache Utilities.ModuleParser.resetParsedModules() def projectOpened(self): """ Public method used to populate the model after a project has been opened. """ self.__vcsStatus = {} states = {} keys = list(self.projectBrowserTypes.keys())[:] if self.project.vcs is not None: for key in keys: for fn in self.project.pdata[key]: states[os.path.normcase( os.path.join(self.project.ppath, fn))] = 0 self.project.vcs.clearStatusCache() states = self.project.vcs.vcsAllRegisteredStates( states, self.project.ppath) self.inRefresh = True for key in keys: # Show the entry in bold in the others browser to make it more # distinguishable if key == "OTHERS": bold = True else: bold = False if key == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" for fn in self.project.pdata[key]: fname = os.path.join(self.project.ppath, fn) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[key], fn) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[key], False, bold) else: itm = ProjectBrowserFileItem( parentItem, fname, self.projectBrowserTypes[key], False, bold, sourceLanguage=sourceLanguage) self._addItem(itm, parentItem) if self.project.vcs is not None: if states[os.path.normcase(fname)] == \ self.project.vcs.canBeCommitted: itm.addVcsStatus(self.project.vcs.vcsName()) else: itm.addVcsStatus(self.tr("local")) else: itm.addVcsStatus("") self.inRefresh = False self.beginResetModel() self.endResetModel() def findParentItemByName(self, type_, name, dontSplit=False): """ Public method to find an item given its name. <b>Note</b>: This method creates all necessary parent items, if they don't exist. @param type_ type of the item @param name name of the item (string) @param dontSplit flag indicating the name should not be split (boolean) @return reference to the item found and the new display name (string) """ if dontSplit: pathlist = [] pathlist.append(name) pathlist.append("ignore_me") else: pathlist = re.split(r'/|\\', name) if len(pathlist) > 1: olditem = self.rootItem path = self.project.ppath for p in pathlist[:-1]: itm = self.findChildItem(p, 0, olditem) path = os.path.join(path, p) if itm is None: itm = ProjectBrowserSimpleDirectoryItem( olditem, type_, p, path) self.__addVCSStatus(itm, path) if self.inRefresh: self._addItem(itm, olditem) else: if olditem == self.rootItem: oldindex = QModelIndex() else: oldindex = self.createIndex( olditem.row(), 0, olditem) self.addItem(itm, oldindex) else: if type_ and type_ not in itm.getProjectTypes(): itm.addProjectType(type_) index = self.createIndex(itm.row(), 0, itm) self.dataChanged.emit(index, index) olditem = itm return (itm, pathlist[-1]) else: return (self.rootItem, name) def findChildItem(self, text, column, parentItem=None): """ Public method to find a child item given some text. @param text text to search for (string) @param column column to search in (integer) @param parentItem reference to parent item @return reference to the item found """ if parentItem is None: parentItem = self.rootItem for itm in parentItem.children(): if itm.data(column) == text: return itm return None def addNewItem(self, typeString, name, additionalTypeStrings=[]): """ Public method to add a new item to the model. @param typeString string denoting the type of the new item (string) @param name name of the new item (string) @param additionalTypeStrings names of additional types (list of string) """ # Show the entry in bold in the others browser to make it more # distinguishable if typeString == "OTHERS": bold = True else: bold = False fname = os.path.join(self.project.ppath, name) parentItem, dt = self.findParentItemByName( self.projectBrowserTypes[typeString], name) if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) if os.path.isdir(fname): itm = ProjectBrowserDirectoryItem( parentItem, fname, self.projectBrowserTypes[typeString], False, bold) else: if typeString == "SOURCES": sourceLanguage = self.project.pdata["PROGLANGUAGE"][0] else: sourceLanguage = "" itm = ProjectBrowserFileItem( parentItem, fname, self.projectBrowserTypes[typeString], False, bold, sourceLanguage=sourceLanguage) self.__addVCSStatus(itm, fname) if additionalTypeStrings: for additionalTypeString in additionalTypeStrings: type_ = self.projectBrowserTypes[additionalTypeString] itm.addProjectType(type_) self.addItem(itm, parentIndex) def renameItem(self, name, newFilename): """ Public method to rename an item. @param name the old display name (string) @param newFilename new filename of the item (string) """ itm = self.findItem(name) if itm is None: return index = self.createIndex(itm.row(), 0, itm) itm.setName(newFilename) self.dataChanged.emit(index, index) self.repopulateItem(newFilename) def findItem(self, name): """ Public method to find an item given its name. @param name name of the item (string) @return reference to the item found """ if QDir.isAbsolutePath(name): name = self.project.getRelativePath(name) pathlist = re.split(r'/|\\', name) if len(pathlist) > 0: olditem = self.rootItem for p in pathlist: itm = self.findChildItem(p, 0, olditem) if itm is None: return None olditem = itm return itm else: return None def itemIndexByName(self, name): """ Public method to find an item's index given its name. @param name name of the item (string) @return index of the item found (QModelIndex) """ itm = self.findItem(name) if itm is None: index = QModelIndex() else: index = self.createIndex(itm.row(), 0, itm) return index def itemIndexByNameAndLine(self, name, lineno): """ Public method to find an item's index given its name. @param name name of the item (string) @param lineno one based line number of the item (integer) @return index of the item found (QModelIndex) """ index = QModelIndex() itm = self.findItem(name) if itm is not None and \ isinstance(itm, ProjectBrowserFileItem): olditem = itm autoPopulate = Preferences.getProject("AutoPopulateItems") while itm is not None: if not itm.isPopulated(): if itm.isLazyPopulated() and autoPopulate: self.populateItem(itm) else: break for child in itm.children(): try: start, end = child.boundaries() if end == -1: end = 1000000 # assume end of file if start <= lineno <= end: itm = child break except AttributeError: pass else: itm = None if itm: olditem = itm index = self.createIndex(olditem.row(), 0, olditem) return index def directoryChanged(self, path): """ Public slot to handle the directoryChanged signal of the watcher. @param path path of the directory (string) """ if path not in self.watchedItems: # just ignore the situation we don't have a reference to the item return if Preferences.getUI("BrowsersListHiddenFiles"): filter = QDir.Filters(QDir.AllEntries | QDir.Hidden | QDir.NoDotAndDotDot) else: filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot) for itm in self.watchedItems[path]: oldCnt = itm.childCount() qdir = QDir(itm.dirName()) entryInfoList = qdir.entryInfoList(filter) # step 1: check for new entries children = itm.children() for f in entryInfoList: fpath = Utilities.toNativeSeparators(f.absoluteFilePath()) childFound = False for child in children: if child.name() == fpath: childFound = True children.remove(child) break if childFound: continue cnt = itm.childCount() self.beginInsertRows( self.createIndex(itm.row(), 0, itm), cnt, cnt) if f.isDir(): node = ProjectBrowserDirectoryItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0], False) else: node = ProjectBrowserFileItem( itm, Utilities.toNativeSeparators(f.absoluteFilePath()), itm.getProjectTypes()[0]) self._addItem(node, itm) if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(node.name()) if state == self.project.vcs.canBeCommitted: node.addVcsStatus(self.project.vcs.vcsName()) else: node.addVcsStatus(self.tr("local")) self.endInsertRows() # step 2: check for removed entries if len(entryInfoList) != itm.childCount(): for row in range(oldCnt - 1, -1, -1): child = itm.child(row) childname = Utilities.fromNativeSeparators(child.name()) entryFound = False for f in entryInfoList: if f.absoluteFilePath() == childname: entryFound = True entryInfoList.remove(f) break if entryFound: continue self._removeWatchedItem(child) self.beginRemoveRows( self.createIndex(itm.row(), 0, itm), row, row) itm.removeChild(child) self.endRemoveRows() def __addVCSStatus(self, item, name): """ Private method used to set the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) """ if self.project.vcs is not None: state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.addVcsStatus(self.project.vcs.vcsName()) else: item.addVcsStatus(self.tr("local")) else: item.addVcsStatus("") def __updateVCSStatus(self, item, name, recursive=True): """ Private method used to update the vcs status of a node. @param item item to work on @param name filename belonging to this item (string) @keyparam recursive flag indicating a recursive update (boolean) """ if self.project.vcs is not None: self.project.vcs.clearStatusCache() state = self.project.vcs.vcsRegisteredState(name) if state == self.project.vcs.canBeCommitted: item.setVcsStatus(self.project.vcs.vcsName()) else: item.setVcsStatus(self.tr("local")) if recursive: name = os.path.dirname(name) parentItem = item.parent() if name and parentItem is not self.rootItem: self.__updateVCSStatus(parentItem, name, recursive) else: item.setVcsStatus("") index = self.createIndex(item.row(), 0, item) self.dataChanged.emit(index, index) def updateVCSStatus(self, name, recursive=True): """ Public method used to update the vcs status of a node. @param name filename belonging to this item (string) @param recursive flag indicating a recursive update (boolean) """ item = self.findItem(name) if item: self.__updateVCSStatus(item, name, recursive) def removeItem(self, name): """ Public method to remove a named item. @param name file or directory name of the item (string). """ fname = os.path.basename(name) parentItem = self.findParentItemByName(0, name)[0] if parentItem == self.rootItem: parentIndex = QModelIndex() else: parentIndex = self.createIndex(parentItem.row(), 0, parentItem) childItem = self.findChildItem(fname, 0, parentItem) if childItem is not None: self.beginRemoveRows(parentIndex, childItem.row(), childItem.row()) parentItem.removeChild(childItem) self.endRemoveRows() def repopulateItem(self, name): """ Public method to repopulate an item. @param name name of the file relative to the project root (string) """ itm = self.findItem(name) if itm is None: return if itm.isLazyPopulated(): if not itm.isPopulated(): # item is not populated yet, nothing to do return if itm.childCount(): index = self.createIndex(itm.row(), 0, itm) self.beginRemoveRows(index, 0, itm.childCount() - 1) itm.removeChildren() self.endRemoveRows() Utilities.ModuleParser.resetParsedModule( os.path.join(self.project.ppath, name)) self.populateItem(itm, True) def projectPropertiesChanged(self): """ Public method to react on a change of the project properties. """ # nothing to do for now return def changeVCSStates(self, statesList): """ Public slot to record the (non normal) VCS states. @param statesList list of VCS state entries (list of strings) giving the states in the first column and the path relative to the project directory starting with the third column. The allowed status flags are: <ul> <li>"A" path was added but not yet comitted</li> <li>"M" path has local changes</li> <li>"O" path was removed</li> <li>"R" path was deleted and then re-added</li> <li>"U" path needs an update</li> <li>"Z" path contains a conflict</li> <li>" " path is back at normal</li> </ul> """ statesList.sort() lastHead = "" itemCache = {} if len(statesList) == 1 and statesList[0] == '--RESET--': statesList = [] for name in list(self.__vcsStatus.keys()): statesList.append(" {0}".format(name)) for name in statesList: state = name[0] name = name[1:].strip() if state == ' ': if name in self.__vcsStatus: del self.__vcsStatus[name] else: self.__vcsStatus[name] = state try: itm = itemCache[name] except KeyError: itm = self.findItem(name) if itm: itemCache[name] = itm if itm: itm.setVcsState(state) itm.setVcsStatus(self.project.vcs.vcsName()) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex( itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) head, tail = os.path.split(name) if head != lastHead: if lastHead: self.__changeParentsVCSState(lastHead, itemCache) lastHead = head if lastHead: self.__changeParentsVCSState(lastHead, itemCache) try: globalVcsStatus = sorted(self.__vcsStatus.values())[-1] except IndexError: globalVcsStatus = ' ' self.vcsStateChanged.emit(globalVcsStatus) def __changeParentsVCSState(self, path, itemCache): """ Private method to recursively change the parents VCS state. @param path pathname of parent item (string) @param itemCache reference to the item cache used to store references to named items """ while path: try: itm = itemCache[path] except KeyError: itm = self.findItem(path) if itm: itemCache[path] = itm if itm: state = " " for id_ in itm.children(): if state < id_.vcsState: state = id_.vcsState if state != itm.vcsState: itm.setVcsState(state) index1 = self.createIndex(itm.row(), 0, itm) index2 = self.createIndex( itm.row(), self.rootItem.columnCount(), itm) self.dataChanged.emit(index1, index2) path, tail = os.path.split(path) def preferencesChanged(self): """ Public method used to handle a change in preferences. """ for code in list(self.colorNames.keys()): color = Preferences.getProjectBrowserColour(self.colorNames[code]) if color.name() == self.itemBackgroundColors[code].name(): continue self.itemBackgroundColors[code] = color color = Preferences.getProjectBrowserColour("Highlighted") if self.highLightColor.name() != color.name(): self.highLightColor = color
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() self.setupUi(self) self.centralwidget.hide() self.parser = argparse.ArgumentParser("playground") self.parser.add_argument("-d", "--data-dir", type=str, help="data dir") self.parser.add_argument("-a", "--console-address", type=str, help="console address", default='localhost') self.parser.add_argument("-p", "--console-port", type=int, help="console port", default=2222) self.args = self.parser.parse_args() self.data_dir = self.args.data_dir self.console_port = self.args.console_port self.console_address = self.args.console_address self.project = CetechProject() self.api = QtConsoleAPI(self.console_address, self.console_port) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) self.script_editor_widget = ScriptEditor(project_manager=self.project, api=self.api) self.script_editor_dock_widget = QDockWidget(self) self.script_editor_dock_widget.setWindowTitle("Script editor") self.script_editor_dock_widget.hide() self.script_editor_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.script_editor_dock_widget.setWidget(self.script_editor_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.script_editor_dock_widget) self.log_widget = LogWidget(self.api, self.script_editor_widget) self.log_dock_widget = QDockWidget(self) self.log_dock_widget.hide() self.log_dock_widget.setWindowTitle("Log") self.log_dock_widget.setWidget(self.log_widget) self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock_widget) self.assetb_widget = AssetBrowser() self.assetb_dock_widget = QDockWidget(self) self.assetb_dock_widget.hide() self.assetb_dock_widget.setWindowTitle("Asset browser") self.assetb_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.assetb_dock_widget.setWidget(self.assetb_widget) self.addDockWidget(Qt.LeftDockWidgetArea, self.assetb_dock_widget) self.recorded_event_widget = RecordEventWidget(api=self.api) self.recorded_event_dock_widget = QDockWidget(self) self.recorded_event_dock_widget.setWindowTitle("Recorded events") self.recorded_event_dock_widget.hide() self.recorded_event_dock_widget.setFeatures(QDockWidget.AllDockWidgetFeatures) self.recorded_event_dock_widget.setWidget(self.recorded_event_widget) self.addDockWidget(Qt.RightDockWidgetArea, self.recorded_event_dock_widget) #TODO bug #114 workaround. Disable create sub engine... if platform.system().lower() != 'darwin': self.ogl_widget = CetechWidget(self, self.api) self.ogl_dock = QDockWidget(self) self.ogl_dock.hide() self.ogl_dock.setWidget(self.ogl_widget) self.addDockWidget(Qt.TopDockWidgetArea, self.ogl_dock) self.tabifyDockWidget(self.assetb_dock_widget, self.log_dock_widget) self.assetb_widget.asset_clicked.connect(self.open_asset) self.file_watch = QFileSystemWatcher(self) self.file_watch.fileChanged.connect(self.file_changed) self.file_watch.directoryChanged.connect(self.dir_changed) self.build_file_watch = QFileSystemWatcher(self) self.build_file_watch.fileChanged.connect(self.build_file_changed) self.build_file_watch.directoryChanged.connect(self.build_dir_changed) def open_asset(self, path, ext): if self.script_editor_widget.support_ext(ext): self.script_editor_widget.open_file(path) self.script_editor_dock_widget.show() self.script_editor_dock_widget.focusWidget() def open_project(self, name, dir): self.project.open_project(name, dir) # self.project.run_cetech(build_type=CetechProject.BUILD_DEBUG, compile=True, continu=True, daemon=True) if platform.system().lower() == 'darwin': wid = None else: wid = self.ogl_widget.winId() self.project.run_cetech(build_type=CetechProject.BUILD_DEBUG, compile_=True, continue_=True, wid=wid) self.api.start(QThread.LowPriority) self.assetb_widget.open_project(self.project.project_dir) self.assetb_dock_widget.show() self.log_dock_widget.show() #TODO bug #114 workaround. Disable create sub engine... if platform.system().lower() != 'darwin': self.ogl_dock.show() self.watch_project_dir() def watch_project_dir(self): files = self.file_watch.files() directories = self.file_watch.directories() if len(files): self.file_watch.removePaths(files) if len(directories): self.file_watch.removePaths(directories) files = self.build_file_watch.files() directories = self.build_file_watch.directories() if len(files): self.build_file_watch.removePaths(files) if len(directories): self.build_file_watch.removePaths(directories) files = [] it = QDirIterator(self.project.source_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.file_watch.addPaths(files) files = [] it = QDirIterator(self.project.build_dir, QDirIterator.Subdirectories) while it.hasNext(): files.append(it.next()) self.build_file_watch.addPaths(files) def file_changed(self, path): self.api.compile_all() def dir_changed(self, path): self.watch_project_dir() def build_file_changed(self, path): self.api.autocomplete_list() def build_dir_changed(self, path): pass def open_script_editor(self): self.script_editor_dock_widget.show() def open_recorded_events(self): self.recorded_event_dock_widget.show() def closeEvent(self, evnt): self.api.disconnect() self.project.killall_process() self.statusbar.showMessage("Disconnecting ...") while self.api.connected: self.api.tick() self.statusbar.showMessage("Disconnected") evnt.accept()
class MainWindow(QMainWindow): OnlineHelpUrl = QUrl("https://eth-cscs.github.io/serialbox2/sdb.html") def __init__(self): super().__init__() Logger.info("Setup main window") self.__input_serializer_data = SerializerData("Input Serializer") self.__input_stencil_data = StencilData(self.__input_serializer_data) self.__reference_serializer_data = SerializerData( "Reference Serializer") self.__reference_stencil_data = StencilData( self.__reference_serializer_data) self.__stencil_field_mapper = StencilFieldMapper( self.__input_stencil_data, self.__reference_stencil_data, GlobalConfig()["async"]) self.__file_system_watcher = QFileSystemWatcher() self.__file_system_watcher.directoryChanged[str].connect( self.popup_reload_box) self.__file_system_watcher_last_modify = time() # Load from session? self.__session_manager = SessionManager() if GlobalConfig()["default_session"]: self.__session_manager.load_from_file() self.__session_manager.set_serializer_data( self.__input_serializer_data) self.__session_manager.set_serializer_data( self.__reference_serializer_data) # Setup GUI self.setWindowTitle('sdb - stencil debugger (%s)' % Version().sdb_version()) self.resize(1200, 600) if GlobalConfig()["center_window"]: self.center() if GlobalConfig()["move_window"]: self.move(GlobalConfig()["move_window"]) self.setWindowIcon(Icon("logo-small.png")) self.init_menu_tool_bar() # Tabs self.__tab_highest_valid_state = TabState.Setup self.__widget_tab = QTabWidget(self) # Setup tab self.__widget_tab.addTab( SetupWindow(self, self.__input_serializer_data, self.__reference_serializer_data), "Setup") # Stencil tab self.__widget_tab.addTab( StencilWindow(self, self.__stencil_field_mapper, self.__input_stencil_data, self.__reference_stencil_data), "Stencil") # Result tab self.__widget_tab.addTab( ResultWindow(self, self.__widget_tab.widget(TabState.Stencil.value), self.__stencil_field_mapper), "Result") # Error tab self.__widget_tab.addTab(ErrorWindow(self), "Error") self.__widget_tab.currentChanged.connect(self.switch_to_tab) self.__widget_tab.setTabEnabled(TabState.Setup.value, True) self.__widget_tab.setTabEnabled(TabState.Stencil.value, False) self.__widget_tab.setTabEnabled(TabState.Result.value, False) self.__widget_tab.setTabEnabled(TabState.Error.value, False) self.__widget_tab.setTabToolTip(TabState.Setup.value, "Setup Input and Refrence Serializer") self.__widget_tab.setTabToolTip( TabState.Stencil.value, "Set the stencil to compare and define the mapping of the fields") self.__widget_tab.setTabToolTip(TabState.Result.value, "View to comparison result") self.__widget_tab.setTabToolTip( TabState.Error.value, "Detailed error desscription of the current field") self.__tab_current_state = TabState.Setup self.set_tab_highest_valid_state(TabState.Setup) self.switch_to_tab(TabState.Setup) self.setCentralWidget(self.__widget_tab) # If the MainWindow is closed, kill all popup windows self.setAttribute(Qt.WA_DeleteOnClose) Logger.info("Starting main loop") self.show() def init_menu_tool_bar(self): Logger.info("Setup menu toolbar") action_exit = QAction("Exit", self) action_exit.setShortcut("Ctrl+Q") action_exit.setStatusTip("Exit the application") action_exit.triggered.connect(self.close) action_about = QAction("&About", self) action_about.setStatusTip("Show the application's About box") action_about.triggered.connect(self.popup_about_box) action_save_session = QAction(Icon("filesave.png"), "&Save", self) action_save_session.setStatusTip("Save current session") action_save_session.setShortcut("Ctrl+S") action_save_session.triggered.connect(self.save_session) action_open_session = QAction(Icon("fileopen.png"), "&Open", self) action_open_session.setShortcut("Ctrl+O") action_open_session.setStatusTip("Open session") action_open_session.triggered.connect(self.open_session) action_help = QAction(Icon("help.png"), "&Online Help", self) action_help.setStatusTip("Online Help") action_help.setToolTip("Online Help") action_help.triggered.connect(self.go_to_online_help) self.__action_continue = QAction(Icon("next_cursor.png"), "Continue", self) self.__action_continue.setStatusTip("Continue to next tab") self.__action_continue.triggered.connect(self.switch_to_next_tab) self.__action_continue.setEnabled(True) self.__action_back = QAction(Icon("prev_cursor.png"), "Back", self) self.__action_back.setStatusTip("Back to previous tab") self.__action_back.triggered.connect(self.switch_to_previous_tab) self.__action_back.setEnabled(False) self.__action_reload = QAction(Icon("step_in.png"), "Reload", self) self.__action_reload.setStatusTip( "Reload Input and Reference Serializer") self.__action_reload.setShortcut("Ctrl+R") self.__action_reload.triggered.connect(self.reload_serializer) self.__action_reload.setEnabled(False) self.__action_try_switch_to_error_tab = QAction( Icon("visualize.png"), "Detailed error description", self) self.__action_try_switch_to_error_tab.setStatusTip( "Detailed error desscription of the current field") self.__action_try_switch_to_error_tab.triggered.connect( self.try_switch_to_error_tab) self.__action_try_switch_to_error_tab.setEnabled(False) menubar = self.menuBar() menubar.setNativeMenuBar(False) self.statusBar() file_menu = menubar.addMenu('&File') file_menu.addAction(action_open_session) file_menu.addAction(action_save_session) file_menu.addAction(action_exit) edit_menu = menubar.addMenu('&Edit') edit_menu.addAction(self.__action_back) edit_menu.addAction(self.__action_continue) edit_menu.addAction(self.__action_reload) help_menu = menubar.addMenu('&Help') help_menu.addAction(action_about) help_menu.addAction(action_help) toolbar = self.addToolBar("Toolbar") toolbar.addAction(action_help) toolbar.addAction(action_open_session) toolbar.addAction(action_save_session) toolbar.addAction(self.__action_back) toolbar.addAction(self.__action_continue) toolbar.addAction(self.__action_reload) toolbar.addAction(self.__action_try_switch_to_error_tab) def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def closeEvent(self, event): self.__session_manager.update_serializer_data( self.__input_serializer_data) self.__session_manager.update_serializer_data( self.__reference_serializer_data) if GlobalConfig()["default_session"]: self.__session_manager.store_to_file() # ===----------------------------------------------------------------------------------------=== # TabWidgets # ==-----------------------------------------------------------------------------------------=== def tab_widget(self, idx): return self.__widget_tab.widget( idx if not isinstance(idx, TabState) else idx.value) def switch_to_tab(self, tab): idx = tab.value if isinstance(tab, TabState) else tab if self.__tab_current_state == TabState(idx): return Logger.info("Switching to %s tab" % TabState(idx).name) self.__tab_current_state = TabState(idx) self.__widget_tab.setCurrentIndex(idx) self.tab_widget(idx).make_update() self.__action_try_switch_to_error_tab.setEnabled( TabState(idx) == TabState.Result) # Error tab is always disabled if not in "Error" self.__widget_tab.setTabEnabled(TabState.Error.value, TabState(idx) == TabState.Error) # First tab if idx == 0: self.__action_continue.setEnabled(True) self.__action_back.setEnabled(False) # Last tab elif idx == self.__widget_tab.count() - 1: self.__action_continue.setEnabled(False) self.__action_back.setEnabled(True) # Middle tab else: self.__action_continue.setEnabled(True) self.__action_back.setEnabled(True) def set_tab_highest_valid_state(self, state): """Set the state at which the data is valid i.e everything <= self.valid_tab_state is valid """ self.__tab_highest_valid_state = state self.enable_tabs_according_to_tab_highest_valid_state() def enable_tabs_according_to_tab_highest_valid_state(self): """Enable/Disable tabs according to self.__tab_highest_valid_state """ if self.__tab_highest_valid_state == TabState.Setup: self.__widget_tab.setTabEnabled(TabState.Setup.value, True) self.__widget_tab.setTabEnabled(TabState.Stencil.value, False) self.__widget_tab.setTabEnabled(TabState.Result.value, False) self.__widget_tab.setTabEnabled(TabState.Error.value, False) self.__action_try_switch_to_error_tab.setEnabled(False) watched_directories = self.__file_system_watcher.directories() if watched_directories: self.__file_system_watcher.removePaths( self.__file_system_watcher.directories()) elif self.__tab_highest_valid_state == TabState.Stencil: self.__file_system_watcher.addPath( self.__input_serializer_data.serializer.directory) self.__file_system_watcher.addPath( self.__reference_stencil_data.serializer.directory) self.__widget_tab.setTabEnabled(TabState.Setup.value, True) self.__widget_tab.setTabEnabled(TabState.Stencil.value, True) self.__widget_tab.setTabEnabled(TabState.Result.value, False) self.__widget_tab.setTabEnabled(TabState.Error.value, False) self.__widget_tab.widget( TabState.Stencil.value).initial_field_match() self.__action_reload.setEnabled(True) self.__action_try_switch_to_error_tab.setEnabled(False) elif self.__tab_highest_valid_state == TabState.Result: self.__widget_tab.setTabEnabled(TabState.Setup.value, True) self.__widget_tab.setTabEnabled(TabState.Stencil.value, True) self.__widget_tab.setTabEnabled(TabState.Result.value, True) self.__widget_tab.setTabEnabled(TabState.Error.value, True) self.__action_try_switch_to_error_tab.setEnabled(True) elif self.__tab_highest_valid_state == TabState.Error: self.__widget_tab.setTabEnabled(TabState.Setup.value, True) self.__widget_tab.setTabEnabled(TabState.Stencil.value, True) self.__widget_tab.setTabEnabled(TabState.Result.value, True) self.__action_try_switch_to_error_tab.setEnabled(False) def switch_to_next_tab(self): self.__widget_tab.currentWidget().make_continue() def switch_to_previous_tab(self): self.__widget_tab.currentWidget().make_back() def try_switch_to_error_tab(self): if self.__widget_tab.widget( TabState.Result.value).try_switch_to_error_tab(): self.__widget_tab.setTabEnabled(TabState.Error.value, True) def error_window_set_result_data(self, result_data): self.__widget_tab.widget( TabState.Error.value).set_result_data(result_data) # ===----------------------------------------------------------------------------------------=== # PopupWidgets # ==-----------------------------------------------------------------------------------------=== def popup_about_box(self): self.__about_widget = PopupAboutWidget(self) def popup_error_box(self, msg): Logger.error( msg.replace("<b>", "").replace("</b>", "").replace("<br />", ":").replace("<br/>", ":")) msg_box = QMessageBox() msg_box.setWindowTitle("Error") msg_box.setIcon(QMessageBox.Critical) msg_box.setText(msg) msg_box.setStandardButtons(QMessageBox.Ok) reply = msg_box.exec_() # Blocking def popup_reload_box(self, path): self.__file_system_watcher.blockSignals(True) reply = QMessageBox.question( self, "Reload serializer?", "The path \"%s\" has changed.\nDo want to reload the serializers?" % path, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) if reply == QMessageBox.Yes: self.reload_serializer() self.__file_system_watcher.blockSignals(False) # ===----------------------------------------------------------------------------------------=== # Session manager # ==-----------------------------------------------------------------------------------------=== def save_session(self): Logger.info("Try saving current session") dialog = QFileDialog(self, "Save current session") dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setDefaultSuffix("json") dialog.setDirectory(getcwd()) if not dialog.exec_(): Logger.info("Abort saving current session") return filename = dialog.selectedFiles() self.__session_manager.update_serializer_data( self.__input_serializer_data) self.__session_manager.update_serializer_data( self.__reference_serializer_data) ret, msglist = self.__session_manager.store_to_file(filename[0]) if not ret: self.popup_error_box("Failed to save configuration file: %s\n%s " % (filename[0], msglist[0])) def open_session(self): Logger.info("Try opening session") filename = QFileDialog.getOpenFileName( self, "Open Session", getcwd(), "JSON configuration (*.json)")[0] if filename is None or filename is "": Logger.info("Abort opening session") return ret, msglist = self.__session_manager.load_from_file(filename) if not ret: self.popup_error_box("Failed to load configuration file: %s\n%s " % (filename, msglist[0])) else: Logger.info("Successfully opened session") self.__session_manager.set_serializer_data( self.__input_serializer_data) self.__session_manager.set_serializer_data( self.__reference_serializer_data) self.switch_to_tab(TabState.Setup) @property def session_manager(self): return self.__session_manager # ===----------------------------------------------------------------------------------------=== # Reload Serializer # ==-----------------------------------------------------------------------------------------=== def reload_serializer(self): Logger.info("Reloading serializers") try: self.__input_serializer_data.reload() self.__reference_serializer_data.reload() if self.__widget_tab.currentIndex() == TabState.Error.value: self.switch_to_tab(TabState.Result) self.__widget_tab.currentWidget().make_update() except RuntimeError as e: self.popup_error_box(str(e)) self.set_tab_highest_valid_state(TabState.Setup) self.switch_to_tab(TabState.Setup) self.__widget_tab.currentWidget().make_update() # ===----------------------------------------------------------------------------------------=== # Online help # ==-----------------------------------------------------------------------------------------=== def go_to_online_help(self): Logger.info("Opening online help") QDesktopServices.openUrl(MainWindow.OnlineHelpUrl)