Exemple #1
0
class MainWatcher:
    def __init__(self):
        self.watcher = QFileSystemWatcher()
        self.folders = []
        self.files = []

    def connect(self):
        self.watcher.directoryChanged.connect(self.directory_changed)

    def add_paths(self, list_of_paths):
        self.watcher.addPaths(list_of_paths)

    def directory_changed(self, path):
        for f in listdir(path):
            complete_path = join(path, f)
            sub_folder = self.map_path_to_folder_id(path)
            if not exists(complete_path):
                continue
            if complete_path in self.folders or complete_path in self.files:
                continue
            if isfile(complete_path):
                fw = FileWatcher()
                fw.subfolder = sub_folder
                fw.base_path = complete_path
                fw.connect()
                fw.finished.connect(self.copied_file)
                self.files.append(fw)
            else:
                fw = SubFolderWatcher()
                fw.base_path = complete_path
                fw.subfolder = sub_folder
                fw.connect()
                fw.finished.connect(self.copied_folder)
                self.folders.append(fw)

    def copied_file(self, path):
        try:
            i = self.files.index(path)
        except IndexError:
            pass
        else:
            del self.files[i]

    def copied_folder(self, path):
        try:
            i = self.folders.index(path)
        except IndexError:
            pass
        else:
            del self.folders[i]

    @staticmethod
    def map_path_to_folder_id(path):
        normalized_path = normpath(path)
        ending = split(normalized_path)[-1]
        return ending
Exemple #2
0
class FileWatcher(object):
    def __init__(self, files=None):
        self._watcher = QFileSystemWatcher()
        self._files = list()  # List of file(s) to watch
        self.isWatching = False
        if files is not None:
            self.addFile(files)

    def test(self):
        file = ['/Users/bitzer/hudat.spec']

        self.addFile(file)

    def addFile(self, files):
        # Add a file(s) to the watch list
        # Files needs to be a list, even if a single file

        # Do it while actively watching?

        for _f in files:
            print(_f)
            self._files.append(_f)

    def removeFile(self):
        # Remove a file
        pass

    def replaceFile(self, file):
        # In the (usual) case of a watching a single file, replace it
        if len(self._files) != 1:
            print('Only allowed if one file is currently watched')

        self._files[0] = file

    def startWatch(self):
        # Start watching the files

        self._watcher.addPaths(self._files)

        self._watcher.fileChanged.connect(self.onChange)
        self.isWatching = True

    def stopWatch(self):
        # Stop Watching the folder

        self._watcher.removePaths(self._files)

        self._watcher.fileChanged.disconnect(self.onChange)

        self.isWatching = False

    def onChange(self, file):
        # When a file changes, do something
        # Which file changed?
        print('changed ' + file)
Exemple #3
0
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")
Exemple #4
0
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()
Exemple #5
0
class SubFolderWatcher(QObject):
    finished = pyqtSignal(str, int)

    def __init__(self):
        super(SubFolderWatcher, self).__init__()
        self.base_path = ''
        self.subfolder = ''
        self.files = {}
        self.finished_checking = False
        self.watcher = QFileSystemWatcher()
        self.timer = QTimer()
        self.check_threshold = 3
        self.check_number = 0
        self.sub_folder = ''

    def connect(self):
        self.watcher.addPaths([self.base_path])
        self.watcher.directoryChanged.connect(
            lambda y: self.directory_changed(y))
        self.watcher.fileChanged.connect(lambda x: self.file_changed(x))
        self.timer.timeout.connect(self.check_files)
        self.timer.start(timer_delay * 1000)

    def check_files(self):
        finished = True
        if exists(self.base_path):
            for sub_path in Path(self.base_path).rglob('*'):
                new_st_mtime = int(stat(sub_path).st_mtime)
                path_name = normpath(sub_path)
                if path_name not in self.files:
                    self.files[path_name] = new_st_mtime
                if self.files[path_name] != new_st_mtime:
                    finished = False
                self.files[path_name] = new_st_mtime
            if finished:
                self.check_number += 1
            if self.check_number > self.check_threshold:
                self.timer.stop()
                self.watcher.removePath(self.base_path)
                self.copy_folder()
        else:
            self.finished_checking = True

    def copy_file(self):
        pass

    def copy_folder(self):
        try:
            shutil.move(self.base_path, join(home, self.subfolder))
        except shutil.Error:
            remove(self.base_path)
        else:
            pass
            """
            NOTE:
            On my home system, I sometimes use the following code to change groups and owner permissions of the
            copied files. 
            I would personally not use this in a production environment, especially with the 775 permissions. 
            The following code doesn't make sense to run on Windows, so you can ignore if you're on that system.
            
            local = join(home, self.subfolder)
            chmod(local, 0o775)
            shutil.chown(local, 'grp', 'usr')
            for root, dirs, files in walk(local):
                chmod(root, 0o775)
                shutil.chown(root, 'grp', 'usr')
                for file_name in files:
                    chmod(join(root, file_name), 0o775)
                    shutil.chown(join(root, file_name), 'grp', 'usr')
            """
        self.finished_checking = True
        self.finished.emit(self.base_path)

    @pyqtSlot(str)
    def directory_changed(self, path):
        if self.timer.isActive():
            self.timer.stop()
            self.timer.start(timer_delay * 1000)
        for sub_path in Path(path).rglob('*'):
            if isfile(sub_path):
                path_name = normpath(join(path, sub_path.name))
                self.files[path_name] = int(stat(self.base_path).st_mtime)

    def file_changed(self):
        pass

    def __eq__(self, other):
        try:
            return other.base_path == self.base_path
        except AttributeError:
            return other == self.base_path
Exemple #6
0
class Editor(CodeEditor, ComponentMixin):

    name = 'Code Editor'

    # This signal is emitted whenever the currently-open file changes and
    # autoreload is enabled.
    triggerRerender = pyqtSignal(bool)
    sigFilenameChanged = pyqtSignal(str)

    preferences = Parameter.create(name='Preferences',
                                   children=[{
                                       'name': 'Font size',
                                       'type': 'int',
                                       'value': 12
                                   }, {
                                       'name': 'Autoreload',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name': 'Autoreload delay',
                                       'type': 'int',
                                       'value': 50
                                   }, {
                                       'name':
                                       'Autoreload: watch imported modules',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name': 'Line wrap',
                                       'type': 'bool',
                                       'value': False
                                   }, {
                                       'name':
                                       'Color scheme',
                                       'type':
                                       'list',
                                       'values':
                                       ['Spyder', 'Monokai', 'Zenburn'],
                                       'value':
                                       'Spyder'
                                   }])

    EXTENSIONS = 'py'

    def __init__(self, parent=None):

        self._watched_file = None

        super(Editor, self).__init__(parent)
        ComponentMixin.__init__(self)

        self.setup_editor(linenumbers=True,
                          markers=True,
                          edge_line=False,
                          tab_mode=False,
                          show_blanks=True,
                          font=QFontDatabase.systemFont(
                              QFontDatabase.FixedFont),
                          language='Python',
                          filename='')

        self._actions =  \
                {'File' : [QAction(icon('new'),
                                  'New',
                                  self,
                                  shortcut='ctrl+N',
                                  triggered=self.new),
                          QAction(icon('open'),
                                  'Open',
                                  self,
                                  shortcut='ctrl+O',
                                  triggered=self.open),
                          QAction(icon('save'),
                                  'Save',
                                  self,
                                  shortcut='ctrl+S',
                                  triggered=self.save),
                          QAction(icon('save_as'),
                                  'Save as',
                                  self,
                                  shortcut='ctrl+shift+S',
                                  triggered=self.save_as),
                          QAction(icon('autoreload'),
                                  'Automatic reload and preview',
                                  self,triggered=self.autoreload,
                                  checkable=True,
                                  checked=False,
                                  objectName='autoreload'),
                          ]}

        for a in self._actions.values():
            self.addActions(a)

        self._fixContextMenu()

        # autoreload support
        self._file_watcher = QFileSystemWatcher(self)
        # we wait for 50ms after a file change for the file to be written completely
        self._file_watch_timer = QTimer(self)
        self._file_watch_timer.setInterval(
            self.preferences['Autoreload delay'])
        self._file_watch_timer.setSingleShot(True)
        self._file_watcher.fileChanged.connect(
            lambda val: self._file_watch_timer.start())
        self._file_watch_timer.timeout.connect(self._file_changed)

        self.updatePreferences()

    def _fixContextMenu(self):

        menu = self.menu

        menu.removeAction(self.run_cell_action)
        menu.removeAction(self.run_cell_and_advance_action)
        menu.removeAction(self.run_selection_action)
        menu.removeAction(self.re_run_last_cell_action)

    def updatePreferences(self, *args):

        self.set_color_scheme(self.preferences['Color scheme'])

        font = self.font()
        font.setPointSize(self.preferences['Font size'])
        self.set_font(font)

        self.findChild(QAction, 'autoreload') \
            .setChecked(self.preferences['Autoreload'])

        self._file_watch_timer.setInterval(
            self.preferences['Autoreload delay'])

        self.toggle_wrap_mode(self.preferences['Line wrap'])

        self._clear_watched_paths()
        self._watch_paths()

    def confirm_discard(self):

        if self.modified:
            rv = confirm(
                self, 'Please confirm',
                'Current document is not saved - do you want to continue?')
        else:
            rv = True

        return rv

    def new(self):

        if not self.confirm_discard(): return

        self.set_text('')
        self.filename = ''
        self.reset_modified()

    def open(self):

        if not self.confirm_discard(): return

        curr_dir = Path(self.filename).abspath().dirname()
        fname = get_open_filename(self.EXTENSIONS, curr_dir)
        if fname != '':
            self.load_from_file(fname)

    def load_from_file(self, fname):

        self.set_text_from_file(fname)
        self.filename = fname
        self.reset_modified()

    def save(self):

        if self._filename != '':

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(True)
                self._file_watch_timer.stop()

            with open(self._filename, 'w') as f:
                f.write(self.toPlainText())

            if self.preferences['Autoreload']:
                self._file_watcher.blockSignals(False)
                self.triggerRerender.emit(True)

            self.reset_modified()

        else:
            self.save_as()

    def save_as(self):

        fname = get_save_filename(self.EXTENSIONS)
        if fname != '':
            with open(fname, 'w') as f:
                f.write(self.toPlainText())
                self.filename = fname

            self.reset_modified()

    def _update_filewatcher(self):
        if self._watched_file and (self._watched_file != self.filename
                                   or not self.preferences['Autoreload']):
            self._clear_watched_paths()
            self._watched_file = None
        if self.preferences[
                'Autoreload'] and self.filename and self.filename != self._watched_file:
            self._watched_file = self._filename
            self._watch_paths()

    @property
    def filename(self):
        return self._filename

    @filename.setter
    def filename(self, fname):
        self._filename = fname
        self._update_filewatcher()
        self.sigFilenameChanged.emit(fname)

    def _clear_watched_paths(self):
        paths = self._file_watcher.files()
        if paths:
            self._file_watcher.removePaths(paths)

    def _watch_paths(self):
        if Path(self._filename).exists():
            self._file_watcher.addPath(self._filename)
            if self.preferences['Autoreload: watch imported modules']:
                module_paths = self.get_imported_module_paths(self._filename)
                if module_paths:
                    self._file_watcher.addPaths(module_paths)

    # callback triggered by QFileSystemWatcher
    def _file_changed(self):
        # neovim writes a file by removing it first so must re-add each time
        self._watch_paths()
        self.set_text_from_file(self._filename)
        self.triggerRerender.emit(True)

    # Turn autoreload on/off.
    def autoreload(self, enabled):
        self.preferences['Autoreload'] = enabled
        self._update_filewatcher()

    def reset_modified(self):

        self.document().setModified(False)

    @property
    def modified(self):

        return self.document().isModified()

    def saveComponentState(self, store):

        if self.filename != '':
            store.setValue(self.name + '/state', self.filename)

    def restoreComponentState(self, store):

        filename = store.value(self.name + '/state')

        if filename and self.filename == '':
            try:
                self.load_from_file(filename)
            except IOError:
                self._logger.warning(f'could not open {filename}')

    def get_imported_module_paths(self, module_path):

        finder = ModuleFinder([os.path.dirname(module_path)])
        imported_modules = []

        try:
            finder.run_script(module_path)
        except SyntaxError as err:
            self._logger.warning(f'Syntax error in {module_path}: {err}')
        except Exception as err:
            self._logger.warning(
                f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}'
            )
        else:
            for module_name, module in finder.modules.items():
                if module_name != '__main__':
                    path = getattr(module, '__file__', None)
                    if path is not None and os.path.isfile(path):
                        imported_modules.append(path)

        return imported_modules
Exemple #7
0
class Window(QMainWindow, Ui_MainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.pbConnect.setProperty("css", True)
        self.pageHost.setMain(self)
        self.pageHost.connectPressed.connect(self.onConnectHost)
        self.pageHost.cancelPressed.connect(self.showPage)
        self.pageLogin.setMain(self)
        self.pageLogin.connectPressed.connect(self.onConnectLogin)
        self.pageLogin.cancelPressed.connect(self.showPage)
        self.pageAbout.setMain(self)
        self.pageAbout.okPressed.connect(self.showPage)

        self.loaded = False
        self.setWindowTitle('Gold Drive')
        app_icon = QIcon()
        app_icon.addFile(':/images/icon_16.png', QSize(16, 16))
        app_icon.addFile(':/images/icon_32.png', QSize(32, 32))
        app_icon.addFile(':/images/icon_64.png', QSize(64, 64))
        app_icon.addFile(':/images/icon_128.png', QSize(128, 128))
        QApplication.setWindowIcon(app_icon)
        stream = QFile(':/style/style.css')
        if stream.open(QIODevice.ReadOnly | QFile.Text):
            self.setStyleSheet(QTextStream(stream).readAll())

        # initial values from settings
        self.settings = QSettings("sganis", "golddrive")
        if self.settings.value("geometry"):
            self.restoreGeometry(self.settings.value("geometry"))
            self.restoreState(self.settings.value("windowState"))
        self.progressBar.setVisible(False)
        self.lblMessage.setText("")
        # self.widgetPassword.setVisible(False)
        self.returncode = util.ReturnCode.NONE

        # read config.json
        self.configfile = fr'{util.DIR}\..\config.json'
        self.param = {}

        self.config = util.load_config(self.configfile)
        # path = os.environ['PATH']
        # sshfs_path = self.config.get('sshfs_path','')
        # sanfs_path = self.config.get('sanfs_path','')
        # os.environ['PATH'] = fr'{sanfs_path};{sshfs_path};{path}'

        self.updateCombo(self.settings.value("cboParam", 0))
        self.fillParam()
        self.lblUserHostPort.setText(self.param['userhostport'])

        menu = Menu(self.pbHamburger)
        menu.addAction('&Connect', self.mnuConnect)
        menu.addAction('&Disconnect', self.mnuDisconnect)
        menu.addAction('Disconnect &all drives', self.mnuDisconnectAll)
        menu.addAction('&Open program location', self.mnuOpenProgramLocation)
        menu.addAction('Open &terminal', self.mnuOpenTerminal)
        menu.addAction('Open &log file', self.mnuOpenLogFile)
        menu.addAction('&Restart Explorer.exe', self.mnuRestartExplorer)
        menu.addAction('&About...', self.mnuAbout)
        self.pbHamburger.setMenu(menu)

        # worker for commands
        self.worker = Worker()
        self.worker.workDone.connect(self.onWorkDone)

        # worker for update
        self.updater = Updater(self.config["update_url"])
        self.updater.checkDone.connect(self.onCheckUpdateDone)
        self.updater.doCheck()

        # worker for watching config.json changes
        self.watcher = QFileSystemWatcher()
        self.watcher.addPaths([self.configfile])
        self.watcher.fileChanged.connect(self.onConfigFileChanged)

        self.checkDriveStatus()
        self.showPage(util.Page.MAIN)
        self.lblMessage.linkActivated.connect(self.on_lblMessage_linkActivated)
        self.loaded = True

    def start(self, message):
        self.showPage(util.Page.MAIN)
        self.returncode = util.ReturnCode.NONE
        self.pbConnect.setEnabled(False)
        self.progressBar.setVisible(True)
        self.showMessage(message)

    def end(self, message=''):
        self.showPage(util.Page.MAIN)
        self.progressBar.setVisible(False)
        self.pbConnect.setEnabled(True)
        self.showMessage(message)

    def showMessage(self, message):
        is_error = (self.returncode != util.ReturnCode.OK
                    and self.returncode != util.ReturnCode.NONE)
        self.lblMessage.setText(message)
        self.lblMessage.setProperty("error", is_error)
        self.lblMessage.style().unpolish(self.lblMessage)
        self.lblMessage.style().polish(self.lblMessage)

    def setPbConnectText(self, status):
        self.pbConnect.setText('&CONNECT')
        if status == 'CONNECTED':
            self.pbConnect.setText('&DISCONNECT')
        elif status == 'BROKEN':
            self.pbConnect.setText('&REPAIR')
        elif status == 'IN USE':
            self.pbConnect.setEnabled(False)

    @pyqtSlot(str, util.ReturnBox)
    def onWorkDone(self, task, rb):

        self.returncode = rb.returncode

        if task == 'check_drive':
            if rb.drive_status == 'CONNECTED':
                drive = self.param['drive']
                link = util.make_hyperlink('open_drive', 'Open')
                msg = util.rich_text(f"{rb.drive_status}\n\n{link}")
            else:
                msg = rb.drive_status
                if rb.error:
                    msg = rb.error
            self.end(msg)

        elif task == 'connect':

            if rb.returncode == util.ReturnCode.BAD_DRIVE:
                self.end(rb.error)

            elif rb.returncode == util.ReturnCode.BAD_HOST:
                self.showPage(util.Page.HOST)
                self.pageHost.showError(rb.error)
                self.progressBar.setVisible(False)

            elif rb.returncode == util.ReturnCode.BAD_LOGIN:
                self.showPage(util.Page.LOGIN)
                self.pageLogin.showError(rb.error)
                self.progressBar.setVisible(False)

            elif rb.returncode == util.ReturnCode.BAD_MOUNT:
                self.end(rb.error)

            elif rb.returncode == util.ReturnCode.BAD_WINFSP:
                msg = (
                    f"WinFSP is not installed\n\n{ util.make_hyperlink('install_winfsp', 'Install') }"
                )
                self.end(util.rich_text(msg))

            else:
                # print(rb.returncode)
                assert rb.returncode == util.ReturnCode.OK
                msg = (
                    f"CONNECTED\n\n{ util.make_hyperlink('open_drive', 'Open') }"
                )
                self.end(util.rich_text(msg))

        elif task == 'connect_login':

            if not rb.returncode == util.ReturnCode.OK:
                self.pageLogin.showError(rb.error)
                self.progressBar.setVisible(False)

            elif rb.drive_status == 'CONNECTED':
                link = util.make_hyperlink('open_drive', 'Open')
                msg = util.rich_text(f"{rb.drive_status}\n\n{link}")

                self.end(msg)
            else:
                msg = rb.drive_status
                if rb.error:
                    msg = rb.error
                self.end(msg)

        elif (task == 'disconnect' or task == 'repair'):
            self.end(rb.drive_status)

        elif task == 'restart_explorer':
            link = util.make_hyperlink('open_drive', 'Open')
            msg = util.rich_text(f"{rb.output}\n\n{link}")
            self.end(msg)

        else:
            msg = rb.output
            if rb.error:
                msg = rb.error
            self.end(msg)
        self.setPbConnectText(rb.drive_status)

    @pyqtSlot(bool)
    def onCheckUpdateDone(self, result):
        logger.info('Check update done')
        if result:
            link = util.make_hyperlink('update', 'Update and re-launch now')
            msg = util.rich_text(f"New version available\n\n{link}")
            self.end(msg)

    def onConfigFileChanged(self, path):
        logger.info('Config file has changed, reloading...')
        self.config = util.load_config(path)
        self.updateCombo(self.settings.value("cboParam", 0))
        self.fillParam()
        self.lblUserHostPort.setText(self.param['userhostport'])
        self.checkDriveStatus()

    def updateCombo(self, currentIndex):
        items = []
        drives = self.config.get('drives', '')
        for d in drives:
            items.append(f"   {d}   {drives[d].get('drivename','GOLDDRIVE')}")
        self.cboParam.blockSignals(True)
        self.cboParam.clear()
        self.cboParam.addItems(items)
        if currentIndex > len(items) - 1:
            currentIndex = 0
        self.cboParam.setCurrentIndex(currentIndex)
        self.cboParam.blockSignals(False)

    def fillParam(self):
        p = self.param
        p['editor'] = self.config.get('editor')
        p['logfile'] = self.config.get('logfile')
        p['configfile'] = self.configfile
        p['user'] = getpass.getuser()
        p['appkey'] = util.get_app_key(p['user'])
        p['userhostport'] = ''
        default_port = 22
        p['port'] = default_port
        p['drive'] = ''
        p['drivename'] = 'GOLDDRIVE'
        drives = self.config.get('drives')
        p['drives'] = drives
        p['no_host'] = not bool(drives)
        if not drives:
            return
        currentText = self.cboParam.currentText()
        if not currentText:
            return
        drive = currentText.split()[0].strip()
        d = drives[drive]
        p['drive'] = drive
        p['drivename'] = d.get('drivename', 'GOLDDRIVE').replace(' ', '-')
        p['host'] = d.get('hosts', 'localhost')[0]
        p['port'] = d.get('port', default_port)
        p['user'] = d.get('user', getpass.getuser())
        p['userhost'] = f"{p['user']}@{p['host']}"
        p['userhostport'] = f"{p['userhost']}:{p['port']}"
        p['appkey'] = util.get_app_key(p['user'])
        p['args'] = d.get('args', '')

    # decorator used to trigger only the int overload and not twice
    @pyqtSlot(int)
    def on_cboParam_currentIndexChanged(self, e):
        # print(f'index changed: {e}')
        cbotext = self.cboParam.currentText()
        if not cbotext:
            return
        self.fillParam()
        self.lblUserHostPort.setText(self.param['userhostport'])
        if self.loaded:
            self.settings.setValue("cboParam", self.cboParam.currentIndex())
            self.checkDriveStatus()

    def checkDriveStatus(self):
        if not self.param.get('drive'):
            return
        self.start(f"Checking {self.param['drive']}...")
        self.worker.doWork('check_drive', self.param)

    def onConnectHost(self, drive, userhostport):
        self.progressBar.setVisible(True)
        self.param['drive'] = drive
        userhost = userhostport
        host = userhostport
        self.param['userhost'] = userhost
        self.param['host'] = host
        self.param['port'] = 22
        if ':' in userhostport:
            userhost, port = userhostport.split(':')
            self.param['port'] = port
            self.param['host'] = userhost
            host = userhost
        if '@' in userhost:
            user, host = userhost.split('@')
            self.param['user'] = user
            self.param['host'] = host
        else:
            user = self.param['user']
        self.param['userhost'] = f'{user}@{host}'
        self.param['appkey'] = util.get_app_key(user)

        # print(f"user: {self.param['user']}")
        # print(f"host: {self.param['host']}")
        # print(f"port: {self.param['port']}")
        # print(f"userhost: {self.param['userhost']}")
        self.worker.doWork('connect', self.param)

    def onConnectLogin(self, password):
        self.progressBar.setVisible(True)
        self.param['password'] = password
        self.worker.doWork('connect_login', self.param)

    def mnuConnect(self):
        if self.param['no_host']:
            self.showPage(util.Page.HOST)
        else:
            self.start('Connecting drive...')
            self.worker.doWork('connect', self.param)

    def mnuDisconnect(self):
        self.start('Disconnecting drive...')
        self.worker.doWork('disconnect', self.param)

    def mnuDisconnectAll(self):
        self.start('Disconnecting all...')
        self.worker.doWork('disconnect_all', self.param)

    def mnuOpenProgramLocation(self):
        editor = self.param["editor"]
        if not os.path.exists(editor):
            editor = 'C:\\windows\\notepad.exe'
        cmd = fr'start /b c:\windows\explorer.exe "{util.DIR}\.."'
        util.run(cmd, shell=True)

    def mnuOpenTerminal(self):

        util.run('start %ComSpec%', shell=True)

    def mnuOpenLogFile(self):
        editor = self.param["editor"]
        if not os.path.exists(editor):
            editor = 'C:\\windows\\notepad.exe'
        logfile = self.param['logfile']
        cmd = fr'start /b "" "{editor}" "{logfile}"'
        util.run(cmd, shell=True)

    def mnuRestartExplorer(self):
        self.start('Restarting explorer...')
        self.worker.doWork('restart_explorer', self.param)

    def mnuAbout(self):
        self.showPage(util.Page.ABOUT)
        self.pageAbout.showAbout()

    def closeEvent(self, event):
        self.settings.setValue("geometry", self.saveGeometry())
        self.settings.setValue("windowState", self.saveState())
        QMainWindow.closeEvent(self, event)
        self.worker.stop()
        self.updater.stop()

    def on_pbConnect_released(self):
        task = self.pbConnect.text().lower().replace('&', '')
        if task == 'connect':
            self.mnuConnect()
        else:
            self.mnuDisconnect()

    def on_lblSettings_linkActivated(self, link):
        editor = self.param["editor"]
        if not os.path.exists(editor):
            editor = 'C:\\windows\\notepad.exe'
        cmd = fr'start /b "" "{editor}" "{util.DIR}\..\config.json'
        util.run(cmd, shell=True)

    def on_lblMessage_linkActivated(self, link):

        if link == 'open_drive':
            drive = self.param['drive']
            cmd = fr'start /b c:\windows\explorer.exe {drive}'
            util.run(cmd, shell=True)

        elif link == 'update':
            self.updater.doUpdate()

        elif link == 'install_winfsp':
            cmd = fr'start /b { self.config["winfsp_url"] }'
            util.run(cmd, shell=True)

    def showPage(self, page):
        if page == util.Page.HOST:
            self.pageHost.init()
        if page == util.Page.LOGIN:
            self.pageLogin.init()
        self.setTopEnabled(page == util.Page.MAIN)
        self.stackedWidget.setCurrentIndex(page.value)
        # print(f'panel visible: {page}')

    def setTopEnabled(self, enable):
        self.pbHamburger.setEnabled(enable)
        self.cboParam.setVisible(enable)
        self.cboParam.setEnabled(enable and self.cboParam.count() > 0)
        self.lblSettings.setVisible(enable)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
            # print('enter pressed in app')
            self.on_pbConnect_released()
            event.accept()
Exemple #8
0
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()
Exemple #9
0
class PreviewTab(QWidget):
    """Preview of QML component given by 'source' argument. If any file in the
    source's directory or one of its subdirectories is changed, the view is
    updated unless paused. Potential errors in the QML code are displayed in
    red."""
    def __init__(self, source=None, parent=None, import_paths=None):
        super().__init__(parent=parent)

        self.updating_paused = False

        self.qml_view = QQuickView()

        engine = self.qml_view.engine()
        import_paths = import_paths or []
        for import_path in import_paths:
            engine.addImportPath(import_path)

        # idea from
        # https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
        self.container = QWidget.createWindowContainer(self.qml_view, self)

        self.error_info = QLabel()
        self.error_info.setWordWrap(True)

        self.qml_source = QUrl() if source is None else QUrl(source)
        self.update_source()

        self.pause_button = QPushButton("Pause")
        self.pause_button.setCheckable(True)

        layout = QVBoxLayout()
        layout.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.error_info)
        layout.addWidget(self.container)
        layout.addWidget(self.pause_button)

        self.setLayout(layout)

        # Observations using the QFileSystemWatcher in various settings:
        # A) fileChanged signal and qml file paths
        #   Collected all *.qml files in the directory of the source and all
        #   subdirectories and added to the watcher. Now the first change would
        #   trigger the signal but none of the following
        # B) additionally connecting directoryChanged signal
        #   same issue
        # C) both signals, (sub)directory file paths
        #   Collected all subdirectories of the source directory and added it to
        #   the watcher, along with the source directory itself. Works as
        #   expected
        # D) directoryChanged signal, (sub)directory file paths
        #   same as C) without fileChanged signal, works as expected
        # Eventual solution: D
        # This implementation also helped me:
        # https://github.com/penk/qml-livereload/blob/master/main.cpp
        self.watcher = QFileSystemWatcher()
        source_dir = os.path.dirname(source)
        source_paths = glob.glob(os.path.join(source_dir, "*/"),
                                 recursive=True)
        source_paths.append(source_dir)
        failed_paths = self.watcher.addPaths(source_paths)
        if failed_paths:
            print("Failed to watch paths: {}".format(", ".join(failed_paths)))

        self.pause_button.clicked.connect(self.toggle_updating)
        self.qml_view.statusChanged.connect(self.check_status)
        self.watcher.directoryChanged.connect(self.update_source)

    def toggle_updating(self, clicked):
        """Callback for pause button."""
        self.pause_button.setText("Resume" if clicked else "Pause")
        self.updating_paused = clicked

        # Update when resuming in case of the source having changed
        if not self.updating_paused:
            self.update_source()

    def update_source(self, _=None):
        """Method and callback to update the QML view source. The second
        argument is required to have a slot matching the directoryChanged()
        interface.
        This immediately returns if updating is paused.
        """
        if self.updating_paused:
            return

        # idea from
        # https://stackoverflow.com/questions/17337493/how-to-reload-qml-file-to-qquickview
        self.qml_view.setSource(QUrl())
        self.qml_view.engine().clearComponentCache()
        # avoid error: No such file or directory
        QThread.msleep(50)
        self.qml_view.setSource(self.qml_source)
        self.container.setMinimumSize(self.qml_view.size())
        self.container.setMaximumSize(self.qml_view.size())
        # avoid error label making the window too wide
        self.error_info.setMaximumWidth(self.container.maximumWidth())

    def check_status(self, status):
        if status == QQuickView.Error:
            self.error_info.setText("<font color='red'>{}</font>".format(
                self.qml_view.errors()[-1].toString()))
        else:
            self.error_info.clear()

    def shutdown(self):
        """Shutdown tab to prepare for removal. Manually delete QML related
        members to free resources. Otherwise error messages are printed even
        after closing the tab, indicating that the QML engine still runs in the
        background.
        """
        del self.container
        del self.qml_view
Exemple #10
0
class DatasetWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dataset()
        self.ui.setupUi(self)

        self.all_thumbnails = []
        self.selected_thumbnails: Set[Thumbnail] = set()

        self.ui.image_list_widget.itemSelectionChanged.connect(
            self.on_changed_image_list_selection)
        self.ui.delete_images_button.clicked.connect(
            self.on_clicked_delete_images_button)
        self.ui.train_button.clicked.connect(self.on_clicked_train_button)

        self.ui.camera_and_images_menu = QMenu()
        self.ui.camera_and_images_menu.addAction(self.ui.select_images_action)
        self.ui.camera_and_images_menu.addAction(self.ui.camera_action)
        self.ui.camera_and_images_button.setMenu(
            self.ui.camera_and_images_menu)

        self.ui.select_images_action.triggered.connect(
            self.on_clicked_select_images_button)
        self.ui.camera_action.triggered.connect(self.on_clicked_camera_button)

        self.ui.image_list_widget.setCurrentItem(
            self.ui.image_list_widget.topLevelItem(0).child(
                0))  # FIXME: refactor
        self.ui.image_list_widget.expandAll()

        self._reload_images(Dataset.Category.TRAINING_OK)
        self.__reload_recent_training_date()

        self.capture_dialog: Optional[ImageCaptureDialog] = None

        self.preview_window = PreviewWindow()

        self.watcher = QFileSystemWatcher(self)
        self.watcher.addPaths([
            str(Dataset.images_path(Dataset.Category.TRAINING_OK)),
            str(Dataset.images_path(Dataset.Category.TEST_OK)),
            str(Dataset.images_path(Dataset.Category.TEST_NG))
        ])
        self.watcher.directoryChanged.connect(
            self.on_dataset_directory_changed)

        self.select_area_dialog = None
        self.msgBox = None

        LearningModel.default().training_finished.connect(
            self.on_finished_training)

    def _reload_images(self, category: Dataset.Category):
        # reset selection
        self.selected_thumbnails.clear()
        self.ui.delete_images_button.setEnabled(False)

        # reset grid area contents
        current_images_count = self.ui.images_grid_area.count()
        if current_images_count > 0:
            for i in reversed(range(current_images_count)):
                self.ui.images_grid_area.itemAt(i).widget().setParent(None)

        image_paths = sorted(Dataset.images_path(category).iterdir())
        nullable_thumbnails = [
            Thumbnail(path=image_path) for image_path in image_paths
        ]
        self.all_thumbnails = [
            thumbnail for thumbnail in nullable_thumbnails
            if not thumbnail.pixmap.isNull()
        ]
        self.ui.number_of_images_label.setText(f'{len(self.all_thumbnails)}枚')

        row = 0
        column = 0
        for thumbnail in self.all_thumbnails:
            thumbnail_cell = ThumbnailCell(thumbnail=thumbnail)
            thumbnail_cell.selection_changed.connect(
                self.on_changed_thumbnail_selection)
            thumbnail_cell.double_clicked.connect(
                self.on_double_clicked_thumbnail)
            self.ui.images_grid_area.addWidget(thumbnail_cell, row, column)

            if column == 4:
                row += 1
                column = 0
            else:
                column += 1

    def on_changed_image_list_selection(self):
        selected_category = self.__selected_dataset_category()
        if selected_category is not None:
            self._reload_images(selected_category)

    def on_changed_thumbnail_selection(self, selected: bool,
                                       thumbnail: Thumbnail):
        if selected:
            self.selected_thumbnails.add(thumbnail)
        else:
            self.selected_thumbnails.remove(thumbnail)

        if self.selected_thumbnails:
            number_of_images_description = f'{len(self.all_thumbnails)}枚 - {len(self.selected_thumbnails)}枚選択中'
            self.ui.delete_images_button.setEnabled(True)
        else:
            number_of_images_description = f'{len(self.all_thumbnails)}枚'
            self.ui.delete_images_button.setEnabled(False)
        self.ui.number_of_images_label.setText(number_of_images_description)

    def on_double_clicked_thumbnail(self, thumbnail: Thumbnail):
        self.preview_window.set_thumbnail(thumbnail)
        self.preview_window.show()
        self.preview_window.activateWindow()
        self.preview_window.raise_()

        # move preview to center
        preview_geometry: QRect = self.preview_window.frameGeometry()
        screen_center = QDesktopWidget().availableGeometry().center()
        preview_geometry.moveCenter(screen_center)
        self.preview_window.move(preview_geometry.topLeft())

    def on_clicked_camera_button(self):
        selected_category = self.__selected_dataset_category()
        if selected_category is None:
            print('TODO: disable to select other items')
            return

        del self.capture_dialog
        self.capture_dialog = ImageCaptureDialog(
            image_save_location=str(Dataset.images_path(selected_category)))
        self.capture_dialog.show()

    def on_clicked_select_images_button(self):
        selected_category = self.__selected_dataset_category()
        if selected_category is None:
            print('TODO: disable to select other items')
            return

        ext_filter = '画像ファイル(*.jpg *.jpeg *.png *.gif *.bmp)'
        source_image_names = QFileDialog.getOpenFileNames(
            caption='データセットに取り込む',
            filter=ext_filter,
            directory=Project.latest_dataset_image_path())[0]
        Project.save_latest_dataset_image_path(
            os.path.dirname(source_image_names[0]))
        if source_image_names:
            for source_image_name in source_image_names:
                try:
                    # TODO: specify correct camera number
                    destination = Dataset.generate_image_path(
                        category=selected_category,
                        cam_number=0,
                        file_extension=Path(source_image_name).suffix)
                    shutil.copyfile(source_image_name, destination)
                except shutil.SameFileError:
                    print("TODO: fix destination")

    def on_clicked_delete_images_button(self):
        assert self.selected_thumbnails

        message = f'{len(self.selected_thumbnails)}枚の画像を削除してよろしいですか?\nこの操作は取り消せません'
        selected_action = QMessageBox.warning(None, '', message,
                                              QMessageBox.Cancel,
                                              QMessageBox.Yes)
        if selected_action == QMessageBox.Yes:
            for selected_thumbnail in self.selected_thumbnails:
                os.remove(path=str(selected_thumbnail.path))
            self._reload_images(self.__selected_dataset_category())

    def on_clicked_train_button(self):

        img_suffix_list = ['.jpg', '.jpeg', '.png', '.gif', '.bmp']

        if not [
                img for img in os.listdir(
                    Dataset.images_path(Dataset.Category.TEST_NG))
                if Path(img).suffix in img_suffix_list
        ]:
            self.msgBox = QMessageBox()
            self.msgBox.setText(
                '性能評価用の不良品画像フォルダが空です.\nトレーニングを開始するには不良品画像を1枚以上追加してください.')
            self.msgBox.exec()
            return
        elif not [
                img for img in os.listdir(
                    Dataset.images_path(Dataset.Category.TEST_OK))
                if Path(img).suffix in img_suffix_list
        ]:
            self.msgBox = QMessageBox()
            self.msgBox.setText(
                '性能評価用の良品画像フォルダが空です.\nトレーニングを開始するには良品画像を1枚以上追加してください.')
            self.msgBox.exec()
            return
        elif not [
                img for img in os.listdir(
                    Dataset.images_path(Dataset.Category.TRAINING_OK))
                if Path(img).suffix in img_suffix_list
        ]:
            self.msgBox = QMessageBox()
            self.msgBox.setText(
                'トレーニング用の良品画像フォルダが空です.\nトレーニングを開始するには良品画像を1枚以上追加してください.')
            self.msgBox.exec()
            return

        del self.select_area_dialog
        self.select_area_dialog = SelectAreaDialog()
        self.select_area_dialog.finish_selecting_area.connect(
            self.on_finished_selecting_area)
        self.select_area_dialog.show()
        self.__reload_recent_training_date()

    def on_finished_selecting_area(self, data: TrimmingData):
        categories = [
            Dataset.Category.TRAINING_OK, Dataset.Category.TEST_OK,
            Dataset.Category.TEST_NG
        ]
        truncated_image_paths = []
        for category in categories:
            dir_path = Dataset.images_path(category)
            save_path = Dataset.trimmed_path(category)
            if os.path.exists(save_path):
                shutil.rmtree(save_path)
            os.mkdir(save_path)
            if not data.needs_trimming:
                copy_tree(str(dir_path), str(save_path))
            else:
                file_list = os.listdir(dir_path)
                file_list = [
                    img for img in file_list if Path(img).suffix in
                    ['.jpg', '.jpeg', '.png', '.gif', '.bmp']
                ]
                for file_name in file_list:
                    truncated_image_path = Dataset.trim_image(
                        os.path.join(dir_path, file_name), save_path, data)
                    if truncated_image_path:
                        file_name = os.path.basename(truncated_image_path)
                        shutil.move(
                            truncated_image_path,
                            os.path.join(
                                Dataset.images_path(
                                    Dataset.Category.TRUNCATED), file_name))
                        truncated_image_paths.append(truncated_image_path)
            Project.save_latest_trimming_data(data)

        # alert for moving truncated images
        if truncated_image_paths:
            self.msgBox = QMessageBox()
            self.msgBox.setText(str(len(truncated_image_paths))+'枚の画像を読み込めませんでした. これらの画像はtruncatedフォルダに移動されました.\n\n'\
                                + 'このままトレーニングを開始しますか?')
            self.msgBox.setStandardButtons(self.msgBox.Yes | self.msgBox.No)
            self.msgBox.setDefaultButton(self.msgBox.Yes)
            reply = self.msgBox.exec()
            if reply == self.msgBox.No:
                return

        # start training
        LearningModel.default().start_training()

    def on_dataset_directory_changed(self, directory: str):
        selected_category = self.__selected_dataset_category()
        if str(Dataset.images_path(selected_category)) == directory:
            self._reload_images(selected_category)

    def on_finished_training(self):
        self.__reload_recent_training_date()

    def __selected_dataset_category(self) -> Optional[Dataset.Category]:
        current_item = self.ui.image_list_widget.currentItem()
        current_item_text = current_item.text(0)
        # FIXME: refactor
        if current_item_text == 'トレーニング用画像' or current_item_text == '性能評価用画像':
            return None
        elif current_item.parent().text(0) == 'トレーニング用画像':
            if current_item_text == '良品':  # train_OK
                return Dataset.Category.TRAINING_OK
        elif current_item.parent().text(0) == '性能評価用画像':
            if current_item_text == '良品':  # test_OK
                return Dataset.Category.TEST_OK
            elif current_item_text == '不良品':  # test_NG
                return Dataset.Category.TEST_NG
        else:
            assert False

    def __reload_recent_training_date(self):
        latest_training_date = Project.latest_training_date()
        if latest_training_date is None:
            self.ui.latest_training_date_label.setText('トレーニング未実行')
        else:
            date_description = latest_training_date.strftime('%Y/%m/%d')
            self.ui.latest_training_date_label.setText(
                f'前回のトレーニング:{date_description}')