Example #1
0
class PangoFileWidget(PangoDockWidget):
    def __init__(self, title, parent=None):
        super().__init__(title, parent)
        self.setFixedWidth(160)

        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.Files | QDir.NoDotAndDotDot)
        self.file_model.setNameFilters(["*.jpg", "*.png"])
        self.file_model.setNameFilterDisables(False)
        self.th_provider = ThumbnailProvider()
        self.file_model.setIconProvider(self.th_provider)

        self.file_view = QListView()
        self.file_view.setModel(self.file_model)
        self.file_view.setViewMode(QListView.IconMode)
        self.file_view.setFlow(QListView.LeftToRight)
        self.file_view.setIconSize(QSize(150, 150))
        
        self.setWidget(self.file_view)

    def select_next_image(self):
        c_idx = self.file_view.currentIndex()
        idx = c_idx.siblingAtRow(c_idx.row()+1)
        if idx.row() != -1:
            self.file_view.setCurrentIndex(idx)

    def select_prev_image(self):
        c_idx = self.file_view.currentIndex()
        idx = c_idx.siblingAtRow(c_idx.row()-1)
        if idx.row() != -1:
            self.file_view.setCurrentIndex(idx)
Example #2
0
class MyGridFilesWidget(QWidget):
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)
        start_dir = QStringListModel()
        start_dir = 'C:/ROBOCZY'

        self.model = QFileSystemModel()
        self.model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot
                             | QDir.AllEntries)
        self.model.setNameFilters()
        self.model.setNameFilterDisables(0)
        #self.model.setRootPath(start_dir)

        #self.model.setRootPath(start_dir)
        self.tree = QTreeView()
        self.tree.setRootIndex(self.model.index(start_dir))
        self.tree.setModel(self.model)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)

        self.tree.setWindowTitle("Dir View")
        self.tree.resize(640, 480)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        self.setLayout(windowLayout)
Example #3
0
class FileChooser(QWidget):
    fileOpened = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.folderBox = QComboBox(self)
        self.explorerTree = FileTreeView(self)
        self.explorerTree.doubleClickCallback = self._fileOpened
        self.explorerModel = QFileSystemModel(self)
        self.explorerModel.setFilter(
            QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)
        self.explorerModel.setNameFilters(["*.py"])
        self.explorerModel.setNameFilterDisables(False)
        self.explorerTree.setModel(self.explorerModel)
        for index in range(1, self.explorerModel.columnCount()):
            self.explorerTree.hideColumn(index)
        self.setCurrentFolder()
        self.folderBox.currentIndexChanged[int].connect(
            self.updateCurrentFolder)

        layout = QVBoxLayout(self)
        layout.addWidget(self.folderBox)
        layout.addWidget(self.explorerTree)
        layout.setContentsMargins(5, 5, 0, 0)

    def _fileOpened(self, modelIndex):
        path = self.explorerModel.filePath(modelIndex)
        if os.path.isfile(path):
            self.fileOpened.emit(path)

    def currentFolder(self):
        return self.explorerModel.rootPath()

    def setCurrentFolder(self, path=None):
        if path is None:
            app = QApplication.instance()
            path = app.getScriptsDirectory()
        else:
            assert os.path.isdir(path)
        self.explorerModel.setRootPath(path)
        self.explorerTree.setRootIndex(self.explorerModel.index(path))
        self.folderBox.blockSignals(True)
        self.folderBox.clear()
        style = self.style()
        dirIcon = style.standardIcon(style.SP_DirIcon)
        self.folderBox.addItem(dirIcon, os.path.basename(path))
        self.folderBox.insertSeparator(1)
        self.folderBox.addItem(self.tr("Browse…"))
        self.folderBox.setCurrentIndex(0)
        self.folderBox.blockSignals(False)

    def updateCurrentFolder(self, index):
        if index < self.folderBox.count() - 1:
            return
        path = QFileDialog.getExistingDirectory(
            self, self.tr("Choose Directory"), self.currentFolder(),
            QFileDialog.ShowDirsOnly)
        if path:
            QSettings().setValue("scripting/path", path)
            self.setCurrentFolder(path)
class FileTree(QWidget):

    def __init__(self, defaultfolder=r'c:\Zen_Output'):
        super(QWidget, self).__init__()

        filter = ['*.czi', '*.ome.tiff', '*ome.tif' '*.tiff' '*.tif']

        # define the style for the FileTree via s style sheet
        self.setStyleSheet("""
            QTreeView::item {
            background-color: rgb(38, 41, 48);
            font-weight: bold;
            }

            QTreeView::item::selected {
            background-color: rgb(38, 41, 48);
            color: rgb(0, 255, 0);

            }

            QTreeView QHeaderView:section {
            background-color: rgb(38, 41, 48);
            color: rgb(255, 255, 255);
            }
            """)

        self.model = QFileSystemModel()
        self.model.setRootPath(defaultfolder)
        self.model.setFilter(QtCore.QDir.AllDirs | QDir.Files | QtCore.QDir.NoDotAndDotDot)
        self.model.setNameFilterDisables(False)
        self.model.setNameFilters(filter)

        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(defaultfolder))
        self.tree.setAnimated(True)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(False)
        header = self.tree.header()
        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        self.setLayout(windowLayout)

        self.tree.clicked.connect(self.on_treeView_clicked)

    @pyqtSlot()
    def on_treeView_clicked(self, index):
        indexItem = self.model.index(index.row(), 0, index.parent())
        filename = self.model.fileName(indexItem)
        filepath = self.model.filePath(indexItem)

        # open the file when clicked
        print('Opening ImageFile : ', filepath)
        open_image_stack(filepath)
def directory(app):
    #app = QApplication(sys.argv)

    QCoreApplication.setApplicationVersion(QT_VERSION_STR)
    parser = QCommandLineParser()
    parser.setApplicationDescription("File Directory")
    parser.addHelpOption()
    parser.addVersionOption()

    dontUseCustomDirectoryIconsOption = QCommandLineOption(
        'C', "Set QFileIconProvider.DontUseCustomDirectoryIcons")
    parser.addOption(dontUseCustomDirectoryIconsOption)
    parser.addPositionalArgument('', "The directory to start in.")
    parser.process(app)
    try:
        rootPath = parser.positionalArguments().pop(0)
    except IndexError:
        rootPath = None

    model = QFileSystemModel()
    model.setRootPath('')
    filter = ['*.db']  #filtering out just by db
    model.setNameFilters(filter)
    model.setNameFilterDisables(0)  #Only show the filtered .db paths
    #filename = model.filePath()
    #print(filename)

    if parser.isSet(dontUseCustomDirectoryIconsOption):
        model.iconProvider().setOptions(
            QFileIconProvider.DontUseCustomDirectoryIcons)
    tree = QTreeView()
    tree.setModel(model)
    if rootPath is not None:
        rootIndex = model.index(QDir.cleanPath(rootPath))
        if rootIndex.isValid():
            tree.setRootIndex(rootIndex)

    # Demonstrating look and feel features.
    tree.setAnimated(False)
    tree.setIndentation(20)
    tree.setSortingEnabled(True)

    availableSize = QApplication.desktop().availableGeometry(tree).size()
    tree.resize(availableSize / 2)
    tree.setColumnWidth(0, tree.width() / 3)

    tree.setWindowTitle("Directory View")
    tree.show()

    sys.exit(app.exec_())
Example #6
0
class DirTreeView(QTreeView):
    """
    A widget class used to display the contents of an Informatic project source
    directory.
    """
    newSelection = pyqtSignal([list])

    def __init__(self, parent=None, rootdir=None):
        """
        The rootdir keyword argument is a filepath for a directory initialized
        as the root directory whose contents are displayed by the widget.
        """

        # Invoke the QTreeView constructor
        super().__init__(parent)

        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        # The widget's contents are based on a file system model
        self.dirTree = QFileSystemModel()

        # Display only files with filename extensions commonly used for Inform 6
        # source files
        self.dirTree.setNameFilterDisables(False)
        self.dirTree.setNameFilters(['*.inf', '*.i6', '*.h'])

        self.dirTree.setRootPath(rootdir)
        self.setModel(self.dirTree)
        self.cd(rootdir)

        # Hide all but the first column, which holds the filename
        for column in range(1, self.dirTree.columnCount()):
            self.hideColumn(column)

    def cd(self, path):
        """
        Takes one argument, path, a directory filepath, and changes the root
        directory displayed by the widget to the directory at that filepath.
        """
        self.setRootIndex(self.dirTree.index(path))

    def selectionChanged(self, selected, deselected):
        """
        Emits the newSelection signal with a list of selected items whenever the
        selection of items in the file tree is changed.
        """
        self.newSelection.emit(selected.indexes())
Example #7
0
class CommanderFileManager(object):
    """docstring for FileManager"""
    def __init__(self):

        self.files_tree_model = QFileSystemModel()
        self.set_filter()
        self.files_tree_model.setRootPath(os.path.abspath(__file__))
        self._file = None

    def set_filter(self, filter_list=['*.lua']):
        self.files_tree_model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot | QDir.AllEntries)
        self.files_tree_model.setNameFilters(filter_list)
        self.files_tree_model.setNameFilterDisables(0)

    def get_path(self, index):
        return self.files_tree_model.filePath(index)

    def open(self, path):
        try:
            with open(path, 'rt') as f:
                text = f.read()
                self._file = path
                return text
        except Exception as e:
            print(e)
        return None

    def save(self, fp, text):
        """ save file document """
        try:
            with open(fp, 'wt') as f:
                f.write(text)
        except Exception as e:
            return False

        return True

    @property
    def file(self):
        return self._file
    @file.setter
    def file(self, f):
        self._file = f
Example #8
0
 def open_project(self, project):
     project_path = project.path
     qfsm = None  # Should end up having a QFileSystemModel
     if project_path not in self.__projects:
         qfsm = QFileSystemModel()
         project.model = qfsm
         qfsm.setRootPath(project_path)
         qfsm.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)
         # If set to true items that dont match are displayed disabled
         qfsm.setNameFilterDisables(False)
         pext = ["*{0}".format(x) for x in project.extensions]
         logger.debug(pext)
         qfsm.setNameFilters(pext)
         self.__projects[project_path] = project
         self.__check_files_for(project_path)
         self.projectOpened.emit(project_path)
     else:
         qfsm = self.__projects[project_path]
     return qfsm
Example #9
0
    def browserFile(self):

        global Browser, Model

        self.browser = QTreeView()

        model = QFileSystemModel()
        model.setNameFilters(['*.nii'])
        model.setNameFilterDisables(False)
        model.setReadOnly(True)

        self.browser.setModel(model)
        self.browser.expandAll()
        self.browser.setColumnWidth(0, 400)

        self.browser.selectionModel().selectionChanged.connect(self.select)

        Browser = self.browser
        Model = model
 def open_project(self, project):
     project_path = project.path
     qfsm = None  # Should end up having a QFileSystemModel
     if project_path not in self.__projects:
         qfsm = QFileSystemModel()
         project.model = qfsm
         qfsm.setRootPath(project_path)
         qfsm.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)
         # If set to true items that dont match are displayed disabled
         qfsm.setNameFilterDisables(False)
         pext = ["*{0}".format(x) for x in project.extensions]
         logger.debug(pext)
         qfsm.setNameFilters(pext)
         self.__projects[project_path] = project
         self.__check_files_for(project_path)
         self.projectOpened.emit(project_path)
     else:
         qfsm = self.__projects[project_path]
     return qfsm
Example #11
0
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 file system view - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.model = QFileSystemModel()
        self.model.setRootPath('/home/rob/Muziek')
        filter = ["*.wav", "*.ogg"]
        self.model.setNameFilters(filter)
        self.model.setNameFilterDisables(0)
        root = self.model.setRootPath('/home/rob/Muziek')
        #print(root)
        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.setRootIndex(root)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)
        self.tree.doubleClicked.connect(self.test)

        self.tree.setWindowTitle("Dir View")
        self.tree.resize(640, 480)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        self.setLayout(windowLayout)

        self.show()

    def test(self, signal):
        file_path = self.model.filePath(signal)
        print(file_path)
Example #12
0
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 file system view - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.initUI()

    def tree_cilcked(self, Qmodelidx):
        print(self.model.filePath(Qmodelidx))
        print(self.model.fileName(Qmodelidx))
        print(self.model.fileInfo(Qmodelidx))

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        # 这里得到目录结构
        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        # 这里过滤,只显示 py 文件
        mf = self.model.setNameFilters(['*.py'])
        self.model.setNameFilterDisables(False)
        # 这里做展示
        self.tree = QTreeView()
        self.tree.setModel(self.model)
        # self.tree.setRootIndex(self.model.index(QDir.currentPath()))
        self.tree.setRootIndex(self.model.index('g:\l'))
        self.tree.doubleClicked.connect(self.tree_cilcked)
        # 这里隐藏了目录信息展示
        for i in [1, 2, 3]:
            self.tree.setColumnHidden(i, True)
        # 缩进
        self.tree.setIndentation(10)
        self.tree.setWindowTitle("Dir View")
        self.tree.resize(640, 480)
        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        self.setLayout(windowLayout)
        self.show()
    def load_tree(self, project):
        """Load the tree view on the right based on the project selected."""
        qfsm = QFileSystemModel()
        qfsm.setRootPath(project.path)
        load_index = qfsm.index(qfsm.rootPath())
        qfsm.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        qfsm.setNameFilterDisables(False)
        pext = ["*{0}".format(x) for x in project.extensions]
        qfsm.setNameFilters(pext)

        self._tree.setModel(qfsm)
        self._tree.setRootIndex(load_index)

        t_header = self._tree.header()
        t_header.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        t_header.setSectionResizeMode(0, QHeaderView.Stretch)
        t_header.setStretchLastSection(False)
        t_header.setClickable(True)

        self._tree.hideColumn(1)  # Size
        self._tree.hideColumn(2)  # Type
        self._tree.hideColumn(3)  # Modification date
Example #14
0
    def load_tree(self, project):
        """Load the tree view on the right based on the project selected."""
        qfsm = QFileSystemModel()
        qfsm.setRootPath(project.path)
        load_index = qfsm.index(qfsm.rootPath())
        qfsm.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        qfsm.setNameFilterDisables(False)
        pext = ["*{0}".format(x) for x in project.extensions]
        qfsm.setNameFilters(pext)

        self._tree.setModel(qfsm)
        self._tree.setRootIndex(load_index)

        t_header = self._tree.header()
        t_header.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        t_header.setSectionResizeMode(0, QHeaderView.Stretch)
        t_header.setStretchLastSection(False)
        t_header.setSectionsClickable(True)

        self._tree.hideColumn(1)  # Size
        self._tree.hideColumn(2)  # Type
        self._tree.hideColumn(3)  # Modification date
Example #15
0
    def setupModel(self):
        self.list_view_imported_symbols.setModel(
            self._parent_controller.get_instruments_model())

        fileName = "G:/Programming/Projects/QtStatisticCalculator/^spx_y_test.csv"
        name = AkFunctions.getShortName(fileName)
        headers, data = AkFunctions.loadCSV(fileName)
        xPeriod = AkPeriod(1, data, headers=headers)

        #instrument = AkInstrument(name, [xPeriod])

        analysis_list = [
            AkAnalysisType.Calendar, AkAnalysisType.Period,
            AkAnalysisType.Series
        ]
        instrument_ = AkInstrument(name,
                                   sources=[data],
                                   analysis_types=analysis_list,
                                   method=AkSelectionMethod.CC,
                                   precision=3)

        self._parent_controller.get_instruments_model().insertRows(
            0, len([instrument_]), [instrument_])

        fileSystemModel = QFileSystemModel()
        fileSystemModel.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot
                                  | QDir.AllEntries)

        filters = ["*.csv"]
        fileSystemModel.setNameFilters(filters)
        fileSystemModel.setNameFilterDisables(False)

        fileSystemModel.setRootPath(QDir.currentPath())

        self.tree_view_windows_files.setModel(fileSystemModel)
        self.tree_view_windows_files.hideColumn(1)
        self.tree_view_windows_files.hideColumn(2)
        self.tree_view_windows_files.hideColumn(3)
class Broswer_Img(QMainWindow):
    """
    选择图片文件并且预览,自动匹配多图的情况和背景图片
    """
    Close_Signal = pyqtSignal(list)

    def __init__(self, *args, **kwargs):
        QMainWindow.__init__(self, *args, **kwargs)
        self.Current_Dir = QDir.home().absolutePath()
        #self.Current_Dir = Wk_Dir
        self.setWindowTitle("Select Imags")
        self.setWindowModality(Qt.ApplicationModal)
        self.Left_Dock_Code()
        self.Central_Frame_Code()
        self.Right_Dock_Code()
        self.connect_Signals()

        self.wb_nav_left.setEnabled(False)
        self.wb_nav_right.setEnabled(False)
        self.bkgd_nav_left.setEnabled(False)
        self.bkgd_nav_right.setEnabled(False)

        #self.setGeometry(200, 200, 1000, 600)
        #self.setMaximumSize(QSize(1000, 600))

    def Left_Dock_Code(self):
        self.Left_Frame = QFrame(self)
        self.Model = QFileSystemModel()
        self.Model.setNameFilterDisables(False)
        self.Model.setRootPath(self.Current_Dir)
        #self.Model.setSorting(QDir.DirsFirst | QDir.IgnoreCase | QDir.Name)
        self.Model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs
                             | QDir.AllEntries)
        self.Model.setNameFilters(['*.tif'])
        self.Tree = QTreeView(self.Left_Frame)
        self.Tree.setModel(self.Model)
        self.Tree.setRootIndex(self.Model.index(self.Current_Dir))
        self.Tree.expandAll()
        self.Dir_Select = QPushButton("Select a Folder", self.Left_Frame)
        layout = QVBoxLayout()
        layout.addWidget(self.Tree)
        layout.addWidget(self.Dir_Select)
        self.Left_Frame.setLayout(layout)
        self.Left_Dock = QDockWidget('Broswer Images', self)
        self.Left_Dock.setWidget(self.Left_Frame)
        self.Left_Dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.Dir_Select.clicked.connect(self.dir_selection)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.Left_Dock)

    def Central_Frame_Code(self):
        self.Central_Frame = QFrame(self)
        layout = QGridLayout()
        self.wb_label = QLabel(self.Central_Frame)
        self.bkgd_label = QLabel(self.Central_Frame)
        #self.wb_label.setMaximumHeight(30)
        self.wb_label.setWordWrap(True)

        self.wb = QLabel(self.Central_Frame)
        self.wb.setScaledContents(True)
        self.bkgd = QLabel(self.Central_Frame)
        self.bkgd.setScaledContents(True)
        self.wb.setMaximumSize(QSize(300, 300))
        self.bkgd.setMaximumSize(QSize(300, 300))

        self.wb_navigator = QFrame(self.Central_Frame)
        self.wb_nav_left = QPushButton('<--', self.wb_navigator)
        self.wb_nav_right = QPushButton('-->', self.wb_navigator)
        nav_layout = QHBoxLayout()
        nav_layout.addWidget(self.wb_nav_left)
        nav_layout.addWidget(self.wb_nav_right)
        self.wb_navigator.setLayout(nav_layout)
        self.wb_navigator.setMaximumHeight(60)

        self.bkgd_navigator = QFrame(self.Central_Frame)
        self.bkgd_nav_left = QPushButton('<--', self.bkgd_navigator)
        self.bkgd_nav_right = QPushButton('-->', self.bkgd_navigator)
        nav_layout2 = QHBoxLayout()
        nav_layout2.addWidget(self.bkgd_nav_left)
        nav_layout2.addWidget(self.bkgd_nav_right)
        self.bkgd_navigator.setLayout(nav_layout2)
        self.bkgd_navigator.setMaximumHeight(60)

        self.btns = QFrame(self.Central_Frame)
        self.btns.setMaximumHeight(60)
        self.Btn_Add = QPushButton('Add', self.btns)
        self.Btn_Close = QPushButton('Close', self.btns)
        btn_layout = QHBoxLayout()
        btn_layout.addWidget(self.Btn_Add)
        btn_layout.addWidget(self.Btn_Close)
        self.btns.setLayout(btn_layout)

        # 根据具体的传入参数构建不同的视图
        layout.addWidget(self.wb_label, 0, 0)
        layout.addWidget(self.bkgd_label, 0, 1)

        layout.addWidget(self.wb, 1, 0)
        layout.addWidget(self.bkgd, 1, 1)

        layout.addWidget(self.wb_navigator, 2, 0)
        layout.addWidget(self.bkgd_navigator, 2, 1)

        layout.addWidget(self.btns, 3, 0, 2, 0)
        layouts = QVBoxLayout()
        layouts.addLayout(layout)
        self.Central_Frame.setLayout(layouts)
        self.setCentralWidget(self.Central_Frame)
        #self.setStyleSheet('border:1px solid red')

    def Right_Dock_Code(self):
        self.Added_Img_tree = Img_Tree([], self)
        self.Right_Dock = QDockWidget('Selected Images', self)
        self.Right_Dock.setWidget(self.Added_Img_tree)
        self.Right_Dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.addDockWidget(Qt.RightDockWidgetArea, self.Right_Dock)

    def connect_Signals(self):
        self.Tree.clicked.connect(self.Load_Img_to_Central_Frame)
        self.wb_nav_left.clicked.connect(self.change_img_index)
        self.wb_nav_right.clicked.connect(self.change_img_index)
        self.bkgd_nav_left.clicked.connect(self.change_img_index)
        self.bkgd_nav_right.clicked.connect(self.change_img_index)
        self.Btn_Add.clicked.connect(self.Add_Btn_Action)
        self.Btn_Close.clicked.connect(self.Close_Btn_Action)

    def Load_Img_to_Central_Frame(self, Index):
        select_file = self.Tree.model().filePath(Index)
        _, ext = os.path.splitext(select_file)
        if ext in ['.tif', '.jpeg', '.png', '.jpg']:
            self.Related_Imgs = BioRad_Imgs(select_file)
        self.set_Central_Frame()

    def set_Central_Frame(self):
        self.wb_nav_left.setEnabled(False)
        self.wb_nav_right.setEnabled(False)
        self.bkgd_nav_left.setEnabled(False)
        self.bkgd_nav_right.setEnabled(False)

        self.current_wb = self.Related_Imgs.WB_list[self.Related_Imgs.wb_index]
        self.current_bkgd = self.Related_Imgs.BKGD_list[
            self.Related_Imgs.bkgd_index]
        self.wb_label.setText(
            self.current_wb.replace(self.Related_Imgs.Dir, '.'))
        self.bkgd_label.setText(
            self.current_bkgd.replace(self.Related_Imgs.Dir, '.'))
        wb, _ = CV_Img_to_QImage(cv2.imread(self.current_wb))
        bkgd, _ = CV_Img_to_QImage(cv2.imread(self.current_bkgd))
        self.wb.setPixmap(wb)
        self.wb.setScaledContents(True)
        self.bkgd.setPixmap(bkgd)
        wb_len = len(self.Related_Imgs.WB_list)
        bkgd_len = len(self.Related_Imgs.BKGD_list)
        if self.Related_Imgs.wb_index > 0:
            self.wb_nav_left.setEnabled(True)
        if wb_len - self.Related_Imgs.wb_index > 1:
            self.wb_nav_right.setEnabled(True)

        if self.Related_Imgs.bkgd_index > 0:
            self.bkgd_nav_left.setEnabled(True)
        if bkgd_len - self.Related_Imgs.bkgd_index > 1:
            self.bkgd_nav_right.setEnabled(True)

    def change_img_index(self):
        sender = self.sender()
        if sender == self.wb_nav_left:
            self.Related_Imgs.wb_index = self.Related_Imgs.wb_index - 1
        if sender == self.wb_nav_right:
            self.Related_Imgs.wb_index = self.Related_Imgs.wb_index + 1
        if sender == self.bkgd_nav_left:
            self.Related_Imgs.bkgd_index = self.Related_Imgs.bkgd_index - 1
        if sender == self.bkgd_nav_right:
            self.Related_Imgs.bkgd_index = self.Related_Imgs.bkgd_index + 1
        self.set_Central_Frame()

    def Add_Btn_Action(self):
        list = [{'wb': self.current_wb, 'bkgd': self.current_bkgd}]
        self.Added_Img_tree.Add_top_Level_Item(list)
        print(self.Added_Img_tree.imgs)

    def Close_Btn_Action(self):
        self.close()

    def closeEvent(self, event):
        self.Close_Signal.emit(self.Added_Img_tree.imgs)

    def dir_selection(self):
        global Wk_Dir
        dir = QFileDialog.getExistingDirectory(self, "Choose a Directory",
                                               Wk_Dir)
        self.Current_Dir = dir
        Wk_Dir = dir
        self.Tree.setRootIndex(self.Model.index(self.Current_Dir))
        self.Left_Dock.setWindowTitle(dir)
Example #17
0
class MainView(QMainWindow):

    resizeCompleted = pyqtSignal()

    def __init__(self, model, controller, image_path):
        self.settings = SettingsModel()
        self.slideshow = SlideshowModel()

        self.model = model
        self.canvas = self.model.canvas
        self.main_controller = controller
        self.canvas_controller = CanvasController(self.canvas)
        super(MainView, self).__init__()
        self.build_ui()
        self.center_ui()

        # Resize timer to prevent laggy updates
        self.resize_timer = None
        self.resizeCompleted.connect(self.resize_completed)

        # Slideshow
        if self.settings.get('Slideshow', 'reverse') == 'True':
            self.slideshow.updateSignal.connect(self.on_previous_item)
        else:
            self.slideshow.updateSignal.connect(self.on_next_item)
            
        self.model.subscribe_update_func(self.update_ui_from_model)

        self.arguments = {
            'image_path': image_path
        }

    def build_ui(self):
        self.ui = Ui_Hitagi()
        self.ui.setupUi(self)

        # File menu
        self.ui.actionSet_as_wallpaper.triggered.connect(self.on_set_as_wallpaper)
        self.ui.actionCopy_to_clipboard.triggered.connect(self.on_clipboard)
        self.ui.actionOpen_current_directory.triggered.connect(self.on_current_dir)
        self.ui.actionOptions.triggered.connect(self.on_options)
        self.ui.actionExit.triggered.connect(self.on_close)

        # Folder menu 
        self.ui.actionOpen_next.triggered.connect(self.on_next_item)
        self.ui.actionOpen_previous.triggered.connect(self.on_previous_item)
        self.ui.actionChange_directory.triggered.connect(self.on_change_directory)
        self.ui.actionSlideshow.triggered.connect(self.on_slideshow)

        # View menu
        self.ui.actionZoom_in.triggered.connect(self.on_zoom_in)
        self.ui.actionZoom_out.triggered.connect(self.on_zoom_out)
        self.ui.actionOriginal_size.triggered.connect(self.on_zoom_original)
        self.ui.actionRotate_clockwise.triggered.connect(self.on_rotate_clockwise)
        self.ui.actionRotate_counterclockwise.triggered.connect(self.on_rotate_counterclockwise)
        self.ui.actionFlip_horizontally.triggered.connect(self.on_flip_horizontal)
        self.ui.actionFlip_vertically.triggered.connect(self.on_flip_vertical)
        self.ui.actionFit_image_width.triggered.connect(self.on_scale_image_to_width)
        self.ui.actionFit_image_height.triggered.connect(self.on_scale_image_to_height)
        self.ui.actionFile_list.triggered.connect(self.on_toggle_filelist)
        self.ui.actionFullscreen.triggered.connect(self.on_fullscreen)

        # Favorite menu
        self.ui.actionAdd_to_favorites.triggered.connect(self.on_add_to_favorites)
        self.ui.actionRemove_from_favorites.triggered.connect(self.on_remove_from_favorites)

        # Help menu
        self.ui.actionChangelog.triggered.connect(self.on_changelog)
        self.ui.actionAbout.triggered.connect(self.on_about)

        # Load stylesheet
        stylesheet_dir = "resources/hitagi.stylesheet"
        with open(stylesheet_dir, "r") as sh:
            self.setStyleSheet(sh.read())
        
        # File listing
        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files)
        self.file_model.setNameFilters(['*.bmp', '*.gif', '*.jpg', '*.jpeg', '*.png', '*.png', '*.pbm', '*.pgm', '*.ppm', '*.xbm', '*.xpm'])
        self.file_model.setNameFilterDisables(False)
        self.file_model.setRootPath(self.settings.get('Directory', 'default'))

        self.ui.treeView.setModel(self.file_model)
        self.ui.treeView.setColumnWidth(0, 120)
        self.ui.treeView.setColumnWidth(1, 120)
        self.ui.treeView.hideColumn(1)
        self.ui.treeView.hideColumn(2)

        # Double click
        self.ui.treeView.activated.connect(self.on_dir_list_activated)
        # Update file list
        self.ui.treeView.clicked.connect(self.on_dir_list_clicked)
        # Open parent
        self.ui.pushButton_open_parent.clicked.connect(self.on_open_parent)
        self.ui.pushButton_favorite.clicked.connect(self.on_manage_favorite)

        # Shortcuts
        _translate = QCoreApplication.translate
        self.ui.actionExit.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Exit')))

        self.ui.actionOpen_next.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Next')))
        self.ui.actionOpen_previous.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Previous')))
        self.ui.actionChange_directory.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Directory')))
        self.ui.actionAdd_to_favorites.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Add to favorites')))
        self.ui.actionRemove_from_favorites.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Remove from favorites')))
        self.ui.actionSlideshow.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Slideshow')))

        self.ui.actionZoom_in.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom in')))
        self.ui.actionZoom_out.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom out')))
        self.ui.actionOriginal_size.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom original')))
        self.ui.actionRotate_clockwise.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Rotate clockwise')))
        self.ui.actionRotate_counterclockwise.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Rotate counterclockwise')))
        self.ui.actionFlip_horizontally.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Flip horizontal')))
        self.ui.actionFlip_vertically.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Flip vertical')))
        self.ui.actionFit_image_width.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fit to width')))
        self.ui.actionFit_image_height.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fit to height')))
        self.ui.actionFile_list.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Toggle filelist')))
        self.ui.actionFullscreen.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fullscreen')))

        # Load favorites in UI
        self.load_favorites()

        # Background
        self.ui.graphicsView.setBackgroundBrush(QBrush(QColor(self.settings.get('Look', 'background')), Qt.SolidPattern))

        # Save current height for fullscreen mode
        self.default_menubar_height = self.ui.menubar.height()
        # Save current width for file list
        self.default_filelist_width = self.ui.fileWidget.width()

    def load_favorites(self):
        self.favorites = FavoritesModel()
        self.ui.menuFavorites.clear()
        for item in self.favorites.items():
            self.ui.menuFavorites.addAction(item).triggered.connect((lambda item: lambda: self.on_open_favorite(item))(item))

    def on_open_favorite(self, path):
        self.main_controller.change_directory(path)

    def center_ui(self):
        ui_geometry = self.frameGeometry()
        center_point = QDesktopWidget().availableGeometry().center()
        ui_geometry.moveCenter(center_point)
        self.move(ui_geometry.topLeft())
   
    # Qt show event
    def showEvent(self, event):
        self.main_controller.start(self.arguments['image_path']) # Arguments and starting behaviour

        # Start in fullscreen mode according to settings
        if self.settings.get('Misc', 'fullscreen_mode') == 'True':
            self.on_fullscreen()
            
        # Initialize container geometry to canvas
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()

    def update_resize_timer(self, interval=None):
        if self.resize_timer is not None:
            self.killTimer(self.resize_timer)
        if interval is not None:
            self.resize_timer = self.startTimer(interval)
        else:
            self.resize_timer = None

    # Qt resize event
    def resizeEvent(self, event):
        self.update_resize_timer(300)

    # Qt timer event
    def timerEvent(self, event):
        if event.timerId() == self.resize_timer:
            self.update_resize_timer()
            self.resizeCompleted.emit()

    def resize_completed(self):
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()
        
    # Additional static shortcuts
    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape and self.model.is_fullscreen:
            self.main_controller.toggle_fullscreen()

    def on_open_parent(self):
        parent_index = self.file_model.parent(self.file_model.index(self.file_model.rootPath()))
        self.file_model.setRootPath(self.file_model.filePath(parent_index))
        self.ui.treeView.setRootIndex(parent_index)

        # Update directory path
        self.model.directory = self.file_model.filePath(parent_index)

        self.update_ui_from_model()

    def on_dir_list_activated(self, index):
        if self.file_model.isDir(index) is not False:
            self.file_model.setRootPath(self.file_model.filePath(index))
            self.ui.treeView.setRootIndex(index)

            # Save current path
            self.model.directory = self.file_model.filePath(index)
            self.update_ui_from_model()
        
    def on_dir_list_clicked(self, index):
        self.main_controller.open_image(self.file_model.filePath(index))

    # File menu
    def on_set_as_wallpaper(self):
        from hitagilib.view.WallpaperView import WallpaperDialog
        from hitagilib.controller.wallpaper import WallpaperController

        image = self.model.get_image()
        if image is not None:
            dialog = WallpaperDialog(self, None, WallpaperController(self.model), image)
            dialog.show()

    def on_clipboard(self):
        self.main_controller.copy_to_clipboard()

    def on_current_dir(self):
        if not self.main_controller.open_in_explorer():
            self.show_explorer_error()

    def on_options(self):
        from hitagilib.view.OptionsView import OptionDialog
        self.dialog = OptionDialog(self)
        self.dialog.show()

    def on_close(self):
        if self.slideshow.isRunning():
            self.slideshow.exit()
        self.close()

    # Folder menu
    def on_next_item(self):
        current_index = self.ui.treeView.currentIndex()
        
        # Slideshow restart - determine if we are at the end of our file list
        if self.slideshow.is_running and self.settings.get('Slideshow', 'restart') == 'True' and not self.ui.treeView.indexBelow(current_index).isValid():
            self.main_controller.open_image(self.file_model.filePath(current_index))
            self.on_slideshow_restart(0) # Restart slideshow
        elif self.slideshow.is_running and self.settings.get('Slideshow', 'random') == 'True':
            # Random index - moveCursor expects constants @http://doc.qt.io/qt-5/qabstractitemview.html#CursorAction-enum
            index = self.ui.treeView.moveCursor(randint(0,9), Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            # Proceed normally, scroll down
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveDown, Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))

    def on_previous_item(self):
        current_index = self.ui.treeView.currentIndex()
        
        # Slideshow restart (reverse) - determine if we are the the top of our file list
        if self.slideshow.is_running and self.settings.get('Slideshow', 'restart') == 'True' and not self.ui.treeView.indexAbove(current_index).isValid():
            self.main_controller.open_image(self.file_model.filePath(current_index))
            self.on_slideshow_restart(1) # Restart slideshow
        elif self.slideshow.is_running and self.settings.get('Slideshow', 'random') == 'True':
            # Random index
            index = self.ui.treeView.moveCursor(randint(0,9), Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            # Proceed normally, scroll up
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveUp, Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))

    def on_slideshow(self):
        if self.ui.actionSlideshow.isChecked():
            self.slideshow.start()
            self.slideshow.is_running = True
        else:
            self.slideshow.is_running = False
            self.slideshow.exit()

    def on_slideshow_restart(self, direction):
        # 0: Restart from top to bottom
        # 1: Restart from bottom to top
        if direction == 0:
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveHome, Qt.NoModifier)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveEnd, Qt.NoModifier)
            self.main_controller.open_image(self.file_model.filePath(index))

        self.ui.treeView.setCurrentIndex(index)
            
        
    def on_change_directory(self):
        self.main_controller.change_directory()

    # View menu
    def on_zoom_in(self):
        self.canvas_controller.scale_image(1.1)

    def on_zoom_out(self):
        self.canvas_controller.scale_image(0.9)
    
    def on_rotate_clockwise(self):
        self.canvas_controller.rotate_image(90)

    def on_rotate_counterclockwise(self):
        self.canvas_controller.rotate_image(-90)

    def on_flip_horizontal(self):
        self.canvas_controller.flip_image(0)

    def on_flip_vertical(self):
        self.canvas_controller.flip_image(1)

    def on_scale_image_to_width(self):
        self.canvas_controller.update_image(1)

    def on_scale_image_to_height(self):
        self.canvas_controller.update_image(2)

    def on_zoom_original(self):
        self.canvas_controller.update_image(3)

    def on_toggle_filelist(self):
        if self.ui.fileWidget.isHidden():
            self.ui.fileWidget.show()
        else:
            self.ui.fileWidget.hide()
        self.update_resize_timer(300)
        
    def on_fullscreen(self):
        self.main_controller.toggle_fullscreen()
        
        if self.model.is_fullscreen:
            self.showFullScreen()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.setMaximumHeight(0) # Workaround to preserve shortcuts
        else:
            self.showNormal()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.setMaximumHeight(self.default_menubar_height)
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()

    # Favorite button
    def on_manage_favorite(self):
        if self.main_controller.check_favorites(self.model.directory):
            self.on_remove_from_favorites()
        else:
            self.on_add_to_favorites()

    # Favorite menu
    def on_add_to_favorites(self):
        self.main_controller.add_to_favorites()
        self.load_favorites()
        self.update_ui_from_model()

    def on_remove_from_favorites(self):
        self.main_controller.remove_from_favorites()
        self.load_favorites()
        self.update_ui_from_model()

    # Help menu
    def on_changelog(self):
        webbrowser.open('https://github.com/gimu/hitagi-reader/releases')

    def on_about(self):
        from hitagilib.view.AboutView import AboutDialog
        dialog = AboutDialog(self, None, None)
        dialog.show()

    def on_fileWidget_visibilityChanged(self, visible):
        """On file list hide/show and de/attachment"""
        if visible:
            self.ui.actionFile_list.setChecked(True)
        else:
            self.ui.actionFile_list.setChecked(False)
        self.update_resize_timer(300)

    def show_explorer_error(self):
        notify = QMessageBox()
        notify.setWindowTitle("Error")
        notify.setText(QCoreApplication.translate('Hitagi', "Couldn't open the current directory with an appropriate filemanager!"))
        notify.exec_()

    def update_ui_from_model(self):
        """Update UI from model."""
        self.settings = SettingsModel()

        # On changing directory
        self.file_model.setRootPath(self.model.directory)
        self.ui.treeView.setRootIndex(self.file_model.index(self.model.directory))

        # Update favorite button
        if self.main_controller.check_favorites(self.model.directory):
            self.ui.pushButton_favorite.setText(QCoreApplication.translate('Hitagi', "Unfavorite"))
        else:
            self.ui.pushButton_favorite.setText(QCoreApplication.translate('Hitagi', "Favorite"))

        # Canvas update
        self.ui.graphicsView.setScene(self.canvas.scene)
Example #18
0
class MainWindow(QObject):
    """
    Main class that is responsible for the graphical user interface.
    """

    message = pyqtSignal(str)
    login_ok = False

    def __init__(self):
        super(self).__init__()
        self.app = QApplication([])
        self.main_window = QWidget()
        self.api = StravaApi(startWithGui=True)

        self.main_window.setMinimumSize(500, 500)
        self.main_window.setWindowTitle("Strava API")
        self.api.apiMessage.connect(self.write_to_logbox, )

        # Log in --------
        user_info_layout = QHBoxLayout()
        user_info_grid_layout = QGridLayout()
        func_button_layout = QVBoxLayout()

        self.name_label = QLabel()
        self.username_label = QLabel()
        self.gender_label = QLabel()
        self.city_label = QLabel()
        self.country_label = QLabel()
        user_info_grid_layout.addWidget(QLabel("Name: "), 0, 0, Qt.AlignLeft)
        user_info_grid_layout.addWidget(self.name_label, 0, 1, Qt.AlignLeft)
        user_info_grid_layout.addWidget(QLabel("Username: "******"Gender: "), 2, 0, Qt.AlignLeft)
        user_info_grid_layout.addWidget(self.gender_label, 2, 1, Qt.AlignLeft)
        user_info_grid_layout.addWidget(QLabel("City: "), 3, 0, Qt.AlignLeft)
        user_info_grid_layout.addWidget(self.city_label, 3, 1, Qt.AlignLeft)
        user_info_grid_layout.addWidget(QLabel("Country: "), 4, 0,
                                        Qt.AlignLeft)
        user_info_grid_layout.addWidget(self.country_label, 4, 1, Qt.AlignLeft)

        self.client_id = QLineEdit()
        self.pass_edit = QLineEdit()

        athlete_info = QPushButton("Fetch Athlete information")
        athlete_info.clicked.connect(self.populate_athlete_labels)
        latest_run = QPushButton("Latest run")
        latest_run.clicked.connect(self.on_get_latest_activity_clicked)
        summary = QPushButton("Workout summary")
        summary.clicked.connect(self.on_summary_clicked)
        upload_activities = QPushButton("Upload Activities")
        upload_activities.clicked.connect(self.on_upload_activities_clicked)

        func_button_layout.addWidget(athlete_info)
        func_button_layout.addWidget(latest_run)
        func_button_layout.addWidget(summary)
        func_button_layout.addWidget(upload_activities)

        user_info_layout.addLayout(user_info_grid_layout)
        user_info_layout.addLayout(func_button_layout)

        self.activity_directory_input_box = QLineEdit()
        self.file_model = QFileSystemModel()
        self.file_model.setNameFilters(["*.gpx"])
        self.file_model.setNameFilterDisables(False)
        self.file_list_view = QListView()

        # Logger window --------
        logger_group_box = self.build_logger_window()

        self.main_layout = QVBoxLayout()
        self.main_layout.addLayout(user_info_layout)
        self.main_layout.addWidget(logger_group_box)

        self.main_window.setLayout(self.main_layout)
        self.main_window.show()

        self.login_dialog()

    def login_dialog(self):
        try:
            dialog = QDialog()
            dialog.setWindowTitle("Strava authorization")
            layout = QVBoxLayout(dialog)
            login_group_box = self.build_credentials_box()
            layout.addWidget(login_group_box)

            url_label = QLabel()
            url_label.setText(
                "Paste the link in an url and then use the \"code\" value in the response and paste that value in the empty box below. "
            )
            self.url_edit = QTextEdit()
            self.url_edit.setReadOnly(True)
            layout.addWidget(url_label)
            layout.addWidget(self.url_edit)
            login_input = QLineEdit()
            layout.addWidget(login_input)
            button = QPushButton("Ok")
            layout.addWidget(button)

            button.clicked.connect(dialog.accept)
            if (dialog.exec() == QDialog.Accepted):
                self.api.authorize(login_input.text().strip())
                self.write_to_logbox("Login successful!")
        except ValueError:
            error = sys.exc_info()[0]
            self.api.apiMessage(error)

    def build_credentials_box(self):
        login_group_box = QGroupBox("Log in")
        box_layout = QHBoxLayout()
        line_layout = QGridLayout()
        line_layout.addWidget(QLabel("Client Id: "), 0, 0)
        line_layout.addWidget(self.client_id, 0, 1)
        line_layout.addWidget(QLabel("Client Secret: "), 1, 0)
        self.pass_edit.setEchoMode(QLineEdit.Password)
        credential_button = QPushButton("Verify Credentials")
        credential_button.clicked.connect(self.on_credential_button_clicked)
        line_layout.addWidget(self.pass_edit, 1, 1)
        line_layout.addWidget(credential_button, 2, 1)

        cred_tuple = self.api.readDefaultCredentialsFromConfig()
        self.client_id.insert(str(cred_tuple[0]))
        self.pass_edit.insert(str(cred_tuple[1]))

        box_layout.addItem(line_layout)
        login_group_box.setLayout(box_layout)

        return login_group_box

    def on_credential_button_clicked(self):
        self.api.readCredentialsFromGui(self.client_id.text().strip(),
                                        self.pass_edit.text().strip())
        self.url_edit.setText(self.api.buildAuthUrl())

    def build_logger_window(self):
        logger_group_box = QGroupBox("Log")

        self.logBox = QTextEdit()
        self.logBox.setMinimumSize(150, 300)
        self.logBox.setReadOnly(True)
        self.logBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        bottom_line_layout = QVBoxLayout()
        bottom_line_layout.addWidget(self.logBox, Qt.AlignBottom)
        logger_group_box.setLayout(bottom_line_layout)
        self.message.connect(self.write_to_logbox)

        return logger_group_box

    def write_to_logbox(self, message):
        self.logBox.moveCursor(QTextCursor.End)
        self.logBox.insertPlainText(message + "\n")
        self.logBox.moveCursor(QTextCursor.End)

    def populate_athlete_labels(self):
        athlete_info = self.api.currentAthlete()
        self.name_label.setText(athlete_info.name)
        self.username_label.setText(athlete_info.username)
        self.gender_label.setText(athlete_info.gender)
        self.city_label.setText(athlete_info.city)
        self.country_label.setText(athlete_info.country)

    def on_summary_clicked(self):
        self.api.athleteSummary()

    def on_get_latest_activity_clicked(self):
        self.api.getLatestActivity()

    def on_upload_activities_clicked(self):
        dialog = QDialog()
        box = QDialogButtonBox(dialog)
        box.addButton("Upload", QDialogButtonBox.AcceptRole)
        box.addButton("Cancel", QDialogButtonBox.RejectRole)

        box.accepted.connect(dialog.accept)
        box.rejected.connect(dialog.reject)
        dialog.setMinimumSize(500, 500)
        dialog.setWindowTitle("Upload Activities")
        layout = QVBoxLayout()
        input_layout = QHBoxLayout()

        label = QLabel("Location: ")
        input_push_button = QPushButton("...")
        input_push_button.setMaximumWidth(30)
        input_layout.addWidget(label, Qt.AlignLeft)
        input_layout.addWidget(self.activity_directory_input_box, Qt.AlignLeft)
        input_layout.addWidget(input_push_button, Qt.AlignRight)

        input_push_button.clicked.connect(
            self.open_activity_directory_selection_dialog)

        layout.addLayout(input_layout)

        self.file_list_view.setModel(self.file_model)
        self.file_list_view.setRootIndex(
            self.file_model.index(QDir.currentPath()))

        layout.addWidget(self.file_list_view)
        layout.addWidget(box)

        dialog.setLayout(layout)

        conclusion = dialog.exec()

        if conclusion == QDialog.Accepted:
            self.api.uploadActivitiesFromDirectory(self.file_model.rootPath())

    def activity_dir_selected(self, directoryStr, model):
        model.setRootPath(directoryStr)

    def open_activity_directory_selection_dialog(self):
        dialog = QFileDialog()
        dialog.setFileMode(QFileDialog.DirectoryOnly)
        dirPath = dialog.getExistingDirectory()

        self.file_model.setRootPath(dirPath)
        self.file_list_view.setRootIndex(self.file_model.index(dirPath))
        self.activity_directory_input_box.setText(dirPath)

    def start_gui(self):
        self.app.exec_()
class Chapterize(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_ChapterizeWindow()
        self.ui.setupUi(self)

        self.dir = QDir.homePath()

        self.chaptersTableModel = ChaptersTableModel()
        self.ui.chapterTable.setModel(self.chaptersTableModel)
        self.ui.chapterTable.setItemDelegate(TimestampDelegate())

        self.ui.chapterTable.contextMenuEvent = self.onChapterContextMenu

        self.mp3ListModel = QFileSystemModel()
        self.mp3ListModel.setNameFilters(['*.mp3'])
        self.mp3ListModel.setFilter(QDir.Files)
        self.mp3ListModel.setRootPath(self.dir)
        self.mp3ListModel.setNameFilterDisables(False)

        self.ui.mp3List.setModel(self.mp3ListModel)
        self.ui.mp3List.setRootIndex(self.mp3ListModel.index(self.dir))
        self.ui.mp3List.selectionModel().selectionChanged.connect(self.onMp3Select)

        self.ui.actionChangeDir.triggered.connect(self.onDirectoryChange)
        self.ui.actionExit.triggered.connect(self.close)
        self.ui.actionSave.triggered.connect(self.chaptersTableModel.save)

    @pyqtSlot()
    def onDirectoryChange(self):
        chosen_dir = QFileDialog.getExistingDirectory(self, "Change Directory...", self.dir, QFileDialog.ShowDirsOnly)
        if chosen_dir != '':
            self.dir = chosen_dir
            self.mp3ListModel.setRootPath(self.dir)
            self.ui.mp3List.setRootIndex(self.mp3ListModel.index(self.dir))

    @pyqtSlot(QItemSelection, QItemSelection)
    def onMp3Select(self, selected, deselected):
        sel_idx = selected.indexes()[0]
        pth = self.mp3ListModel.fileInfo(sel_idx).absoluteFilePath()
        self.chaptersTableModel.set_file(pth)

    @pyqtSlot(QContextMenuEvent)
    def onChapterContextMenu(self, e):
        idx = self.ui.chapterTable.indexAt(e.pos())
        row = idx.row()
        if row < 0:
            row = self.chaptersTableModel.rowCount()

        menu = QMenu(self)

        insertAction = QAction('&Insert', menu)
        insertAction.triggered.connect(lambda: self.chaptersTableModel.insertRow(row))

        menu.addAction(insertAction)

        deleteAction = QAction('&Delete', menu)
        deleteAction.triggered.connect(lambda: self.chaptersTableModel.removeRow(row))

        menu.addAction(deleteAction)

        menu.exec_(e.globalPos())
Example #20
0
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.title = 'Whenson\'s Custom INI Merger'
        self.geo = (200, 200, 800, 600)
        self.initGUI()

    def initGUI(self):
        # windows title and geometry
        self.setGeometry(*(self.geo))
        self.setWindowTitle(self.title)
        self.show()

        # master layout and widget definition
        self.layout = QHBoxLayout()

        self.file_box = QVBoxLayout()

        self.list_box = QVBoxLayout()
        self.btn_group = QVBoxLayout()

        # deploy layout
        self.setLayout(self.layout)
        self.layout.addLayout(self.file_box)

        self.layout.addLayout(self.list_box)
        self.layout.addLayout(self.btn_group)

        # file box
        self.file_box.addWidget(QLabel('File Browser'))
        self.folder_tree = QTreeView()
        self.file_box.addWidget(self.folder_tree)
        self.file_list = QTreeView()
        self.file_box.addWidget(self.file_list)

        # list box

        self.normal_list = DropListWidget()
        self.demo_list = DropListWidget()

        self.list_box.addWidget(QLabel('Output File'))
        self.list_box.addWidget(QLineEdit())

        self.list_box.addWidget(QLabel('Module List'))
        self.list_box.addWidget(self.normal_list)

        temp = QHBoxLayout()
        self.up_btn = QPushButton()
        self.up_btn.setText('ᐱ')
        self.down_btn = QPushButton()
        self.down_btn.setText('ᐯ')
        self.list_box.addLayout(temp)
        temp.addWidget(self.up_btn)
        temp.addWidget(self.down_btn)

        self.list_box.addWidget(QLabel('With Demostration'))
        self.list_box.addWidget(self.demo_list)

        # button group
        self.merge_btn = QPushButton('Merge')
        self.btn_group.addWidget(self.merge_btn)

        self.load_btn = QPushButton('Load')
        self.btn_group.addWidget(self.load_btn)

        self.init_file_tree()
        self.init_drag()

    def init_file_tree(self):
        self.dir_model = QFileSystemModel()
        self.dir_model.setRootPath('C:/')
        self.dir_model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        self.folder_tree.setModel(self.dir_model)
        self.folder_tree.hideColumn(1)
        self.folder_tree.hideColumn(2)
        self.folder_tree.hideColumn(3)

        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.NoDotAndDotDot | QDir.Files)
        self.file_list.setModel(self.file_model)
        self.file_list.setRootIndex(self.file_model.setRootPath('C:/'))
        self.file_model.setNameFilters(['*.txt', '*.ini', '*.map'])
        self.file_model.setNameFilterDisables(False)
        self.folder_tree.clicked.connect(self.tree_on_clicked)
        self.file_list.setSelectionMode(QAbstractItemView.ExtendedSelection)

    def init_drag(self):
        self.file_list.setDragEnabled(True)
        self.file_list.setDropIndicatorShown(True)
        # self.file_list.setDefaultDropAction(Qt.MoveAction)

    def tree_on_clicked(self, index):
        path = self.dir_model.fileInfo(index).absoluteFilePath()
        self.file_list.setRootIndex(self.file_model.setRootPath(path))
Example #21
0
class FileManager(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(FileManager, self).__init__(parent)
        self.title = 'PyQt5 file system view - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.default_path = (os.path.join(os.path.expanduser('~'),
                                          'labvcnc/nc_files/examples'))
        self.user_path = (os.path.join('/media'))
        self.currentPath = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files)
        self.model.setNameFilterDisables(False)
        self.model.setNameFilters(["*.ngc", '*.py'])

        self.list = QListView()
        self.list.setModel(self.model)
        self.updateDirectoryView(self.default_path)
        self.list.setWindowTitle("Dir View")
        self.list.resize(640, 480)
        self.list.clicked[QModelIndex].connect(self.clicked)
        self.list.activated.connect(self.load)
        self.list.setAlternatingRowColors(True)

        self.cb = QComboBox()
        self.cb.currentTextChanged.connect(self.filterChanged)
        self.cb.addItems(sorted({'*.ngc', '*.py', '*'}))
        #self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))

        self.button = QPushButton()
        self.button.setText('Media')
        self.button.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button.setToolTip('Jump to Media directory')
        self.button.clicked.connect(self.onMediaClicked)

        self.button2 = QPushButton()
        self.button2.setText('User')
        self.button2.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button2.setToolTip('Jump to labvcnc directory')
        self.button2.clicked.connect(self.onUserClicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.button)
        hbox.addWidget(self.button2)
        hbox.addWidget(self.cb)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.list)
        windowLayout.addLayout(hbox)
        self.setLayout(windowLayout)
        self.show()

    def updateDirectoryView(self, path):
        self.list.setRootIndex(self.model.setRootPath(path))

    def filterChanged(self, text):
        self.model.setNameFilters([text])

    def clicked(self, index):
        # the signal passes the index of the clicked item
        dir_path = self.model.filePath(index)
        if self.model.fileInfo(index).isFile():
            self.currentPath = dir_path
            return
        root_index = self.model.setRootPath(dir_path)
        self.list.setRootIndex(root_index)

    def onMediaClicked(self):
        self.updateDirectoryView(self.user_path)

    def onUserClicked(self):
        self.updateDirectoryView(self.default_path)

    def select_row(self, style):
        style = style.lower()
        selectionModel = self.list.selectionModel()
        row = selectionModel.currentIndex().row()
        self.rows = self.model.rowCount(self.list.rootIndex())

        if style == 'last':
            row = self.rows
        elif style == 'up':
            if row > 0:
                row -= 1
            else:
                row = 0
        elif style == 'down':
            if row < self.rows:
                row += 1
            else:
                row = self.rows
        else:
            return
        top = self.model.index(row, 0, self.list.rootIndex())
        selectionModel.setCurrentIndex(
            top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    def _hal_init(self):
        if self.PREFS_:
            last_path = self.PREFS_.getpref('last_file_path',
                                            self.default_path, str,
                                            'BOOK_KEEPING')
            self.updateDirectoryView(last_path)
            LOG.debug("lAST FILE PATH: {}".format(last_path))
        else:
            LOG.debug("lAST FILE PATH: {}".format(self.default_path))
            self.updateDirectoryView(self.default_path)

    # get current selection and update the path
    # then if the path is good load it into labvcnc
    # record it in the preference file if available
    def load(self):
        row = self.list.selectionModel().currentIndex()
        self.clicked(row)

        fname = self.currentPath
        if fname is None:
            return
        if fname:
            if self.PREFS_:
                self.PREFS_.putpref('last_file_path', fname, str,
                                    'BOOK_KEEPING')
            ACTION.OPEN_PROGRAM(fname)
            STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')

    def up(self):
        self.select_row('up')

    def down(self):
        self.select_row('down')
Example #22
0
class FileManager(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(FileManager, self).__init__(parent)
        self.title = 'Qtvcp File System View'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self._last = 0

        if INFO.PROGRAM_PREFIX is not None:
            self.user_path = os.path.expanduser(INFO.PROGRAM_PREFIX)
        else:
            self.user_path = (os.path.join(os.path.expanduser('~'),
                                           'linuxcnc/nc_files'))
        user = os.path.split(os.path.expanduser('~'))[-1]
        self.media_path = (os.path.join('/media', user))
        temp = [('User', self.user_path), ('Media', self.media_path)]
        self._jumpList = OrderedDict(temp)
        self.currentPath = None
        self.currentFolder = None
        self.PREFS_ = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        pasteBox = QHBoxLayout()
        self.textLine = QLineEdit()
        self.textLine.setToolTip('Current Director/selected File')
        self.pasteButton = QToolButton()
        self.pasteButton.setEnabled(False)
        self.pasteButton.setText('Paste')
        self.pasteButton.setToolTip(
            'Copy file from copy path to current directory/file')
        self.pasteButton.clicked.connect(self.paste)
        self.pasteButton.hide()
        pasteBox.addWidget(self.textLine)
        pasteBox.addWidget(self.pasteButton)

        self.copyBox = QFrame()
        hbox = QHBoxLayout()
        hbox.setContentsMargins(0, 0, 0, 0)
        self.copyLine = QLineEdit()
        self.copyLine.setToolTip('File path to copy from, when pasting')
        self.copyButton = QToolButton()
        self.copyButton.setText('Copy')
        self.copyButton.setToolTip('Record current file as copy path')
        self.copyButton.clicked.connect(self.recordCopyPath)
        hbox.addWidget(self.copyButton)
        hbox.addWidget(self.copyLine)
        self.copyBox.setLayout(hbox)
        self.copyBox.hide()

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files)
        self.model.setNameFilterDisables(False)
        self.model.rootPathChanged.connect(self.folderChanged)

        self.list = QListView()
        self.list.setModel(self.model)
        self.list.resize(640, 480)
        self.list.clicked[QModelIndex].connect(self.listClicked)
        self.list.activated.connect(self._getPathActivated)
        self.list.setAlternatingRowColors(True)
        self.list.hide()

        self.table = QTableView()
        self.table.setModel(self.model)
        self.table.resize(640, 480)
        self.table.clicked[QModelIndex].connect(self.listClicked)
        self.table.activated.connect(self._getPathActivated)
        self.table.setAlternatingRowColors(True)

        header = self.table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
        header.swapSections(1, 3)
        header.setSortIndicator(1, Qt.AscendingOrder)

        self.table.setSortingEnabled(True)
        self.table.setColumnHidden(2, True)  # type
        self.table.verticalHeader().setVisible(False)  # row count header

        self.cb = QComboBox()
        self.cb.currentIndexChanged.connect(self.filterChanged)
        self.fillCombobox(INFO.PROGRAM_FILTERS_EXTENSIONS)
        self.cb.setMinimumHeight(30)
        self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,
                                          QSizePolicy.Fixed))

        self.button2 = QToolButton()
        self.button2.setText('User')
        self.button2.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button2.setMinimumSize(60, 30)
        self.button2.setToolTip(
            'Jump to User directory.\nLong press for Options.')
        self.button2.clicked.connect(self.onJumpClicked)

        self.button3 = QToolButton()
        self.button3.setText('Add Jump')
        self.button3.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button3.setMinimumSize(60, 30)
        self.button3.setToolTip('Add current directory to jump button list')
        self.button3.clicked.connect(self.onActionClicked)

        self.settingMenu = QMenu(self)
        self.button2.setMenu(self.settingMenu)

        hbox = QHBoxLayout()
        hbox.addWidget(self.button2)
        hbox.addWidget(self.button3)
        hbox.insertStretch(2, stretch=0)
        hbox.addWidget(self.cb)

        windowLayout = QVBoxLayout()
        windowLayout.addLayout(pasteBox)
        windowLayout.addWidget(self.copyBox)
        windowLayout.addWidget(self.list)
        windowLayout.addWidget(self.table)
        windowLayout.addLayout(hbox)
        self.setLayout(windowLayout)
        self.show()

    def _hal_init(self):
        if self.PREFS_:
            last_path = self.PREFS_.getpref('last_loaded_directory',
                                            self.user_path, str,
                                            'BOOK_KEEPING')
            LOG.debug("lAST FILE PATH: {}".format(last_path))
            if not last_path == '':
                self.updateDirectoryView(last_path)
            else:
                self.updateDirectoryView(self.user_path)

            # get all the saved jumplist paths
            temp = self.PREFS_.getall('FILEMANAGER_JUMPLIST')
            self._jumpList.update(temp)

        else:
            LOG.debug("lAST FILE PATH: {}".format(self.user_path))
            self.updateDirectoryView(self.user_path)

        # install jump paths into toolbutton menu
        for i in self._jumpList:
            self.addAction(i)

        # set recorded columns sort settings
        self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
        sect = self.SETTINGS_.value('sortIndicatorSection', type=int)
        order = self.SETTINGS_.value('sortIndicatorOrder', type=int)
        self.SETTINGS_.endGroup()
        if not None in (sect, order):
            self.table.horizontalHeader().setSortIndicator(sect, order)

    # when qtvcp closes this gets called
    # record jump list paths
    def _hal_cleanup(self):
        if self.PREFS_:
            for i, key in enumerate(self._jumpList):
                if i in (0, 1):
                    continue
                self.PREFS_.putpref(key, self._jumpList.get(key), str,
                                    'FILEMANAGER_JUMPLIST')

        # record sorted columns
        h = self.table.horizontalHeader()
        self.SETTINGS_.beginGroup("FileManager-{}".format(self.objectName()))
        self.SETTINGS_.setValue('sortIndicatorSection',
                                h.sortIndicatorSection())
        self.SETTINGS_.setValue('sortIndicatorOrder', h.sortIndicatorOrder())
        self.SETTINGS_.endGroup()

    #########################
    # callbacks
    #########################

    # add shown text and hidden filter data from the INI
    def fillCombobox(self, data):
        for i in data:
            self.cb.addItem(i[0], i[1])

    def folderChanged(self, data):
        data = os.path.normpath(data)
        self.currentFolder = data
        self.textLine.setText(data)

    def updateDirectoryView(self, path, quiet=False):
        if os.path.exists(path):
            self.list.setRootIndex(self.model.setRootPath(path))
            self.table.setRootIndex(self.model.setRootPath(path))
        else:
            LOG.debug(
                "Set directory view error - no such path {}".format(path))
            if not quiet:
                STATUS.emit(
                    'error', LOW_ERROR,
                    "File Manager error - No such path: {}".format(path))

    # retrieve selected filter (it's held as QT.userData)
    def filterChanged(self, index):
        userdata = self.cb.itemData(index)
        self.model.setNameFilters(userdata)

    def listClicked(self, index):
        # the signal passes the index of the clicked item
        dir_path = os.path.normpath(self.model.filePath(index))
        if self.model.fileInfo(index).isFile():
            self.currentPath = dir_path
            self.textLine.setText(self.currentPath)
            return
        root_index = self.model.setRootPath(dir_path)
        self.list.setRootIndex(root_index)
        self.table.setRootIndex(root_index)

    def onUserClicked(self):
        self.showUserDir()

    def onMediaClicked(self):
        self.showMediaDir()

    # jump directly to a saved path shown on the button
    def onJumpClicked(self):
        data = self.button2.text()
        if data.upper() == 'MEDIA':
            self.showMediaDir()
        elif data.upper() == 'USER':
            self.showUserDir()
        else:
            temp = self._jumpList.get(data)
            if temp is not None:
                self.updateDirectoryView(temp)
            else:
                STATUS.emit('error', linuxcnc.OPERATOR_ERROR,
                            'file jumopath: {} not valid'.format(data))
                log.debug('file jumopath: {} not valid'.format(data))

    # jump directly to a saved path from the menu
    def jumpTriggered(self, data):
        if data.upper() == 'MEDIA':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip(
                'Jump to Media directory.\nLong press for Options.')
            self.showMediaDir()
        elif data.upper() == 'USER':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip(
                'Jump to User directory.\nLong press for Options.')
            self.showUserDir()
        else:
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip('Jump to directory:\n{}'.format(
                self._jumpList.get(data)))
            self.updateDirectoryView(self._jumpList.get(data))

    # add a jump list path
    def onActionClicked(self):
        i = self.currentFolder
        try:
            self._jumpList[i] = i
        except Exception as e:
            print(e)
        button = QAction(QIcon.fromTheme('user-home'), i, self)
        # weird lambda i=i to work around 'function closure'
        button.triggered.connect(lambda state, i=i: self.jumpTriggered(i))
        self.settingMenu.addAction(button)

    # get current selection and update the path
    # then if the path is good load it into linuxcnc
    # record it in the preference file if available
    def _getPathActivated(self):
        if self.list.isVisible():
            row = self.list.selectionModel().currentIndex()
        else:
            row = self.table.selectionModel().currentIndex()
            self.listClicked(row)

        fname = self.currentPath
        if fname is None:
            return
        if fname:
            self.load(fname)

    def recordCopyPath(self):
        data, isFile = self.getCurrentSelected()
        if isFile:
            self.copyLine.setText(os.path.normpath(data))
            self.pasteButton.setEnabled(True)
        else:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)
            STATUS.emit('error', OPERATOR_ERROR,
                        'Can only copy a file, not a folder')

    def paste(self):
        res = self.copyFile(self.copyLine.text(), self.textLine.text())
        if res:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)

    ########################
    # helper functions
    ########################

    def addAction(self, i):
        axisButton = QAction(QIcon.fromTheme('user-home'), i, self)
        # weird lambda i=i to work around 'function closure'
        axisButton.triggered.connect(lambda state, i=i: self.jumpTriggered(i))
        self.settingMenu.addAction(axisButton)

    def showList(self, state=True):
        if state:
            self.table.hide()
            self.list.show()
        else:
            self.table.show()
            self.list.hide()

    def showTable(self, state=True):
        self.showList(not state)

    def showCopyControls(self, state):
        if state:
            self.copyBox.show()
            self.pasteButton.show()
        else:
            self.copyBox.hide()
            self.pasteButton.hide()

    def showMediaDir(self, quiet=False):
        self.updateDirectoryView(self.media_path, quiet)

    def showUserDir(self, quiet=False):
        self.updateDirectoryView(self.user_path, quiet)

    def copyFile(self, s, d):
        try:
            shutil.copy(s, d)
            return True
        except Exception as e:
            LOG.error("Copy file error: {}".format(e))
            STATUS.emit('error', OPERATOR_ERROR,
                        "Copy file error: {}".format(e))
            return False

    @pyqtSlot(float)
    @pyqtSlot(int)
    def scroll(self, data):
        if data > self._last:
            self.up()
        elif data < self._last:
            self.down()
        self._last = data

    # moves the selection up
    # used with MPG scrolling
    def up(self):
        self.select_row('up')

    # moves the selection down
    # used with MPG scrolling
    def down(self):
        self.select_row('down')

    def select_row(self, style='down'):
        style = style.lower()
        if self.list.isVisible():
            i = self.list.rootIndex()
            selectionModel = self.list.selectionModel()
        else:
            i = self.table.rootIndex()
            selectionModel = self.table.selectionModel()

        row = selectionModel.currentIndex().row()
        self.rows = self.model.rowCount(i)

        if style == 'last':
            row = self.rows
        elif style == 'up':
            if row > 0:
                row -= 1
            else:
                row = 0
        elif style == 'down':
            if row < self.rows - 1:
                row += 1
            else:
                row = self.rows - 1
        else:
            return
        top = self.model.index(row, 0, i)
        selectionModel.setCurrentIndex(
            top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    # returns the current highlighted (selected) path as well as
    # whether it's a file or not.
    def getCurrentSelected(self):
        if self.list.isVisible():
            selectionModel = self.list.selectionModel()
        else:
            selectionModel = self.table.selectionModel()
        index = selectionModel.currentIndex()
        dir_path = os.path.normpath(self.model.filePath(index))
        if self.model.fileInfo(index).isFile():
            return (dir_path, True)
        else:
            return (dir_path, False)

    # This can be class patched to do something else
    def load(self, fname=None):
        try:
            if fname is None:
                self._getPathActivated()
                return
            self.recordBookKeeping()
            ACTION.OPEN_PROGRAM(fname)
            STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')
        except Exception as e:
            LOG.error("Load file error: {}".format(e))
            STATUS.emit('error', NML_ERROR, "Load file error: {}".format(e))

    # This can be class patched to do something else
    def recordBookKeeping(self):
        fname = self.currentPath
        if fname is None:
            return
        if self.PREFS_:
            self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(),
                                str, 'BOOK_KEEPING')
            self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING')
Example #23
0
class Ui(QtWidgets.QMainWindow):
    """Main Class of the simple DMS user interface.

    Arguments:
        nothing

    Returns:
        nothing

    """
    def __init__(self):
        """Initialize variables and connect actions with functions."""
        super(Ui, self).__init__()
        uic.loadUi(os.path.join(CURRDIR, "ui", "main_simpledms.ui"), self)
        self.loadpref()
        self.rules = rules.Rules(self.pref["dmsroot"])
        self.currentselectedrulesfolder = None
        self.currentselectedsearchfolder = None
        self.current_monitorfolder_index = None
        self.parent_index = None
        self.filemodelmonitor = QFileSystemModel()
        self.rulesfoldermodel = QFileSystemModel()
        self.resultfoldermodel = QFileSystemModel()
        self.searchfoldermodel = QFileSystemModel()
        self.textEdit_tags = MyTextEdit(self.textEdit_tags)
        self.updateui_settings()
        self.updateui_pdfrename()
        self.show()

        # Connect Widget Toolbar Actions
        self.actionScan.triggered.connect(self.select_widget)
        self.actionPdf.triggered.connect(self.select_widget)
        self.actionSettings.triggered.connect(self.select_widget)
        self.actionAbout.triggered.connect(self.show_ui_about)
        self.actionExit.triggered.connect(self.select_widget)

        # Connect Preferences
        self.pushButton_setmonitorfolder.clicked.connect(
            self.browse_monitor_folder)
        self.pushButton_setdmsroot.clicked.connect(self.browse_dms_root)
        self.treeView_rulesfolders.clicked.connect(self.rulesfolderselected)
        self.treeView_rules.doubleClicked.connect(self.ruledoubleclicked)
        self.pushButton_addrule.clicked.connect(self.addruleclicked)
        self.pushButton_deleterule.clicked.connect(self.deleteruleclicked)

        # Connect page pdf renaming
        self.listView_monitorfiles.clicked.connect(
            self.listView_monitorfiles_clicked)

        self.treeView_output.clicked.connect(self.treeView_output_clicked)
        self.pushButton_ok.clicked.connect(self.pushButton_ok_clicked)
        self.listView_monitorfiles.doubleClicked.connect(
            self.listView_monitorfiles_doubleclicked)
        self.lineEdit_outputfilename.textChanged.connect(self.readyforstorage)
        self.pushButton_addDate.clicked.connect(
            self.pushButton_addDate_clicked)

    # -------- Settings page -----------
    def rulesfolderselected(self, signal):
        """Update ui if a folder in settings -> rules is selected."""
        self.currentselectedrulesfolder = self.rulesfoldermodel.filePath(
            signal)
        self.updateui_settings()
        self.pushButton_addrule.setEnabled(True)

    def ruledoubleclicked(self):
        """Open ui for rule adaption of double clicked rule."""
        selectedrule = self.treeView_rules_model.itemData(
            self.treeView_rules.selectedIndexes()[0])
        rule = self.rules.returnruleofkeywords([selectedrule[0]],
                                               self.currentselectedrulesfolder)
        rulesdialog = MyRulesWidget(
            keywords=rule[0][1],
            booleanoperator=rule[0][2],
            tags=rule[0][3],
            doctitle=rule[0][4],
            indexertags=set(self.rules.returnalltags()),
        )
        rulesdialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        if rulesdialog.exec_():
            self.rules.replacerule(
                rule[0][0],
                rulesdialog.keywords,
                rulesdialog.booleanoperator,
                rulesdialog.tags,
                rulesdialog.doctitle,
                self.currentselectedrulesfolder,
            )
            self.updateui_settings()

    def deleteruleclicked(self):
        """Delete selected rule and update ui."""
        if self.treeView_rules.selectedIndexes():
            selectedrule = self.treeView_rules_model.itemData(
                self.treeView_rules.selectedIndexes()[0])
            self.rules.delrule([selectedrule[0]],
                               self.currentselectedrulesfolder)
            self.updateui_settings()

    def addruleclicked(self):
        """Add rule to database if it does not exist yet."""
        rulesdialog = MyRulesWidget(
            indexertags=set(self.rules.returnalltags()))
        rulesdialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        if rulesdialog.exec_():
            if self.rules.returnruleofkeywords(
                    rulesdialog.keywords, self.currentselectedrulesfolder):
                QtWidgets.QMessageBox.information(
                    self, "Error",
                    "A rule with these keywords already exists.")
            else:
                self.rules.addrule(
                    rulesdialog.keywords,
                    rulesdialog.booleanoperator,
                    rulesdialog.tags,
                    rulesdialog.doctitle,
                    self.currentselectedrulesfolder,
                )
                self.updateui_settings()

    def loadpref(self):
        """Load preferences: root of dms and monitorfolder."""
        if os.path.isfile("pref.json"):
            with open("pref.json") as f:
                self.pref = json.load(f)
                if not os.path.isdir(self.pref["dmsroot"]):
                    os.makedirs(self.pref["dmsroot"])
                    QtWidgets.QMessageBox.information(
                        self, "Attention!",
                        "Stored path of dmsroot does not exist")
                if not os.path.isdir(self.pref["monitorfolder"]):
                    os.makedirs(self.pref["monitorfolder"])
                    QtWidgets.QMessageBox.information(
                        self,
                        "Attention!",
                        "Stored path of monitorfolder does not exist.",
                    )
        else:
            # If pref.json file does not exist
            if not os.path.isdir(
                    os.path.join(os.path.expanduser("~"), "paperwork")):
                os.makedirs(os.path.join(os.path.expanduser("~"), "paperwork"))
                QtWidgets.QMessageBox.information(
                    self,
                    "Attention!",
                    "Standard path for file cabinet"
                    "was created. If "
                    "needed, please change.",
                )

            if not os.path.isdir(
                    os.path.join(os.path.expanduser("~"), "paperwork_open")):
                os.makedirs(
                    os.path.join(os.path.expanduser("~"), "paperwork_open"))
                QtWidgets.QMessageBox.information(
                    self,
                    "Attention!",
                    "Standard path for monitor folder"
                    "was created. If "
                    "needed, please change.",
                )

            self.pref = {
                "dmsroot":
                os.path.join(os.path.expanduser("~"), "paperwork"),
                "monitorfolder":
                os.path.join(os.path.expanduser("~"), "paperwork_open"),
            }
            self.savepref()

    def savepref(self):
        """Save preferences to pref.json."""
        with open("pref.json", "w") as f:
            json.dump(self.pref, f)

    def browse_monitor_folder(self):
        """Select monitor folder."""
        # execute getExistingDirectory dialog and set the directory variable to be equal
        # to the user selected directory
        directory = QFileDialog.getExistingDirectory(
            self, "Select a monitor folder with files to be "
            "processed/imported")
        # if user didn't pick a directory don't continue
        if directory:
            self.pref["monitorfolder"] = directory
            self.savepref()
            self.updateui_settings()
            self.updateui_pdfrename()

    def browse_dms_root(self):
        """Select dms root folder."""
        # execute getExistingDirectory dialog and set the directory variable to be equal
        # to the user selected directory
        directory = QFileDialog.getExistingDirectory(
            self, "Select a root directory of the filing cabinet")
        # if user didn't pick a directory don't continue
        if directory:
            if not len(self.rules.returnallrules()) == 0:
                result = QtWidgets.QMessageBox.question(
                    self,
                    "Attention",
                    "If the root directory is changed, the current rules are "
                    "deleted! Are you sure and want to proceed?",
                )
                if result == QtWidgets.QMessageBox.No:
                    return
            self.rules.resetdb(directory)
            self.pref["dmsroot"] = directory
            self.savepref()
            # self.indexer.__init__(directory)
            self.updateui_settings()

    def updateui_settings(self):
        """Update ui elements of settings page."""
        self.label_monitorfolder.setText(self.pref["monitorfolder"])
        self.label_monitordir.setText(
            ".." + os.sep +
            os.path.basename(os.path.normpath(self.pref["monitorfolder"])))
        self.label_dmsroot.setText(self.pref["dmsroot"])
        self.rulesfoldermodel.setRootPath(self.pref["dmsroot"])
        self.rulesfoldermodel.setFilter(QtCore.QDir.NoDotAndDotDot
                                        | QtCore.QDir.Dirs)
        self.treeView_rulesfolders.setModel(self.rulesfoldermodel)
        self.treeView_rulesfolders.setRootIndex(
            self.rulesfoldermodel.index(self.pref["dmsroot"]))
        self.treeView_rulesfolders.hideColumn(1)
        self.treeView_rulesfolders.hideColumn(2)
        self.treeView_rulesfolders.hideColumn(3)
        self.treeView_rules_model = QtGui.QStandardItemModel()
        self.treeView_rules.setModel(self.treeView_rules_model)
        self.treeView_rules_model.setHorizontalHeaderLabels(
            ["Rules (keywords)"])
        rulesoffolder = self.rules.returnrulesoffolder(
            self.currentselectedrulesfolder)
        if rulesoffolder is not None:
            for i in rulesoffolder:
                rule = QtGui.QStandardItem(i[1])
                self.treeView_rules_model.appendRow(rule)

    # -------- Action Bar -----------
    def select_widget(self):
        """Select index of stacked widget based on toolbox actions."""
        sender = self.sender()
        if sender.text() == "Scan":
            pass
        elif sender.text() == "Import":
            self.stackedWidget.setCurrentIndex(0)
        elif sender.text() == "Settings":
            self.stackedWidget.setCurrentIndex(1)
        elif sender.text() == "Exit":
            QtWidgets.QApplication.instance().quit()

    def show_ui_about(self):
        """Show about page."""
        dialog = QDialog()
        dialog.ui = ui.about.Ui_Dialog()
        dialog.ui.setupUi(dialog)
        dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        dialog.exec_()

    # -------- pdf renaming page -----------
    def listView_monitorfiles_clicked(self, index):
        """Show preview of pdf, extract date and words and propose tags and directory in dms root."""
        self.current_monitorfolder_index = index
        pdffile = os.path.join(self.pref["monitorfolder"],
                               self.filemodelmonitor.itemData(index)[0])

        # Check if file is really a pdf.
        if "PDF" not in magic.from_file(pdffile):
            QtWidgets.QMessageBox.information(self, "Attention!",
                                              "Document is not a pdf.")
            return

        self.listView_monitorfiles.setEnabled(False)
        self.setCursor(QtCore.Qt.BusyCursor)
        self.statusbar.showMessage("Reading pdf...")

        self.pdfhandler = PdfHandler(pdffile)
        self.pdfhandler.thumbheight = (
            self.listView_monitorfiles.frameGeometry().height())
        self.pdfhandler.createthumbnail()

        pixmap = QtGui.QPixmap(self.pdfhandler.thumbfpath)
        self.thumbnail.setPixmap(pixmap)
        self.thumbnail.resize(pixmap.width(), pixmap.height())
        self.analyze_text()
        if self.readyforstorage():
            self.statusbar.showMessage("Automatic renaming and moving file...")
            # self.pushButton_ok_clicked()
        self.listView_monitorfiles.setEnabled(True)
        self.setCursor(QtCore.Qt.ArrowCursor)
        self.statusbar.showMessage("Ready")

    def listView_monitorfiles_doubleclicked(self, index):
        """Open doubleclicked file.

        Arguments:
            index: index from pyqt5 listview which element was clicked.

        Returns:
            nothing

        """
        file2open = os.path.join(self.pref["monitorfolder"],
                                 self.filemodelmonitor.itemData(index)[0])
        webbrowser.open(file2open)

    def listWidget_pdfthumbnails_doubleclicked(self):
        """Open file in browser."""
        file2open = self.pdfhandler.filepath
        if os.path.isfile(file2open):
            webbrowser.open(file2open)
        else:
            QtWidgets.QMessageBox.information(self, "Attention!",
                                              "File does not exist!")

    def analyze_text(self):
        """Analyze the found text."""
        text = self.pdfhandler.gettext()
        dateext = DateExtractor(text)
        date = dateext.getdate()
        # If Date not found in text, search for date in filename
        if not date:
            dateext = DateExtractor(self.pdfhandler.filepath)
            date = dateext.getdate()
        result = self.rules.applyrule(text)

        if result["doctitle"] is not None:
            if date is not None:
                self.lineEdit_outputfilename.setText(
                    date.strftime("%Y-%m-%d") + " " + result["doctitle"])
            else:
                self.lineEdit_outputfilename.setText(result["doctitle"])
        else:
            if date is not None:
                self.lineEdit_outputfilename.setText(
                    date.strftime("%Y-%m-%d") + " ")
            else:
                self.lineEdit_outputfilename.clear()

        if result["tags"] is not None:
            self.textEdit_tags.setText(result["tags"])
        else:
            self.textEdit_tags.clear()

        self.destination = result["destination"]

        self.treeView_output.setCurrentIndex(
            self.resultfoldermodel.index(self.destination))

        if not self.readyforstorage():
            self.lineEdit_outputfilename.setFocus()
        else:
            self.pushButton_ok.setFocus()

        self.statusbar.showMessage("Ready")
        self.setCursor(QtCore.Qt.ArrowCursor)

    def readyforstorage(self) -> bool:
        """Check if file infos like date, text, tags and folder are set."""
        if (self.lineEdit_outputfilename.text() and re.match(
                r"(\d{4}-\d{2}-\d{2}\s\S+)",
                self.lineEdit_outputfilename.text(),
                re.I | re.UNICODE,
        ) and self.resultfoldermodel.filePath(
                self.treeView_output.currentIndex())):
            self.pushButton_ok.setEnabled(True)
            return True
        else:
            self.pushButton_ok.setEnabled(False)
            return False

    def pushButton_ok_clicked(self):
        """Store document with new metadata in target directory."""
        self.setCursor(QtCore.Qt.BusyCursor)
        self.statusbar.showMessage("Storing...")
        doctitle = self.lineEdit_outputfilename.text()[11:]
        date = self.lineEdit_outputfilename.text()[0:10]
        tags = self.textEdit_tags.toPlainText()
        tags = tags.strip(", ")
        path = self.resultfoldermodel.filePath(
            self.treeView_output.currentIndex())
        self.pdfhandler.update_and_move(path, doctitle, tags, date)
        self.updateui_pdfrename()
        self.setCursor(QtCore.Qt.ArrowCursor)
        row = self.current_monitorfolder_index.row()
        n_elements = self.filemodelmonitor.rowCount(self.parent_index)
        if row + 1 < n_elements and n_elements > 0:
            self.listView_monitorfiles_clicked(
                self.current_monitorfolder_index.sibling(row + 1, 0))
        if row + 1 >= n_elements and n_elements > 1:
            self.listView_monitorfiles_clicked(
                self.current_monitorfolder_index.sibling(row - 1, 0))
        self.updateui_pdfrename()
        self.statusbar.showMessage("ready")

    def pushButton_addDate_clicked(self):
        """Add current date to document name field."""
        if self.lineEdit_outputfilename.text():
            if re.match(r"(\d{4}-\d{2}-\d{2})",
                        self.lineEdit_outputfilename.text()):
                text = (str(datetime.date.today()) +
                        self.lineEdit_outputfilename.text()[10:])
                self.lineEdit_outputfilename.setText(text)
            else:
                self.lineEdit_outputfilename.setText(
                    str(datetime.date.today()) + " ")
        else:
            self.lineEdit_outputfilename.setText(
                str(datetime.date.today()) + " ")
        self.lineEdit_outputfilename.setFocus()

    def treeView_output_clicked(self):
        """Check if all input is available after the target directory was selected."""
        self.readyforstorage()

    def updateui_pdfrename(self):
        """Update ui of page pdfrename."""
        self.filemodelmonitor.setFilter(QtCore.QDir.NoDotAndDotDot
                                        | QtCore.QDir.Files)
        self.filemodelmonitor.setNameFilters(["*.pdf"])
        self.filemodelmonitor.setNameFilterDisables(False)
        self.parent_index = self.filemodelmonitor.setRootPath(
            self.pref["monitorfolder"])
        self.listView_monitorfiles.setModel(self.filemodelmonitor)
        self.listView_monitorfiles.setRootIndex(
            self.filemodelmonitor.index(self.pref["monitorfolder"]))
        self.resultfoldermodel.setRootPath(self.pref["dmsroot"])
        self.resultfoldermodel.setFilter(QtCore.QDir.NoDotAndDotDot
                                         | QtCore.QDir.Dirs)
        self.treeView_output.setModel(self.resultfoldermodel)
        self.treeView_output.setRootIndex(
            self.resultfoldermodel.index(self.pref["dmsroot"]))
        self.treeView_output.hideColumn(1)
        self.treeView_output.hideColumn(2)
        self.treeView_output.hideColumn(3)
        indexertags = set(self.rules.returnalltags())
        completer = MyDictionaryCompleter(myKeywords=indexertags)
        self.textEdit_tags.setCompleter(completer)
Example #24
0
class FileChooser(QWidget):
    fileOpened = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        # TODO: migrate to FolderComboBox?
        self.folderBox = QComboBox(self)
        self.explorerTree = FileTreeView(self)
        self.explorerTree.doubleClickCallback = self._fileOpened
        self.explorerModel = QFileSystemModel(self)
        self.explorerModel.setFilter(QDir.AllDirs | QDir.Files
                                     | QDir.NoDotAndDotDot)
        self.explorerModel.setNameFilters(["*.py"])
        self.explorerModel.setNameFilterDisables(False)
        self.explorerTree.setModel(self.explorerModel)
        for index in range(1, self.explorerModel.columnCount()):
            self.explorerTree.hideColumn(index)
        self.setCurrentFolder()
        self.folderBox.currentIndexChanged[int].connect(
            self.updateCurrentFolder)

        layout = QVBoxLayout(self)
        layout.addWidget(self.folderBox)
        layout.addWidget(self.explorerTree)
        layout.setContentsMargins(5, 5, 0, 0)

    def _fileOpened(self, modelIndex):
        path = self.explorerModel.filePath(modelIndex)
        if os.path.isfile(path):
            self.fileOpened.emit(path)

    def currentFolder(self):
        return self.explorerModel.rootPath()

    def setCurrentFolder(self, path=None):
        if path is None:
            app = QApplication.instance()
            path = app.getScriptsDirectory()
        else:
            assert os.path.isdir(path)
        self.explorerModel.setRootPath(path)
        self.explorerTree.setRootIndex(self.explorerModel.index(path))
        self.folderBox.blockSignals(True)
        self.folderBox.clear()
        style = self.style()
        dirIcon = style.standardIcon(style.SP_DirIcon)
        self.folderBox.addItem(dirIcon, os.path.basename(path))
        self.folderBox.insertSeparator(1)
        self.folderBox.addItem(self.tr("Browse…"))
        self.folderBox.setCurrentIndex(0)
        self.folderBox.blockSignals(False)

    def updateCurrentFolder(self, index):
        if index < self.folderBox.count() - 1:
            return
        path = QFileDialog.getExistingDirectory(self,
                                                self.tr("Choose Directory"),
                                                self.currentFolder(),
                                                QFileDialog.ShowDirsOnly)
        if path:
            QSettings().setValue("scripting/path", path)
            self.setCurrentFolder(path)
Example #25
0
class FileManager(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(FileManager, self).__init__(parent)
        self.title = 'Qtvcp File System View'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.media_path = (os.path.join(os.path.expanduser('~'),
                                        'linuxcnc/nc_files'))
        user = os.path.split(os.path.expanduser('~'))[-1]
        self.user_path = (os.path.join('/media', user))
        self.currentPath = None
        self.currentFolder = None
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        pasteBox = QHBoxLayout()
        self.textLine = QLineEdit()
        self.textLine.setToolTip('Current Director/selected File')
        self.pasteButton = QToolButton()
        self.pasteButton.setEnabled(False)
        self.pasteButton.setText('Paste')
        self.pasteButton.setToolTip(
            'Copy file from copy path to current directory/file')
        self.pasteButton.clicked.connect(self.paste)
        self.pasteButton.hide()
        pasteBox.addWidget(self.textLine)
        pasteBox.addWidget(self.pasteButton)

        self.copyBox = QFrame()
        hbox = QHBoxLayout()
        hbox.setContentsMargins(0, 0, 0, 0)
        self.copyLine = QLineEdit()
        self.copyLine.setToolTip('File path to copy from, when pasting')
        self.copyButton = QToolButton()
        self.copyButton.setText('Copy')
        self.copyButton.setToolTip('Record current file as copy path')
        self.copyButton.clicked.connect(self.recordCopyPath)
        hbox.addWidget(self.copyButton)
        hbox.addWidget(self.copyLine)
        self.copyBox.setLayout(hbox)
        self.copyBox.hide()

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files)
        self.model.setNameFilterDisables(False)
        self.model.rootPathChanged.connect(self.folderChanged)

        self.list = QListView()
        self.list.setModel(self.model)
        self.updateDirectoryView(self.media_path)
        self.list.resize(640, 480)
        self.list.clicked[QModelIndex].connect(self.listClicked)
        self.list.activated.connect(self._getPathActivated)
        self.list.setAlternatingRowColors(True)

        self.cb = QComboBox()
        self.cb.currentIndexChanged.connect(self.filterChanged)
        self.fillCombobox(INFO.PROGRAM_FILTERS_EXTENSIONS)
        self.cb.setMinimumHeight(30)
        self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed,
                                          QSizePolicy.Fixed))

        self.button2 = QToolButton()
        self.button2.setText('Media')
        self.button2.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button2.setMinimumSize(60, 30)
        self.button2.setToolTip('Jump to Media directory')
        self.button2.clicked.connect(self.onJumpClicked)

        SettingMenu = QMenu(self)
        self.settingMenu = SettingMenu
        for i in ('Media', 'User'):
            axisButton = QAction(QIcon.fromTheme('user-home'), i, self)
            # weird lambda i=i to work around 'function closure'
            axisButton.triggered.connect(
                lambda state, i=i: self.jumpTriggered(i))
            SettingMenu.addAction(axisButton)
        self.button2.setMenu(SettingMenu)

        self.button3 = QToolButton()
        self.button3.setText('Add Jump')
        self.button3.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button3.setMinimumSize(60, 30)
        self.button3.setToolTip('Add current directory to jump button list')
        self.button3.clicked.connect(self.onActionClicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.button2)
        hbox.addWidget(self.button3)
        hbox.insertStretch(2, stretch=0)
        hbox.addWidget(self.cb)

        windowLayout = QVBoxLayout()
        windowLayout.addLayout(pasteBox)
        windowLayout.addWidget(self.copyBox)
        windowLayout.addWidget(self.list)
        windowLayout.addLayout(hbox)
        self.setLayout(windowLayout)
        self.show()

    def _hal_init(self):
        if self.PREFS_:
            last_path = self.PREFS_.getpref('last_loaded_directory',
                                            self.media_path, str,
                                            'BOOK_KEEPING')
            self.updateDirectoryView(last_path)
            LOG.debug("lAST FILE PATH: {}".format(last_path))
        else:
            LOG.debug("lAST FILE PATH: {}".format(self.media_path))
            self.updateDirectoryView(self.media_path)

    #########################
    # callbacks
    #########################

    # add shown text and hidden filter data from the INI
    def fillCombobox(self, data):
        for i in data:
            self.cb.addItem(i[0], i[1])

    def folderChanged(self, data):
        self.currentFolder = data
        self.textLine.setText(data)

    def updateDirectoryView(self, path):
        self.list.setRootIndex(self.model.setRootPath(path))

    # retrieve selected filter (it's held as QT.userData)
    def filterChanged(self, index):
        userdata = self.cb.itemData(index)
        self.model.setNameFilters(userdata)

    def listClicked(self, index):
        # the signal passes the index of the clicked item
        dir_path = self.model.filePath(index)
        if self.model.fileInfo(index).isFile():
            self.currentPath = dir_path
            self.textLine.setText(self.currentPath)
            return
        root_index = self.model.setRootPath(dir_path)
        self.list.setRootIndex(root_index)

    def onUserClicked(self):
        self.showUserDir()

    def onMediaClicked(self):
        self.showMediaDir()

    def onJumpClicked(self):
        data = self.button2.text()
        if data == 'Media':
            self.showMediaDir()
        elif data == 'User':
            self.showUserDir()
        else:
            self.updateDirectoryView(self.button2.text())

    def jumpTriggered(self, data):
        if data == 'Media':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip('Jump to Media directory')
            self.showMediaDir()
        elif data == 'User':
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip('Jump to User directory')
            self.showUserDir()
        else:
            self.button2.setText('{}'.format(data))
            self.button2.setToolTip('Jump to directory: {}'.format(data))
            self.updateDirectoryView(self.button2.text())

    def onActionClicked(self):
        i = self.currentFolder
        button = QAction(QIcon.fromTheme('user-home'), i, self)
        # weird lambda i=i to work around 'function closure'
        button.triggered.connect(lambda state, i=i: self.jumpTriggered(i))
        self.settingMenu.addAction(button)

    # get current selection and update the path
    # then if the path is good load it into linuxcnc
    # record it in the preference file if available
    def _getPathActivated(self):
        row = self.list.selectionModel().currentIndex()
        self.listClicked(row)

        fname = self.currentPath
        if fname is None:
            return
        if fname:
            self.load(fname)

    def recordCopyPath(self):
        data, isFile = self.getCurrentSelected()
        if isFile:
            self.copyLine.setText(os.path.normpath(data))
            self.pasteButton.setEnabled(True)
        else:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)
            STATUS.emit('error', OPERATOR_ERROR,
                        'Can only copy a file, not a folder')

    def paste(self):
        res = self.copyFile(self.copyLine.text(), self.textLine.text())
        if res:
            self.copyLine.setText('')
            self.pasteButton.setEnabled(False)

    ########################
    # helper functions
    ########################

    def showCopyControls(self, state):
        if state:
            self.copyBox.show()
            self.pasteButton.show()
        else:
            self.copyBox.hide()
            self.pasteButton.hide()

    def showMediaDir(self):
        self.updateDirectoryView(self.user_path)

    def showUserDir(self):
        self.updateDirectoryView(self.media_path)

    def copyFile(self, s, d):
        try:
            shutil.copy(s, d)
            return True
        except Exception as e:
            LOG.error("Copy file error: {}".format(e))
            STATUS.emit('error', OPERATOR_ERROR,
                        "Copy file error: {}".format(e))
            return False

    # moves the selection up
    # used with MPG scrolling
    def up(self):
        self.select_row('up')

    # moves the selection down
    # used with MPG scrolling
    def down(self):
        self.select_row('down')

    def select_row(self, style='down'):
        style = style.lower()
        selectionModel = self.list.selectionModel()
        row = selectionModel.currentIndex().row()
        self.rows = self.model.rowCount(self.list.rootIndex())

        if style == 'last':
            row = self.rows
        elif style == 'up':
            if row > 0:
                row -= 1
            else:
                row = 0
        elif style == 'down':
            if row < self.rows:
                row += 1
            else:
                row = self.rows
        else:
            return
        top = self.model.index(row, 0, self.list.rootIndex())
        selectionModel.setCurrentIndex(
            top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    # returns the current highlighted (selected) path as well as
    # whether it's a file or not.
    def getCurrentSelected(self):
        selectionModel = self.list.selectionModel()
        index = selectionModel.currentIndex()
        dir_path = self.model.filePath(index)
        if self.model.fileInfo(index).isFile():
            return (dir_path, True)
        else:
            return (dir_path, False)

    # This can be class patched to do something else
    def load(self, fname=None):
        if fname is None:
            self._getPathActivated()
            return
        self.recordBookKeeping()
        ACTION.OPEN_PROGRAM(fname)
        STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')

    # This can be class patched to do something else
    def recordBookKeeping(self):
        fname = self.currentPath
        if fname is None:
            return
        if self.PREFS_:
            self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(),
                                str, 'BOOK_KEEPING')
            self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING')
Example #26
0
class Explorer(QWidget):
    def __init__(self, rootdir=QDir.rootPath()):
        QWidget.__init__(self)
        self.treeview = QTreeView()
        self.listview = QListView()
        self.path = rootdir
        self.filename = ''
        self.filepath = rootdir
        self.canvas = None
        self.col_selector = None

        self.header = ''
        self.xcol = [1]
        self.ycol = [1]
        self.ncols = 0

        self.dirModel = QFileSystemModel()
        self.dirModel.setRootPath(self.path)
        self.dirModel.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)

        self.fileModel = QFileSystemModel()
        self.fileModel.setRootPath(self.filepath)
        self.fileModel.setFilter(QDir.NoDotAndDotDot | QDir.Files)
        self.fileModel.setNameFilters(['*.txt'])
        self.fileModel.setNameFilterDisables(0)

        self.treeview.setModel(self.dirModel)
        self.listview.setModel(self.fileModel)
        for i in [1, 2, 3]: self.treeview.setColumnHidden(i, True)
        self.treeview.setHeaderHidden(True)

        self.treeview.setRootIndex(self.dirModel.index(self.path))
        self.listview.setRootIndex(self.fileModel.index(self.path))

        self.treeview.clicked.connect(self.on_clicked)
        self.listview.selectionModel().currentChanged.connect(self.file_selected)
        self.listview.selectionModel().currentChanged.connect(lambda: self.canvas.update_plot(self))
        self.listview.selectionModel().currentChanged.connect(lambda: self.col_selector.update_range(self.ncols))

    def on_clicked(self, index):
        self.path = self.dirModel.fileInfo(index).absoluteFilePath()
        self.listview.setRootIndex(self.fileModel.setRootPath(self.path))

    def file_selected(self, index):
        self.filename = self.fileModel.fileName(index)
        self.filepath = self.fileModel.filePath(index)
        self.load_file()

    def load_file(self):
        try:
            if self.filepath.endswith('.txt'):
                with open(self.filepath, 'r') as file:
                        self.header, self.xcol, self.ycol = '', [], []
                        for ln in file:
                            if ln.startswith('#'):
                                self.header += ln[2:]
                            else:
                                cols = ln.split('\t')
                                self.xcol.append(float(cols[0]))
                                self.ycol.append(float(cols[self.col_selector.sp.value()]))
                        self.ncols = len(cols)
                        self.col_selector.update_range(self.ncols)
        except:
            self.header, self.xcol, self.ycol = '', [0], [0]

    def update_rootdir(self, rootdir):
        self.path = rootdir
        self.treeview.setRootIndex(self.dirModel.index(self.path))
        self.listview.setRootIndex(self.fileModel.index(self.path))
Example #27
0
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # style window 
        self.setWindowTitle("Waifu File Sort")
        app_icon = QtGui.QIcon()
        app_icon.addFile("./icons/waifu_sort.png", QtCore.QSize(256, 256))
        self.setWindowIcon(app_icon)

        # store important data
        self.path_hotkey_dict = {}
        self.hotkey_path_dict = {}
        self._shortcut_list = []
        self.undo_list = []
        self.delete_folder = str(pathlib.Path(find_data_file("delete")))
        self.current_file_folder = ""
        self.pic_ext_list = [".jpg", ".png", ".webp", ".JPEG", ".PNG"]
        self.default_palette = QtGui.QGuiApplication.palette()

        # initialize source directory
        self.model = QFileSystemModel()
        self.model.setNameFilters(
            ["*.jpg", "*.png", "*.webp", "*.JPEG", "*.PNG"])
        self.model.setNameFilterDisables(False)
        self.ui.treeView.setModel(self.model)
        self.ui.treeView.setSelectionMode(QAbstractItemView.SingleSelection)
        self.ui.tableWidget.setSelectionMode(
            QtWidgets.QAbstractItemView.SingleSelection
        )
        self.ui.treeView.selectionModel().selectionChanged.connect(
            self.update_image_label
        )

        # hotkeys
        self.delShortcut = QShortcut(QKeySequence("Delete"), self)
        self.delShortcut.activated.connect(
            partial(self.move_cb, None, self.delete_folder)
        )

        self.undoShortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
        self.undoShortcut.activated.connect(self.undo_cb)


        # callbacks init
        self.ui.browseBtn.clicked.connect(self.browse_source_click)
        self.ui.addDest.clicked.connect(self.add_dest_to_table)
        self.ui.removeDest.clicked.connect(self.remove_destination)
        self.ui.actionAbout.triggered.connect(self.show_about_dialog)
        self.ui.actionSave_Preset.triggered.connect(self.save_preset_cb)
        self.ui.actionLoad_Preset.triggered.connect(self.load_preset_cb)
        self.ui.actionClear_Delete_Folder.triggered.connect(self.clear_deleted_folder)
        self.ui.unmoveBtn.clicked.connect(self.undo_cb)
        self.ui.checkDeletedBtn.clicked.connect(self.check_deleted_btn_cb)
        self.ui.actionFancy.triggered.connect(self.set_fancy_style)
        self.ui.actionLight.triggered.connect(self.set_light_style)
        self.ui.actionDark.triggered.connect(self.set_dark_style)
        self.ui.comboMode.currentTextChanged.connect(self.change_file_type)
        self.ui.actionOrange.triggered.connect(self.set_orange_style)
        self.ui.actionRemove_Duplicates.triggered.connect(
            self.remove_duplicate_pictures)
        self.ui.actionRemove_Duplicates_Recursively.triggered.connect(
            partial(self.remove_duplicate_pictures, recursive_delete=True))

        self.set_dark_style()

    def remove_duplicate_pictures(self, recursive_delete=False):
        root_path = self.model.rootPath()
        if root_path == ".":
            return  # if source destination wasnt chosen

        # check for missclick
        msg = "Are you sure you want to delete (trash bin) duplicate pictures from source folder?"
        reply = QtWidgets.QMessageBox.question(self, 'Message',
                                               msg, QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply != QtWidgets.QMessageBox.Yes:
            return

        # gather pictures from root path
        all_pictures = []
        if recursive_delete:  # recursive search
            for path in pathlib.Path(root_path).glob(r'**/*'):
                if path.suffix in self.pic_ext_list:
                    all_pictures.append(path)

        else:  # non recursive
            for path in pathlib.Path(root_path).glob(r'*'):
                if path.suffix in self.pic_ext_list:
                    all_pictures.append(path)

        # add phash of picture to dictionary, replace with shorter filename if same hash found
        result_pics = {}
        for path in all_pictures:
            with Image.open(path) as img:
                img_hash = str(imagehash.phash(img))
                if img_hash in result_pics:
                    dict_fname = result_pics[img_hash].stem
                    if len(path.stem) < len(dict_fname):
                        result_pics[img_hash] = path
                else:
                    result_pics[img_hash] = path

        result_pics = {value: key for key, value in result_pics.items()}
        # delete all pictures that are not in a result_pics dict
        for path in all_pictures:
            if path not in result_pics:
                try:
                    shutil.move(str(path), self.delete_folder)
                except shutil.Error:
                    send2trash(str(path))

        QtWidgets.QMessageBox.about(self, "Info", "Done")

    def add_text_to_buttons(self):
        self.ui.addDest.setText("Add")
        self.ui.removeDest.setText("Remove")
        self.ui.browseBtn.setText("Browse")
        self.ui.checkDeletedBtn.setText("Deleted")
        self.ui.unmoveBtn.setText("Undo")

    def remove_text_from_buttons(self):
        self.ui.addDest.setText("")
        self.ui.removeDest.setText("")
        self.ui.browseBtn.setText("")
        self.ui.checkDeletedBtn.setText("")
        self.ui.unmoveBtn.setText("")

    def set_orange_style(self):
        QtGui.QGuiApplication.setPalette(self.default_palette)
        with open("./styles/orange.css") as f:
            style_text = f.read()
            self.setStyleSheet(style_text)

        self.add_text_to_buttons()

    def set_fancy_style(self):
        QtGui.QGuiApplication.setPalette(self.default_palette)
        with open("./styles/fancy.css") as f:
            style_text = f.read()
            self.setStyleSheet(style_text)

        self.remove_text_from_buttons()

    def set_light_style(self):
        QtGui.QGuiApplication.setPalette(self.default_palette)
        self.setStyleSheet(" ")
        self.add_text_to_buttons()

    def set_dark_style(self):
        self.setStyleSheet(" ")
        self.add_text_to_buttons()

        dark_palette = QPalette()
        dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.WindowText, Qt.white)
        dark_palette.setColor(QPalette.Base, QColor(25, 25, 25))
        dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ToolTipBase, Qt.white)
        dark_palette.setColor(QPalette.ToolTipText, Qt.white)
        dark_palette.setColor(QPalette.Text, Qt.white)
        dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ButtonText, Qt.white)
        dark_palette.setColor(QPalette.BrightText, Qt.red)
        dark_palette.setColor(QPalette.Link, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.HighlightedText, Qt.black)
        QtGui.QGuiApplication.setPalette(dark_palette)

    def change_file_type(self):
        """
        Change source directory display between pictures and files
        """
        mode = self.ui.comboMode.currentText()
        if mode == "Files":
            self.model.setNameFilters(["*.*"])

        elif mode == "Pictures":
            self.model.setNameFilters(
                ["*.jpg", "*.png", "*.webp", ".JPEG", ".PNG"])

    def check_deleted_btn_cb(self):
        """
        This is supposed to change model view to the deleted folder,
        and second press is supposed to bring you back to the previous 
        folder, but it doesnt work if you dont select an image in deleted folder.
        """
        ind = self.ui.treeView.currentIndex()
        file_path = self.model.filePath(ind)
        try:
            file_path = pathlib.Path(file_path).parents[0].resolve()
        except IndexError:
            return

        if file_path != pathlib.Path(self.delete_folder).resolve():
            self.model.setRootPath(self.delete_folder)
            self.ui.treeView.setRootIndex(self.model.index(self.delete_folder))
        else:
            self.model.setRootPath(self.current_file_folder)
            self.ui.treeView.setRootIndex(
                self.model.index(self.current_file_folder))

    def clear_deleted_folder(self):
        msg = "Are you sure you want to clear folder with deleted files?"
        reply = QtWidgets.QMessageBox.question(self, 'Message',
                                               msg, QtWidgets.QMessageBox.Yes,
                                               QtWidgets.QMessageBox.No)

        if reply == QtWidgets.QMessageBox.Yes:
            p = pathlib.Path(self.delete_folder)
            for filename in p.glob("*"):
                send2trash(str(filename))
            QtWidgets.QMessageBox.about(
                self, "Delete folder cleared", "Delete folder cleared"
            )

    def undo_cb(self):
        """
        Store actions in a list, revert them 1 by 1
        """
        try:
            last_operation = self.undo_list[-1]
        except IndexError:
            return
        pic_path, dest_path = last_operation
        pic_path = pathlib.Path(pic_path)
        dest_path = pathlib.Path(dest_path, pic_path.name)
        pic_path, dest_path = dest_path, pic_path

        # print(pic_path.parents[0], dest_path)
        try:
            shutil.move(pic_path, str(dest_path))
        except shutil.Error:
            QtWidgets.QMessageBox.warning(
                self, "Warning", "File already exists")
        except AttributeError:
            return
        except FileNotFoundError:
            return
        del self.undo_list[-1]

    def load_preset_cb(self):
        """
        Load user settings from file
        """
        dialog = QFileDialog()
        dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
        dialog.setDefaultSuffix("json")
        dialog.setAcceptMode(QFileDialog.AcceptOpen)
        dialog.setNameFilters(["JSON (*.json)"])
        if dialog.exec_() == QDialog.Accepted:
            preset_path = dialog.selectedFiles()[0]
            self.path_hotkey_dict = load_json(preset_path)
            self.path_hotkey_dict = {
                k: v for k, v in self.path_hotkey_dict.items() if v is not None
            }
            print("loaded dict: ", self.path_hotkey_dict)
            self.hotkey_path_dict = {
                value: key for key, value in self.path_hotkey_dict.items()
            }
            self.restore_table_from_dict()
        else:
            print("Cancelled")

    def save_preset_cb(self):
        """
        Save user settings to file
        """
        dialog = QFileDialog()
        dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
        dialog.setDefaultSuffix("json")
        dialog.setAcceptMode(QFileDialog.AcceptSave)
        dialog.setNameFilters(["JSON (*.json)"])
        if dialog.exec_() == QDialog.Accepted:
            preset_path = dialog.selectedFiles()[0]
            save_json(self.path_hotkey_dict, preset_path)
            QtWidgets.QMessageBox.information(
                self, "Saved", f"Saved hotkey preset: {preset_path}"
            )
        else:
            print("Cancelled")

    def show_about_dialog(self):
        text = (
            "<center>"
            "<h1>Waifu File Sort</h1>"
            "&#8291;"
            "</center>"
            f"<p>Version {get_version()}<br/>"
        )
        QtWidgets.QMessageBox.about(self, "About Waifu File Sort", text)

    def restore_table_from_dict(self):
        self.clear_table_widget()
        row_counter = 0

        for shortcut in self._shortcut_list:
            shortcut.setEnabled(False)

        self._shortcut_list = []

        for path, hotkey in self.path_hotkey_dict.items():
            path = pathlib.Path(path)
            self.add_dest_to_table(dest_path=path.name, hotkey=hotkey)
            self.ui.tableWidget.item(row_counter, 0).setToolTip(str(path))
            shortcut = QShortcut(QKeySequence(hotkey), self)
            shortcut.activated.connect(
                lambda mypath=path: self.move_cb(input_path=mypath))
            self._shortcut_list.append(shortcut)
            row_counter += 1

    def clear_table_widget(self):
        self.ui.tableWidget.clearContents()
        self.ui.tableWidget.setRowCount(0)

    def remove_destination(self):
        # get selected row or return
        # delete info from both dicts
        # reconstruct table widget from dict

        current_row = self.ui.tableWidget.currentRow()
        # print(f"{current_row=}")
        try:
            dest_path = self.ui.tableWidget.item(current_row, 0).toolTip()
        except AttributeError:
            return
        hotkey = self.ui.tableWidget.cellWidget(current_row, 2).text()
        # print("deleting hotkey: ", hotkey)
        self.delete_hotkey(hotkey)
        try:
            del self.path_hotkey_dict[dest_path]
        except KeyError:
            pass
        try:
            del self.hotkey_path_dict[hotkey]
        except KeyError:
            pass

        self.restore_table_from_dict()

    def delete_hotkey(self, name):
        for shortcut in self._shortcut_list:
            key_name = shortcut.key().toString()
            # print("k-name: ", key_name)
            if key_name == name.upper():
                # print("DELETED hotkey: ", name)
                shortcut.setEnabled(False)

    def add_dest_to_table(self, dest_path=None, hotkey=None):
        self.ui.tableWidget.setEditTriggers(
            self.ui.tableWidget.NoEditTriggers
        )  # disable editing and sorting
        self.ui.tableWidget.setSortingEnabled(False)

        row_counter = self.ui.tableWidget.rowCount()
        self.ui.tableWidget.insertRow(row_counter)
        ########################################################
        # add path label
        dest_path = QtWidgets.QTableWidgetItem(
            dest_path or "Press browse to specify destination directory"
        )
        self.ui.tableWidget.setItem(row_counter, 0, dest_path)
        ########################################################
        # add browse button
        browse_btn = QtWidgets.QPushButton("Browse")
        browse_btn.clicked.connect(
            lambda *args, row_ind=row_counter: self.browse_dest_click(row_ind)
        )
        self.ui.tableWidget.setCellWidget(row_counter, 1, browse_btn)
        ########################################################
        # add hotkey line edit
        hotkey_line = QtWidgets.QLineEdit()
        hotkey_line.setPlaceholderText("Add hotkey")
        hotkey_line.setText(hotkey)
        hotkey_line.setMaxLength(1)

        hotkey_line.textChanged.connect(
            lambda *args, row_ind=row_counter: self.hotkey_line_text_changed_cb(
                hotkey_line, row_ind
            )
        )
        self.ui.tableWidget.setCellWidget(row_counter, 2, hotkey_line)
        ########################################################
        # add send button
        send_btn = QtWidgets.QPushButton("Send")
        send_btn.clicked.connect(
            lambda *args, row_ind=row_counter: self.move_cb(row=row_ind)
        )
        self.ui.tableWidget.setCellWidget(row_counter, 3, send_btn)

    def move_cb(self, row=None, input_path=None):
        ind = self.ui.treeView.currentIndex()
        pic_path = self.model.filePath(ind)
        dest_path = input_path or self.ui.tableWidget.item(row, 0).toolTip()
        dest_path = pathlib.Path(dest_path)

        if dest_path.is_dir() and str(dest_path) != ".":
            try:
                shutil.move(pic_path, str(dest_path))
            except shutil.Error:
                QtWidgets.QMessageBox.warning(
                    self, "Warning", "File already exists")
                return
            self.undo_list.append((pic_path, str(dest_path)))
        else:
            # notify user
            QtWidgets.QMessageBox.warning(
                self, "Warning", "Press Browse to add destination folder"
            )

    def hotkey_line_text_changed_cb(self, hotkey_line, row_ind):
        hotkey = hotkey_line.text()
        path = self.ui.tableWidget.item(row_ind, 0).toolTip()
        if not path and len(hotkey) > 0:
            QtWidgets.QMessageBox.warning(
                self, "Warning", "Press Browse to add destination folder, add hotkey after"
            )
            hotkey_line.clear()
            hotkey_line.clearFocus()
            return

        # check if hotkey line edit is empty and delete hotkey
        if len(hotkey) == 0 and path != "":
            hotkey_to_del = self.path_hotkey_dict[path]
            self.delete_hotkey(hotkey_to_del)

        self.path_hotkey_dict[path] = hotkey
        self.hotkey_path_dict = {
            value: key for key, value in self.path_hotkey_dict.items()
        }
        shortcut = QShortcut(QKeySequence(hotkey), self)
        # self._shortcut_list.append(shortcut.key().toString())
        self._shortcut_list.append(shortcut)
        dest_path = self.hotkey_path_dict[hotkey]
        shortcut.activated.connect(lambda: self.move_cb(input_path=dest_path))
        if len(hotkey) > 0:
            hotkey_line.clearFocus()

    def browse_dest_click(self, caller_row):
        # print(caller_row)
        dialog = QFileDialog()
        folder_path = dialog.getExistingDirectory(None, "Select Folder")
        p = pathlib.Path(folder_path)

        if folder_path:
            self.ui.tableWidget.item(caller_row, 0).setText(p.name)
            self.ui.tableWidget.item(caller_row, 0).setToolTip(str(p))
            self.path_hotkey_dict[str(p)] = self.ui.tableWidget.cellWidget(
                caller_row, 2
            ).text()

    def browse_source_click(self):
        dialog = QFileDialog()
        folder_path = dialog.getExistingDirectory(None, "Select Folder")
        if folder_path:
            self.model.setRootPath(folder_path)
            self.ui.treeView.setRootIndex(self.model.index(folder_path))

    def update_image_label(self):
        ind = self.ui.treeView.currentIndex()
        file_path = self.model.filePath(ind)

        # keep track of current folder for check button return location
        try:
            path_to_current_folder = pathlib.Path(file_path).parents[0]
        except IndexError:
            return # fix click on C drive crash

        if str(path_to_current_folder.resolve()) != str(
            pathlib.Path(self.delete_folder).resolve()
        ):
            self.current_file_folder = str(path_to_current_folder)

        pixmap = QtGui.QPixmap(file_path)
        pixmap = pixmap.scaled(
            self.ui.imageLabel.width(),
            self.ui.imageLabel.height(),
            QtCore.Qt.KeepAspectRatio,
        )
        self.ui.imageLabel.setPixmap(pixmap)
        self.ui.imageLabel.setAlignment(QtCore.Qt.AlignCenter)
Example #28
0
class FileRenamer(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent=parent)

        self.help_text = ''

        self.in_context = False

        self.build_ui()
        self.connect_events()

        self.cache = {}

    def build_ui(self):
        self.ui = UI_file_renamer.Ui_Form()
        self.ui.setupUi(self)
        self.setAcceptDrops(True)

        self.original_model = QFileSystemModel()
        self.original_model.setNameFilters(["*.wav", "*.zc"])
        self.original_model.setNameFilterDisables(False)
        self.ui.treeview_original.setModel(self.original_model)
        for i in range(6, 0, -1):
            self.ui.treeview_original.setColumnHidden(i, True)

        self.changed_model = RenamedFileSystemModel(
            rename_function=self.change_fname_function)
        self.changed_model.setNameFilters(["*.wav", "*.zc"])
        self.changed_model.setNameFilterDisables(False)
        self.ui.treeview_changed.setModel(self.changed_model)
        for i in range(6, 0, -1):
            self.ui.treeview_changed.setColumnHidden(i, True)

        icon = QIcon(
            utils.resource_path('../resources/icons/nabat_circle_color.ico'))
        self.setWindowIcon(icon)

        self.ui.progressBar.setVisible(False)
        self.ui.progressBar_tarball.setVisible(False)

    def connect_events(self):
        self.ui.btn_browse.clicked.connect(self.browse)
        self.ui.btn_browse_output.clicked.connect(self.browse_output)
        self.ui.directory_name.textChanged.connect(self.load_directory)
        self.ui.btn_save.clicked.connect(self.save)
        self.ui.btn_tarball.clicked.connect(self.tarball)

        self.ui.treeview_original.expanded.connect(self.expand_changed)
        self.ui.treeview_original.collapsed.connect(self.collapse_changed)
        self.ui.treeview_changed.expanded.connect(self.expand_original)
        self.ui.treeview_changed.collapsed.connect(self.collapse_original)
        self.sb_original = self.ui.treeview_original.verticalScrollBar()
        self.sb_original.valueChanged.connect(self.sync_scroll_right)
        self.sb_changed = self.ui.treeview_changed.verticalScrollBar()
        self.sb_changed.valueChanged.connect(self.sync_scroll_left)

        self.ui.chk_auto_rename.stateChanged.connect(self.update_filenames)
        self.ui.chk_replace.stateChanged.connect(self.update_filenames)

        self.ui.replace_from.textChanged.connect(self.update_filenames)
        self.ui.replace_to.textChanged.connect(self.update_filenames)

        self.ui.chk_replace.stateChanged.connect(self.enable_chk_replace)
        self.ui.chk_replace2.stateChanged.connect(self.enable_chk_replace2)
        self.ui.chk_folderGRTS.stateChanged.connect(self.enable_chk_grtsfolder)
        self.ui.chk_sitename_folder.stateChanged.connect(
            self.enable_chk_sitefolder)

        self.ui.site_text.textChanged.connect(self.update_filenames)
        self.ui.grts_txt.textChanged.connect(self.update_filenames)
        self.ui.replace_from.textChanged.connect(self.update_filenames)
        self.ui.replace_to.textChanged.connect(self.update_filenames)
        self.ui.replace_from2.textChanged.connect(self.update_filenames)
        self.ui.replace_to2.textChanged.connect(self.update_filenames)
        self.ui.grts_folder.toggled.connect(self.grts_parent_change)
        self.ui.grts_folder2x.toggled.connect(self.grts_parent_change)
        self.ui.site_folder.toggled.connect(self.site_parent_change)
        self.ui.site_folder2x.toggled.connect(self.site_parent_change)

    def grts_parent_change(self):
        self.ui.grts_txt.setText('')
        self.update_filenames()

    def site_parent_change(self):
        self.ui.site_text.setText('')
        self.update_filenames()

    def enable_chk_replace(self):
        checked = self.ui.chk_replace.isChecked()
        self.ui.replace_from.setEnabled(checked)
        self.ui.replace_to.setEnabled(checked)
        self.ui.chk_use_re.setEnabled(checked)
        self.ui.label_2.setEnabled(checked)
        self.update_filenames()

    def enable_chk_replace2(self):
        checked = self.ui.chk_replace2.isChecked()
        self.ui.replace_from2.setEnabled(checked)
        self.ui.replace_to2.setEnabled(checked)
        self.ui.chk_use_re2.setEnabled(checked)
        self.ui.label_3.setEnabled(checked)
        self.update_filenames()

    def enable_chk_grtsfolder(self):
        checked = self.ui.chk_folderGRTS.isChecked()
        self.ui.grts_folder.setEnabled(checked)
        self.ui.grts_folder2x.setEnabled(checked)
        self.ui.grts_folder3x.setEnabled(checked)
        self.ui.grts_txt.setEnabled(checked)
        self.update_filenames()

    def enable_chk_sitefolder(self):
        checked = self.ui.chk_sitename_folder.isChecked()
        self.ui.site_folder.setEnabled(checked)
        self.ui.site_folder2x.setEnabled(checked)
        self.ui.site_folder3x.setEnabled(checked)
        self.ui.site_text.setEnabled(checked)
        self.update_filenames()

    def sync_scroll_right(self, int):
        self.sb_changed.setValue(self.sb_original.value())

    def sync_scroll_left(self, int):
        self.sb_original.setValue(self.sb_changed.value())

    def update_filenames(self):
        self.load_directory()

    def expand_changed(self, index):
        self.ui.treeview_changed.setExpanded(
            self.changed_model.index(self.original_model.filePath(index)),
            True)

    def collapse_changed(self, index):
        self.ui.treeview_changed.setExpanded(
            self.changed_model.index(self.original_model.filePath(index)),
            False)

    def expand_original(self, index):
        self.ui.treeview_original.setExpanded(
            self.original_model.index(self.changed_model.filePath(index)),
            True)

    def collapse_original(self, index):
        self.ui.treeview_original.setExpanded(
            self.original_model.index(self.changed_model.filePath(index)),
            False)

    def browse(self):
        settings = QSettings('USGS', 'guanoeditor')
        last_data_fname = str(Path(settings.value('lastDataDname', '')).parent)

        dname = QFileDialog.getExistingDirectory(self, "Open a folder",
                                                 last_data_fname)
        if dname:
            settings.setValue('lastDataDname', dname)

            self.ui.directory_name.setText(dname)

            self.load_directory()

    def browse_output(self):
        settings = QSettings('USGS', 'guanoeditor')
        last_data_fname = str(Path(settings.value('lastDataDname', '')).parent)

        dname = QFileDialog.getExistingDirectory(self,
                                                 "Select an output folder",
                                                 last_data_fname)
        if dname:
            settings.setValue('lastDataDname', dname)

            self.ui.directory_name_output.setText(dname)

    def dragEnterEvent(self, e):
        if e.mimeData().hasUrls() and e.mimeData().urls()[0].isLocalFile():
            url = e.mimeData().urls()[0].toLocalFile()
            if url.endswith('.wav'):
                e.accept()
            else:
                e.ignore()
        else:
            e.ignore()

    def dropEvent(self, e):
        """
        Updates the form with the contents of an xml node dropped onto it.
        Parameters
        ----------
        e : qt event
        Returns
        -------
        None
        """
        try:
            e.setDropAction(Qt.CopyAction)
            e.accept()
            url = e.mimeData().urls()[0].toLocalFile()
            self.ui.file_name.setText(url)

            # self.from_xml(element)
        except:
            e = sys.exc_info()[0]
            print('problem drop', e)

    def change_fname_function(self, fname, index=None, f=None):

        renamed = fname

        if self.ui.chk_auto_rename.isChecked():
            try:
                renamed = utils.clean_name(fname)
            except:
                renamed = fname

        try:
            if self.ui.chk_replace.isChecked():
                if self.ui.chk_use_re.isChecked():
                    renamed = re.sub(self.ui.replace_from.text(),
                                     self.ui.replace_to.text(), renamed)
                else:
                    renamed = renamed.replace(self.ui.replace_from.text(),
                                              self.ui.replace_to.text())
        except:
            pass

        try:
            if self.ui.chk_replace2.isChecked():
                if self.ui.chk_use_re2.isChecked():
                    renamed = re.sub(self.ui.replace_from2.text(),
                                     self.ui.replace_to2.text(), renamed)
                else:
                    renamed = renamed.replace(self.ui.replace_from2.text(),
                                              self.ui.replace_to2.text())
        except:
            pass

        if not self.ui.chk_folderGRTS.isChecked() and \
            not self.ui.chk_sitename_folder.isChecked():
            return renamed

        try:
            parts = renamed.split('_')
            time_str = parts[-1]
            date_str = parts[-2]

            if len(parts) == 4:
                grts_str, site_str = parts[:2]
            elif len(parts) == 3:
                grts_str = parts[0]
                site_str = 'UnknownSiteName'
        except:
            return renamed

        if self.ui.chk_folderGRTS.isChecked():
            grts_str = self.ui.grts_txt.text()
            if not grts_str:
                if self.ui.grts_folder.isChecked():
                    if index is not None:
                        grts_str = self.changed_model.parent(index).data()
                    elif f is not None:
                        grts_str = f.parent()
                elif self.ui.grts_folder2x.isChecked():
                    if index is not None:
                        grts_str = self.changed_model.parent(
                            index).parent().data()
                    elif f is not None:
                        grts_str = f.parent().parent()
                elif self.ui.grts_folder3x.isChecked():
                    if index is not None:
                        grts_str = self.changed_model.parent(
                            index).parent().parent().data()
                    elif f is not None:
                        grts_str = f.parent().parent().parent()

        if self.ui.chk_sitename_folder.isChecked():
            site_str = self.ui.site_text.text()
            if not site_str:
                if self.ui.site_folder.isChecked():
                    if index is not None:
                        site_str = self.changed_model.parent(index).data()
                    elif f is not None:
                        site_str = f.parent()
                elif self.ui.site_folder2x.isChecked():
                    if index is not None:
                        site_str = self.changed_model.parent(
                            index).parent().data()
                    elif f is not None:
                        site_str = f.parent().parent()
                elif self.ui.site_folder3x.isChecked():
                    if index is not None:
                        site_str = self.changed_model.parent(
                            index).parent().parent().data()
                    elif f is not None:
                        site_str = f.parent().parent().parent()

        try:
            parts = [grts_str, site_str, date_str, time_str]
            renamed = "_".join(parts)
        except:
            return renamed

        return renamed

    def load_directory(self):
        self.ui.treeview_original.setRootIndex(
            self.original_model.index(self.ui.directory_name.text()))
        self.original_model.setRootPath(self.ui.directory_name.text())

        self.ui.treeview_changed.setRootIndex(
            self.changed_model.index(self.ui.directory_name.text()))
        self.changed_model.setRootPath(self.ui.directory_name.text())

    def save(self):

        d = Path(self.ui.directory_name.text())
        wavs = list(d.glob('**\*.wav'))
        wavs += list(d.glob('**\*.zc'))

        self.ui.progressBar.setVisible(True)
        self.ui.progressBar.setMinimum(1)
        self.ui.progressBar.setMaximum(len(wavs))
        self.ui.progressBar.setVisible(1)

        if self.ui.directory_name_output.text():
            if not Path(self.ui.directory_name_output.text()).exists():
                msq = r"The output directory specified does not exist!  Please point to an existing directory."
                QMessageBox.warning(self, "Output directory does not exist",
                                    msg)
                return None
            out_dir = Path(self.ui.directory_name_output.text())
            make_copy = True
        else:
            out_dir = Path(self.ui.directory_name.text())
            make_copy = False

        for f in wavs:
            original_fname = f.name

            new_fname = self.change_fname_function(original_fname, f=f)

            full_name = f.parent.joinpath(new_fname)
            full_name = str(full_name).replace(str(Path(self.ui.directory_name.text())), \
                                                   str(out_dir))
            if not Path(full_name).exists():
                Path(full_name).parent.mkdir(parents=True, exist_ok=True)
                if make_copy:
                    shutil.copy(str(f), full_name)
                else:
                    f.rename(full_name)

            if original_fname != new_fname:
                try:
                    g = GuanoFile(full_name)
                    g['Original Filename'] = original_fname
                    g.write(make_backup=False)
                except:
                    pass

            self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)

        msg = f"Finished renaming files in directory:\n\n{out_dir}"
        QMessageBox.information(self, "Renaming Process Complete", msg)

        self.ui.progressBar.setVisible(False)

    def tarball(self):
        if self.ui.directory_name_output.text():
            if not Path(self.ui.directory_name_output.text()).exists():
                msq = r"The output directory specified does not exist!  Please point to an existing directory."
                QMessageBox.warning(self, "Output directory does not exist",
                                    msg)
                return None
            out_dir = Path(self.ui.directory_name_output.text())
        else:
            out_dir = Path(self.ui.directory_name.text())

        wavs = list(out_dir.glob('**\*.wav'))
        wavs += list(out_dir.glob('**\*.zc'))

        self.ui.progressBar_tarball.setVisible(True)
        self.ui.progressBar_tarball.setMinimum(1)
        self.ui.progressBar_tarball.setMaximum(len(wavs))
        self.ui.progressBar_tarball.setVisible(1)

        tar_name = out_dir.joinpath(out_dir.name + '.tar.gz')

        with tarfile.open(tar_name, "w:gz") as tar_handle:
            for f in wavs:
                tar_handle.add(str(f), arcname=f.name)
                self.ui.progressBar_tarball.setValue(
                    self.ui.progressBar.value() + 1)

        self.ui.progressBar_tarball.setValue(
            self.ui.progressBar_tarball.maximum())

        msg = f"Finished creating tar.gz archive of directory contents.:\n\n{tar_name}"
        QMessageBox.information(self, "Tarball Process Complete", msg)

        self.ui.progressBar_tarball.setVisible(False)
Example #29
0
class FileManager(QWidget, _HalWidgetBase):
    def __init__(self, parent=None):
        super(FileManager, self).__init__(parent)
        self.title = 'PyQt5 file system view - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 640
        self.height = 480
        self.default_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files/examples'))
        self.user_path = (os.path.join('/media'))
        self.currentPath = None
        self.EXT = INFO.PROGRAM_FILTERS_EXTENSIONS
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.currentPath())
        self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files)
        self.model.setNameFilterDisables(False)
        self.model.setNameFilters(self.EXT)

        self.list = QListView()
        self.list.setModel(self.model)
        self.updateDirectoryView(self.default_path)
        self.list.setWindowTitle("Dir View")
        self.list.resize(640, 480)
        self.list.clicked[QModelIndex].connect(self.clicked)
        self.list.activated.connect(self._getPathActivated)
        #self.list.currentChanged = self.currentChanged
        self.list.setAlternatingRowColors(True)

        self.cb = QComboBox()
        self.cb.currentTextChanged.connect(self.filterChanged)
        self.cb.addItems(self.EXT)
        #self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))

        self.button = QPushButton()
        self.button.setText('Media')
        self.button.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button.setToolTip('Jump to Media directory')
        self.button.clicked.connect(self.onMediaClicked)

        self.button2 = QPushButton()
        self.button2.setText('User')
        self.button2.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        self.button2.setToolTip('Jump to linuxcnc directory')
        self.button2.clicked.connect(self.onUserClicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.button)
        hbox.addWidget(self.button2)
        hbox.addWidget(self.cb)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.list)
        windowLayout.addLayout(hbox)
        self.setLayout(windowLayout)
        self.show()

    # this could return the current/previous selected as it's selected.
    # need to uncomment monkey patch of self.list.currentChanged above
    # so far this is not needed
    def currentChanged(self,c,p):
        dir_path = self.model.filePath(c)
        print('-> ',dir_path)

    def updateDirectoryView(self, path):
        self.list.setRootIndex(self.model.setRootPath(path))

    def filterChanged(self, text):
        self.model.setNameFilters([text])

    def clicked(self, index):
        # the signal passes the index of the clicked item
        dir_path = self.model.filePath(index)
        if self.model.fileInfo(index).isFile():
            self.currentPath = dir_path
            return
        root_index = self.model.setRootPath(dir_path)
        self.list.setRootIndex(root_index)

    def onMediaClicked(self):
        self.updateDirectoryView(self.user_path)

    def onUserClicked(self):
        self.updateDirectoryView(self.default_path)

    def select_row(self, style):
        style = style.lower()
        selectionModel = self.list.selectionModel()
        row = selectionModel.currentIndex().row()
        self.rows = self.model.rowCount(self.list.rootIndex())

        if style == 'last':
            row = self.rows
        elif style == 'up':
            if row > 0:
                row -= 1
            else:
                row = 0
        elif style == 'down':
            if row < self.rows:
                row += 1
            else:
                row = self.rows
        else:
            return
        top = self.model.index(row, 0, self.list.rootIndex())
        selectionModel.setCurrentIndex(top, QItemSelectionModel.Select | QItemSelectionModel.Rows)
        selection = QItemSelection(top, top)
        selectionModel.clearSelection()
        selectionModel.select(selection, QItemSelectionModel.Select)

    # returns the current highlighted (selected) path as well as
    # whether it's a file or not.
    def getCurrentSelected(self):
        selectionModel = self.list.selectionModel()
        index = selectionModel.currentIndex()
        dir_path = self.model.filePath(index)
        if self.model.fileInfo(index).isFile():
            return (dir_path, True)
        else:
            return (dir_path, False)

    def _hal_init(self):
        if self.PREFS_:
            last_path = self.PREFS_.getpref('last_loaded_directory', self.default_path, str, 'BOOK_KEEPING')
            self.updateDirectoryView(last_path)
            LOG.debug("lAST FILE PATH: {}".format(last_path))
        else:
            LOG.debug("lAST FILE PATH: {}".format(self.default_path))
            self.updateDirectoryView(self.default_path)

    # get current selection and update the path
    # then if the path is good load it into linuxcnc
    # record it in the preference file if available
    def _getPathActivated(self):
        row = self.list.selectionModel().currentIndex()
        self.clicked(row)

        fname = self.currentPath
        if fname is None: 
            return
        if fname:
            self.load(fname)

    # this can be class patched to do something else
    def load(self, fname=None):
        if fname is None:
            self._getPathActivated()
            return
        self.recordBookKeeping()
        ACTION.OPEN_PROGRAM(fname)
        STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')

    # this can be class patched to do something else
    def recordBookKeeping(self):
        fname = self.currentPath
        if fname is None: 
            return
        if self.PREFS_:
            self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(), str, 'BOOK_KEEPING')
            self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING')

    # moves the selection up
    # used with MPG scrolling
    def up(self):
        self.select_row('up')

    # moves the selection down
    # used with MPG scrolling
    def down(self):
        self.select_row('down')
Example #30
0
class Window(QMainWindow):
    save_crop_signal = pyqtSignal()

    samling_ratio = 60

    class State(Enum):
        Initial = 1
        OneCursorPlaced = 2
        TwoCursorPlaced = 3

    def __init__(self):
        super().__init__()
        self.player = QMediaPlayer()
        self.ui = Ui_MainWindow()
        # setup main Window's UI
        self.ui.setupUi(self)
        self._setup_connections()
        self._setup_matplotlib()
        self._setup_directory_browser()
        self.play_timer = QTimer(self)
        self.play_timer.timeout.connect(self.plot_animator)
        self.ymax = 30000
        self.state = None
        self.crop_line_1_pos = None
        self.crop_line_2_pos = None
        self.play_limit = (0, 0)
        self.current_zoom = 100
        self.seconds_to_prefetch = 60
        self.minimum_file_file_length = 2.5
        self.save_crop_signal.connect(self.save_crop_to_file)

    def _setup_matplotlib(self):

        self.figure = Figure()
        self.canvas = FigureCanvas(self.figure)
        layout = self.ui.graph_widget.layout()
        layout.addWidget(self.canvas)
        self.canvas_click_connection = None
        self.canvas_scroll_connection = None
        self.subplot = self.figure.add_subplot(111)

        self.audio_plot, = self.subplot.plot([0], [0],
                                             '-',
                                             color=(0.70, 0.70, 0.70))
        self.playIndicator, = self.subplot.plot([0], [0], '^')
        self.crop_line_1, = self.subplot.plot([], [], '-r')
        self.crop_line_2, = self.subplot.plot([], [], '-r')
        self.ui.vertical_splitter.setSizes([2, 12])

    def _setup_directory_browser(self):
        curdir = os.path.abspath(os.path.curdir)
        self.file_system_model = QFileSystemModel(self)
        self.file_system_model.setReadOnly(True)
        self.file_system_model.setFilter(QDir.AllDirs | QDir.AllEntries
                                         | QDir.NoDotAndDotDot)
        self.file_system_model.setNameFilters(['*.wav', '*.txt'])
        self.file_system_model.setNameFilterDisables(False)

        self.ui.directory_view.setModel(self.file_system_model)
        self.file_system_model.setRootPath(curdir)
        index = self.file_system_model.index(curdir)
        self.ui.directory_view.setRootIndex(index)
        self.ui.directory_view.hideColumn(1)
        self.ui.directory_view.hideColumn(2)
        self.ui.directory_view.hideColumn(3)
        # index: QModelIndex = model.index(os.path.abspath(os.path.curdir))
        # self.ui.directory_view.expand(index);
        # self.ui.directory_view.scrollTo(index)
        # self.ui.directory_view.setCurrentIndex(index)
        self.ui.directory_view.doubleClicked.connect(self.file_selected)

    def plot(self, ydata_byte, start_pos=0):
        ''' plot some random stuff '''
        ploty = numpy.fromstring(ydata_byte, numpy.int16)
        plotdatay = ploty[0:len(ploty):Window.samling_ratio]
        plotdatax = [
            x for x in range(start_pos,
                             len(ploty) + start_pos, Window.samling_ratio)
        ]

        self.set_view_range(start_pos, plotdatax[-1])
        self.view_limit_range = (start_pos, plotdatax[-1])
        _max = max(plotdatay)
        self.figure.get_axes()[0].set_ylim(-_max, _max)
        print("The real plot limit", (start_pos, plotdatax[-1]))
        self.audio_plot.set_data(plotdatax, plotdatay)
        # refresh canvas
        self.canvas.draw()

    def set_view_range(self, start, end):
        self.figure.get_axes()[0].set_xlim(start, end)
        self.current_view = [start, end]

    def plot_animator(self):

        # current_music_time = self.play_started_audio_time + diff
        current_time_ms = self.player.position()
        current_music_frame = int(
            (current_time_ms * self.wave.getframerate()) / 1000)

        self.playIndicator.set_data([current_music_frame], [0])
        self.canvas.draw()
        if current_time_ms >= self.play_limit[1]:
            self.player.pause()
            self.play_timer.stop()
            self.playIndicator.set_data(self.crop_line_2_pos, 0)
        self.canvas.draw()

    def _setup_connections(self):
        self.player.stateChanged.connect(self.player_status_changed)
        self.player.positionChanged.connect(self.playback_progress_change)

    def toggle_pause(self):
        if self.playing:
            self.player.pause()
            self.play_timer.stop()
        else:
            self.player.play()

            self.play_timer.start()
        self.playing = not self.playing

    def seek(self, pos):
        self.player.setPosition(pos)
        self.player.play()
        if self.player.state() != QMediaPlayer.PlayingState:
            self.player.play()
        if not self.play_timer.isActive():
            self.play_timer.start()

    def seek_frame(self, frame_pos):
        self.seek(int((frame_pos * 1000) / (self.wave.getframerate())))
        # self.seek(int(frame_pos / (self.wave.getframerate() * 1000)))

    def canvas_click_listener(self, event):
        if not event.xdata:
            return
        currentframe = event.xdata
        current_time_in_milli = int(currentframe / self.wave.getframerate() *
                                    1000)
        if (current_time_in_milli < 0):
            current_time_in_milli = 0
        current_x = int(event.xdata)
        if (current_x < 0):
            current_x = 0

        if self.state == Window.State.Initial:
            self.crop_line_1.set_data([current_x, current_x],
                                      [-self.ymax, self.ymax])
            self.crop_line_1_pos = current_x
        elif self.state == Window.State.OneCursorPlaced:
            if current_x > self.crop_line_1_pos:
                self.crop_line_2_pos = current_x
                self.crop_line_2.set_data([current_x, current_x],
                                          [-self.ymax, self.ymax])
        self.canvas.draw()
        self.seek(current_time_in_milli)

    def canvas_scroll_listener(self, event):
        current_range = self.current_view[1] - self.current_view[0]
        if current_range < 0:
            return
        if event.button == 'down':
            new_range = current_range / 1.3
        else:
            new_range = current_range * 1.3

        new_view = [event.xdata - new_range / 2, event.xdata + new_range / 2]
        count = 0
        if new_view[0] < self.view_limit_range[0]:
            new_view[1] += self.view_limit_range[0] - new_view[0]
            new_view[0] = self.view_limit_range[0]
            if new_view[1] > self.view_limit_range[1]:
                new_view[1] = self.view_limit_range[1]

        elif new_view[1] > self.view_limit_range[1]:
            new_view[0] -= new_view[1] - self.view_limit_range[1]
            new_view[1] = self.view_limit_range[1]
            if new_view[0] < self.view_limit_range[0]:
                new_view[0] = self.view_limit_range[0]
        if new_view[0] > new_view[1]:
            return
        self.figure.get_axes()[0].set_xlim(new_view[0], new_view[1])
        self.current_view = new_view
        self.canvas.draw()

    def eventFilter(self, event):
        print("New Event", event.type())
        return False

    def keyPressEvent(self, event):

        key = event.key()
        if key == Qt.Key_Space:
            self.toggle_pause()
        if key == Qt.Key_Return:

            if self.state == Window.State.Initial:
                if self.crop_line_1_pos:
                    self.state = Window.State.OneCursorPlaced
                    self.crop_line_1.set_data(
                        [self.crop_line_1_pos, self.crop_line_1_pos],
                        [-self.ymax, self.ymax])
                    self.crop_line_1.set_color("green")
                    self.canvas.draw()

            elif self.state == Window.State.OneCursorPlaced:
                if self.crop_line_2_pos:
                    self.crop_line_2.set_color("green")
                    self.zoom_to_crop()
                    self.state = Window.State.TwoCursorPlaced
                    self.ui.statusbar.showMessage(
                        "Press Enter to save the clip to file", 3000)

            elif self.state == Window.State.TwoCursorPlaced:
                self.crop_to_save = self.data[self.crop_line_1_pos *
                                              2:self.crop_line_2_pos * 2]
                self.crop_nchannel = self.wave.getnchannels()
                self.crop_stampwidth = self.wave.getsampwidth()
                self.crop_framerate = self.wave.getframerate()
                self.save_crop_signal.emit()
                self.update_cropped_plot()

        if key == Qt.Key_Backspace:
            if self.state == Window.State.OneCursorPlaced:
                self.state = Window.State.Initial
                self.crop_line_1_pos = None
                self.crop_line_1.set_color("red")
                self.crop_line_2.set_data([], [])
                self.canvas.draw()
            elif self.state == Window.State.TwoCursorPlaced:

                self.play_limit = self.play_limit_bak
                self.state = Window.State.OneCursorPlaced
                self.crop_line_2_pos = None
                self.crop_line_2.set_color("red")
                self.canvas.draw()

    @pyqtSlot(QMediaPlayer.State)
    def player_status_changed(self, x):
        if x == QMediaPlayer.StoppedState:
            self.play_timer.stop()
            pass
        elif x == QMediaPlayer.PlayingState:
            pass
        elif x == QMediaPlayer.PausedState:
            pass

    @pyqtSlot('qint64')
    def playback_progress_change(self, position):
        pass
        # self.ui.progressBar.setValue(int(position / self.player.duration() * 100))

    def read_file(self, filename):
        # reset the cursors to default values
        self.wave = wave.open(filename, 'rb')
        self.channels = self.wave.getnchannels()
        self.rate = self.wave.getframerate()
        nframes = self.seconds_to_prefetch * self.wave.getframerate()

        self.playing = False
        self.data = self.wave.readframes(nframes)
        self.total_frames_read = len(self.data) // 2
        if self.total_frames_read / self.wave.getframerate(
        ) < self.minimum_file_file_length:
            self.ui.statusbar.showMessage("The file is too short.")
            return

        self.player.setMedia(QMediaContent(QUrl.fromLocalFile(filename)))
        self.plot(self.data)
        nframes = min(self.wave.getnframes(), nframes)
        self.data_shift = 0
        self.play_limit = (0, (nframes * 1000) / self.wave.getframerate())
        self.crop_count = 0
        self.current_open_file_name = filename[:-4]

        self.toggle_pause()
        if not self.canvas_scroll_connection:
            self.canvas_click_connection = self.canvas.mpl_connect(
                "button_press_event", self.canvas_click_listener)
            self.canvas_scroll_connection = self.canvas.mpl_connect(
                "scroll_event", self.canvas_scroll_listener)
        self.crop_line_1_pos = None
        self.crop_line_2_pos = None
        self.state = Window.State.Initial
        self.crop_line_2.set_data([], [])
        self.crop_line_2.set_color("red")
        self.crop_line_1.set_data([], [])
        self.crop_line_1.set_color("red")

    def zoom_to_crop(self):
        ##TODO : make crop from line1 and line2 position
        self.set_view_range(self.crop_line_1_pos - 1000,
                            self.crop_line_2_pos + 1000)
        # cropped_data = self.data[self.crop_line_1_pos:self.crop_line_2_pos]
        self.play_limit_bak = self.play_limit
        self.play_limit = ((self.crop_line_1_pos * 1000) /
                           self.wave.getframerate(),
                           (1000 * self.crop_line_2_pos) /
                           self.wave.getframerate())
        self.seek_frame(self.crop_line_1_pos)
        self.canvas.draw()

    def update_cropped_plot(self):
        # frames remain in the total sound clip
        remaining_frames = (self.wave.getnframes()) - int(self.crop_line_2_pos)
        # time remain for compleliton of sound clip
        remaining_ms = (remaining_frames * 1000) / self.wave.getframerate()

        if remaining_ms < 3000:
            return self.crop_completed(remaining_ms)

        # the no of frames that have been loaded into memory
        frames_in_memory = int(self.total_frames_read - self.crop_line_2_pos)
        data_pos = int(self.crop_line_2_pos * 2 - self.data_shift)
        self.data_shift = self.crop_line_2_pos * 2
        # all the data from sound have been read into the memory
        if frames_in_memory == remaining_frames:
            self.data = self.data[data_pos:len(self.data)]

        else:
            # the no of maximum frames that will be showed in preview
            total_frames_required = self.seconds_to_prefetch * self.wave.getframerate(
            )
            # the no of frames that needs to be read from disk
            frames_to_read = total_frames_required - frames_in_memory
            # the file may not have that many frames, so it's the minimun of frames to read and frames in disk remain
            #  to read
            frames_that_will_be_read = min(
                self.wave.getnframes() - self.total_frames_read,
                frames_to_read)

            self.total_frames_read += frames_that_will_be_read
            self.data = self.data[data_pos:len(
                self.data)] + self.wave.readframes(frames_that_will_be_read)

        self.plot(self.data, self.crop_line_2_pos)
        # frames_remain_to_read = self.wave.getnframes() - self.total_frames_read
        self.state = Window.State.Initial
        self.play_limit = ((self.crop_line_2_pos * 1000) /
                           self.wave.getframerate(),
                           (self.total_frames_read * 1000) /
                           self.wave.getframerate())
        self.view_limit_range = (self.crop_line_2_pos, self.total_frames_read)
        self.seek_frame(self.crop_line_2_pos)
        self.crop_line_1.set_data([], [])
        self.crop_line_2.set_data([], [])
        self.crop_line_1.set_color("red")
        self.crop_line_2.set_color("red")
        self.crop_line_1_pos = None
        self.crop_line_2_pos = None

    @pyqtSlot(QModelIndex)
    def file_selected(self, index):
        filename = self.file_system_model.filePath(index)
        if filename.endswith('.txt'):
            self.text_file = QFile(filename)
            if not self.text_file.open(QFile.ReadOnly | QFile.Text):
                QMessageBox.warning(self, "Application", "Cannot read file",
                                    self.text_file.errorString())
                return
            in_stream = QTextStream(self.text_file)
            # self.ui.text_edit.setPlainText(in_stream.readAll())

            # font: QFont = self.ui.text_browser.font()
            # font.setPixelSize(40)
            # self.ui.text_browser.setFont(font)
            data = in_stream.readAll()
            self.ui.text_browser.setPlainText(data)
            self.ui.text_edit.setPlainText(data)
        else:
            try:
                self.read_file(filename)
            except:
                traceback.print_exc(2)
                self.ui.statusbar.showMessage("Reading the file failed", 300)

    @pyqtSlot()
    def save_crop_to_file(self):
        self.crop_count += 1
        wave_file = wave.open(
            self.current_open_file_name + "_crop_" + str(self.crop_count) +
            '.wav', 'wb')
        wave_file.setnchannels(self.crop_nchannel)
        wave_file.setsampwidth(self.crop_stampwidth)
        wave_file.setframerate(self.crop_framerate)
        wave_file.writeframes(self.crop_to_save)
        wave_file.close()

    def crop_completed(self, remaining_ms):
        self.state = Window.State.Initial
        self.crop_line_1.set_data([], [])
        self.crop_line_2.set_data([], [])
        self.crop_line_1.set_color("red")
        self.crop_line_2.set_color("red")
        self.crop_line_1_pos = None
        self.crop_line_2_pos = None
        self.audio_plot.set_data([], [])
        self.ui.statusbar.showMessage("Cropping this file has been completed")
        self.canvas.mpl_disconnect(self.canvas_click_connection)
        self.canvas.mpl_disconnect(self.canvas_scroll_connection)
        self.canvas_scroll_connection = None
        self.canvas_click_connection = None
        self.canvas.draw()
        self.player.stop()
        self.play_timer.stop()
        self.wave.close()
        self.ui.statusbar.showMessage(
            "Only %f seconds left thus this file is considered completed" %
            (remaining_ms / 1000))
Example #31
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width() - self.width()) // 2,
			          (screenRect.height() - self.height()) // 2)
			if not screenRect.contains(self.geometry()):
				self.showMaximized()
		if sys.platform.startswith('darwin'):
			# https://github.com/retext-project/retext/issues/198
			searchPaths = QIcon.themeSearchPaths()
			searchPaths.append('/opt/local/share/icons')
			searchPaths.append('/usr/local/share/icons')
			QIcon.setThemeSearchPaths(searchPaths)
		setIconThemeFromSettings()
		if QFile.exists(getBundledIcon('retext')):
			self.setWindowIcon(QIcon(getBundledIcon('retext')))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.splitter = QSplitter(self)
		self.treeView = QTreeView(self.splitter)
		self.treeView.doubleClicked.connect(self.treeItemSelected)
		self.tabWidget = QTabWidget(self.splitter)
		self.initTabWidget()
		self.splitter.setSizes([self.width() / 5, self.width() * 4 / 5])
		self.initDirectoryTree(globalSettings.showDirectoryTree, globalSettings.directoryPath)
		self.setCentralWidget(self.splitter)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		self.toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		self.toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh',
			lambda: self.currentTab.readTextFromFile())
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionCloseCurrentTab = self.act(self.tr('Close tab'), 'window-close',
			lambda: self.closeTab(self.ind), shct=QKeySequence.Close)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find',
			self.search, shct=QKeySequence.Find)
		self.actionGoToLine = self.act(self.tr('Go to line'),
			trig=self.goToLine, shct=Qt.CTRL+Qt.Key_G)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(getBundledIcon('document-preview')))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		menuPreview = QMenu()
		menuPreview.addAction(self.actionLivePreview)
		self.actionPreview.setMenu(menuPreview)
		self.actionInsertTable = self.act(self.tr('Insert table'),
			trig=lambda: self.insertFormatting('table'))
		self.actionTableMode = self.act(self.tr('Table editing mode'),
			shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.currentTab.editBox.enableTableMode(x))
		self.actionInsertImages = self.act(self.tr('Insert images by file path'),
			trig=lambda: self.insertImages())
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setChecked(self.isFullScreen())
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste)
		self.actionPasteImage = self.act(self.tr('Paste image'), 'edit-paste',
			lambda: self.currentTab.editBox.pasteImage(), shct=Qt.CTRL+Qt.SHIFT+Qt.Key_V)
		self.actionMoveUp = self.act(self.tr('Move line up'), 'go-up',
			lambda: self.currentTab.editBox.moveLineUp(), shct=Qt.ALT+Qt.Key_Up)
		self.actionMoveDown = self.act(self.tr('Move line down'), 'go-down',
			lambda: self.currentTab.editBox.moveLineDown(), shct=Qt.ALT+Qt.Key_Down)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant is not None:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		if ReTextWebKitPreview is None:
			globalSettings.useWebKit = False
			self.actionWebKit.setEnabled(False)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionWebEngine = self.act(self.tr('Use WebEngine (Chromium) renderer'),
			trigbool=self.enableWebEngine)
		if ReTextWebEnginePreview is None:
			globalSettings.useWebEngine = False
		self.actionWebEngine.setChecked(globalSettings.useWebEngine)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionReplace = self.act(self.tr('Replace'), 'edit-find-replace',
			lambda: self.find(replace=True))
		self.actionReplaceAll = self.act(self.tr('Replace all'), trig=self.replaceAll)
		menuReplace = QMenu()
		menuReplace.addAction(self.actionReplaceAll)
		self.actionReplace.setMenu(menuReplace)
		self.actionCloseSearch = self.act(self.tr('Close'), 'window-close',
			lambda: self.searchBar.setVisible(False),
			shct=QKeySequence.Cancel)
		self.actionCloseSearch.setPriority(QAction.LowPriority)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup.name == globalSettings.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertFormatting('bold'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertFormatting('italic'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertFormatting('underline'))
		self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering',
			'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote',
			'table')
		self.usefulChars = ('deg', 'divide', 'euro', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.formattingBox = QComboBox(self.editBar)
		self.formattingBox.addItem(self.tr('Formatting'))
		self.formattingBox.addItems(self.usefulTags)
		self.formattingBox.activated[str].connect(self.insertFormatting)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = self.menuBar()
		menuFile = menubar.addMenu(self.tr('&File'))
		menuEdit = menubar.addMenu(self.tr('&Edit'))
		menuHelp = menubar.addMenu(self.tr('&Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addAction(self.actionCloseCurrentTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addAction(self.actionPasteImage)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionMoveUp)
		menuEdit.addAction(self.actionMoveDown)
		menuEdit.addSeparator()
		if enchant is not None:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionGoToLine)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		if ReTextWebKitPreview is not None or ReTextWebEnginePreview is None:
			menuEdit.addAction(self.actionWebKit)
		else:
			menuEdit.addAction(self.actionWebEngine)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionInsertTable)
		menuEdit.addAction(self.actionTableMode)
		menuEdit.addAction(self.actionInsertImages)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.toolBar.addAction(self.actionNew)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionOpen)
		self.toolBar.addAction(self.actionSave)
		self.toolBar.addAction(self.actionPrint)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionPreview)
		self.toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.formattingBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.replaceEdit = QLineEdit(self.searchBar)
		self.replaceEdit.setPlaceholderText(self.tr('Replace with'))
		self.replaceEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addWidget(self.replaceEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.addAction(self.actionReplace)
		self.searchBar.addAction(self.actionCloseSearch)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant is not None:
			self.sl = globalSettings.spellCheckLocale
			try:
				enchant.Dict(self.sl or None)
			except enchant.errors.Error as e:
				warnings.warn(str(e), RuntimeWarning)
				globalSettings.spellCheck = False
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def restoreLastOpenedFiles(self):
		for file in readListFromSettings("lastFileList"):
			self.openFileWrapper(file)

		# Show the tab of last opened file
		lastTabIndex = globalSettings.lastTabIndex
		if lastTabIndex >= 0 and lastTabIndex < self.tabWidget.count():
			self.tabWidget.setCurrentIndex(lastTabIndex)

	def iterateTabs(self):
		for i in range(self.tabWidget.count()):
			yield self.tabWidget.widget(i)

	def updateStyleSheet(self):
		self.ss = None
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.setMovable(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent
		self.tabWidget.setTabBarAutoHide(globalSettings.tabBarAutoHide)

	def initDirectoryTree(self, visible, path):
		if visible:
			self.fileSystemModel = QFileSystemModel(self.treeView)
			self.fileSystemModel.setRootPath(path)
			supportedExtensions = ['.txt']
			for markup in markups.get_all_markups():
				supportedExtensions += markup.file_extensions
			filters = ["*" + s for s in supportedExtensions]
			self.fileSystemModel.setNameFilters(filters)
			self.fileSystemModel.setNameFilterDisables(False)
			self.treeView.setModel(self.fileSystemModel)
			self.treeView.setRootIndex(self.fileSystemModel.index(path))
			self.treeView.setColumnHidden(1, True)
			self.treeView.setColumnHidden(2, True)
			self.treeView.setColumnHidden(3, True)
			self.treeView.setHeaderHidden(True)
		self.treeView.setVisible(visible)

	def treeItemSelected(self, signal):
		file_path = self.fileSystemModel.filePath(signal)
		if os.path.isdir(file_path):
			return
		self.openFileWrapper(file_path)

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(getBundledIcon(name)))

	def printError(self):
		import traceback
		print('Exception occurred while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def updateTabTitle(self, ind, tab):
		changed = tab.editBox.document().isModified()
		if changed and not self.autoSaveActive(tab):
			title = tab.getBaseName() + '*'
		else:
			title = tab.getBaseName()
		self.tabWidget.setTabText(ind, title)

	def tabFileNameChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		filename of the current tab has changed.
		'''
		if tab == self.currentTab:
			if tab.fileName:
				self.setWindowTitle("")
				if globalSettings.windowTitleFullPath:
					self.setWindowTitle(tab.fileName + '[*]')
				self.setWindowFilePath(tab.fileName)
				self.updateTabTitle(self.ind, tab)
				self.tabWidget.setTabToolTip(self.ind, tab.fileName)
				QDir.setCurrent(QFileInfo(tab.fileName).dir().path())
			else:
				self.setWindowFilePath('')
				self.setWindowTitle(self.tr('New document') + '[*]')

			canReload = bool(tab.fileName) and not self.autoSaveActive(tab)
			self.actionSetEncoding.setEnabled(canReload)
			self.actionReload.setEnabled(canReload)

	def tabActiveMarkupChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		active markup class of the current tab has changed.
		'''
		if tab == self.currentTab:
			markupClass = tab.getActiveMarkupClass()
			dtMarkdown = (markupClass == markups.MarkdownMarkup)
			dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup)
			self.formattingBox.setEnabled(dtMarkdown)
			self.symbolBox.setEnabled(dtMarkdown)
			self.actionUnderline.setEnabled(dtMarkdown)
			self.actionBold.setEnabled(dtMkdOrReST)
			self.actionItalic.setEnabled(dtMkdOrReST)

	def tabModificationStateChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		modification state of the current tab has changed.
		'''

		if tab == self.currentTab:
			changed = tab.editBox.document().isModified()
			if self.autoSaveActive(tab):
				changed = False
			self.actionSave.setEnabled(changed)
			self.updateTabTitle(self.ind, tab)
			self.setWindowModified(changed)

	def createTab(self, fileName):
		previewStatesByName = {
			'editor': PreviewDisabled,
			'normal-preview': PreviewNormal,
			'live-preview': PreviewLive,
		}
		previewState = previewStatesByName.get(globalSettings.defaultPreviewState, PreviewDisabled)
		if previewState == PreviewNormal and not fileName:
			previewState = PreviewDisabled  # Opening empty document in preview mode makes no sense
		self.currentTab = ReTextTab(self, fileName, previewState)
		self.currentTab.fileNameChanged.connect(lambda: self.tabFileNameChanged(self.currentTab))
		self.currentTab.modificationStateChanged.connect(lambda: self.tabModificationStateChanged(self.currentTab))
		self.currentTab.activeMarkupChanged.connect(lambda: self.tabActiveMarkupChanged(self.currentTab))
		self.tabWidget.addTab(self.currentTab, self.tr("New document"))
		self.currentTab.updateBoxesVisibility()
		if previewState > 0:
			QTimer.singleShot(500, self.currentTab.triggerPreviewUpdate)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.createTab("")
			closedTab = self.tabWidget.widget(ind)
			if closedTab.fileName:
				self.fileSystemWatcher.removePath(closedTab.fileName)
			self.tabWidget.removeTab(ind)
			closedTab.deleteLater()

	def changeIndex(self, ind):
		'''
		This function is called when a different tab is selected.
		It changes the state of the window to mirror the current state
		of the newly selected tab. Future changes to this state will be
		done in response to signals emitted by the tab, to which the
		window was subscribed when the tab was created. The window is
		subscribed to all tabs like this, but only the active tab will
		logically generate these signals.
		Aside from the above this function also calls the handlers for
		the other changes that are implied by a tab switch: filename
		change, modification state change and active markup change.
		'''
		self.currentTab = self.tabWidget.currentWidget()
		editBox = self.currentTab.editBox
		previewState = self.currentTab.previewState
		self.actionUndo.setEnabled(editBox.document().isUndoAvailable())
		self.actionRedo.setEnabled(editBox.document().isRedoAvailable())
		self.actionCopy.setEnabled(editBox.textCursor().hasSelection())
		self.actionCut.setEnabled(editBox.textCursor().hasSelection())
		self.actionPreview.setChecked(previewState >= PreviewLive)
		self.actionLivePreview.setChecked(previewState == PreviewLive)
		self.actionTableMode.setChecked(editBox.tableModeEnabled)
		self.editBar.setEnabled(previewState < PreviewNormal)
		self.ind = ind
		editBox.setFocus(Qt.OtherFocusReason)

		self.tabFileNameChanged(self.currentTab)
		self.tabModificationStateChanged(self.currentTab)
		self.tabActiveMarkupChanged(self.currentTab)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			self.setEditorFont(font)

	def setEditorFont(self, font):
		globalSettings.editorFont = font
		for tab in self.iterateTabs():
			tab.editBox.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			self.setPreviewFont(font)

	def setPreviewFont(self, font):
		globalSettings.font = font
		for tab in self.iterateTabs():
			tab.triggerPreviewUpdate()

	def preview(self, viewmode):
		self.currentTab.previewState = viewmode * 2
		self.actionLivePreview.setChecked(False)
		self.editBar.setDisabled(viewmode)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableLivePreview(self, livemode):
		self.currentTab.previewState = int(livemode)
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		globalSettings.useWebEngine = False
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableWebEngine(self, enable):
		globalSettings.useWebKit = False
		globalSettings.useWebEngine = enable
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for tab in self.iterateTabs():
				tab.editBox.installFakeVimHandler()
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		try:
			dict = enchant.Dict(self.sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
			self.actionEnableSC.setChecked(False)
			yes = False
		self.setAllDictionaries(dict if yes else None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for tab in self.iterateTabs():
			hl = tab.highlighter
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		localedlg = LocaleDialog(self, defaultText=self.sl)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		try:
			enchant.Dict(sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
		else:
			self.sl = sl or None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
			if localedlg.checkBox.isChecked():
				globalSettings.spellCheckLocale = sl

	def search(self):
		self.searchBar.setVisible(True)
		self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def goToLine(self):
		line, ok = QInputDialog.getInt(self, self.tr("Go to line"), self.tr("Type the line number"))
		if ok:
			self.currentTab.goToLine(line-1)

	def searchBarVisibilityChanged(self, visible):
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False, replace=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text() if replace else None
		found = self.currentTab.find(text, flags, replaceText=replaceText)
		self.setSearchEditColor(found)

	def replaceAll(self):
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text()
		found = self.currentTab.replaceAll(text, replaceText)
		self.setSearchEditColor(found)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def showInDir(self):
		if self.currentTab.fileName:
			path = QFileInfo(self.currentTab.fileName).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def moveToTopOfRecentFileList(self, fileName):
		if fileName:
			files = readListFromSettings("recentFileList")
			if fileName in files:
				files.remove(fileName)
			files.insert(0, fileName)
			recentCount = globalSettings.recentDocumentsCount
			if len(files) > recentCount:
				del files[recentCount:]
			writeListToSettings("recentFileList", files)

	def createNew(self, text=None):
		self.createTab("")
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.currentTab.editBox.textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFunction(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFunction(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.currentTab.getActiveMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype is None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown", "text/markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), QDir.currentPath(),
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	@pyqtSlot(str)
	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.currentTab.fileName or
				self.currentTab.editBox.toPlainText() or
				self.currentTab.editBox.document().isModified()
			)
			if noEmptyTab:
				self.createTab(fileName)
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.currentTab.readTextFromFile(fileName)
			self.moveToTopOfRecentFileList(self.currentTab.fileName)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		codecsSet = set(bytes(QTextCodec.codecForName(alias).name())
		                for alias in QTextCodec.availableCodecs())
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in sorted(codecsSet)],
			0, False)
		if ok:
			self.currentTab.readTextFromFile(None, encoding)

	def saveFileAs(self):
		self.saveFile(dlg=True)

	def saveAll(self):
		for tab in self.iterateTabs():
			if (tab.fileName and tab.editBox.document().isModified()
				and QFileInfo(tab.fileName).isWritable()):
				tab.saveTextToFile()

	def saveFile(self, dlg=False):
		fileNameToSave = self.currentTab.fileName

		if (not fileNameToSave) or dlg:
			proposedFileName = ""
			markupClass = self.currentTab.getActiveMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			if fileNameToSave is not None:
				proposedFileName = fileNameToSave
			fileNameToSave = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), proposedFileName, defaultExt)[0]
			if fileNameToSave:
				if not QFileInfo(fileNameToSave).suffix():
					fileNameToSave += ext
				# Make sure we don't overwrite a file opened in other tab
				for tab in self.iterateTabs():
					if tab is not self.currentTab and tab.fileName == fileNameToSave:
						QMessageBox.warning(self, "",
							self.tr("Cannot save to file which is open in another tab!"))
						return False
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if fileNameToSave:
			if self.currentTab.saveTextToFile(fileNameToSave):
				self.moveToTopOfRecentFileList(self.currentTab.fileName)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		result = htmlFile.open(QIODevice.WriteOnly)
		if not result:
			QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
			return
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self, title, htmltext):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, title)
		td.setHtml(htmltext)
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		title, htmltext, _ = self.currentTab.getDocumentForExport()
		try:
			document = self.textDocument(title, htmltext)
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), self.currentTab.getBaseName() + ".odt",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat(b"odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), self.currentTab.getBaseName() + ".html",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self, title, htmltext, preview):
		if globalSettings.useWebKit:
			return preview
		try:
			return self.textDocument(title, htmltext)
		except Exception:
			self.printError()

	def standardPrinter(self, title):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(title)
		printer.setCreator('ReText %s' % app_version)
		if globalSettings.paperSize:
			pageSize = self.getPageSizeByName(globalSettings.paperSize)
			if pageSize is not None:
				printer.setPaperSize(pageSize)
			else:
				QMessageBox.warning(self, '',
					self.tr('Unrecognized paperSize setting "%s".') %
					globalSettings.paperSize)
		return printer

	def getPageSizeByName(self, pageSizeName):
		""" Returns a validated PageSize instance corresponding to the given
		name. Returns None if the name is not a valid PageSize.
		"""
		pageSize = None

		lowerCaseNames = {pageSize.lower(): pageSize for pageSize in
		                  self.availablePageSizes()}
		if pageSizeName.lower() in lowerCaseNames:
			pageSize = getattr(QPagedPaintDevice, lowerCaseNames[pageSizeName.lower()])

		return pageSize

	def availablePageSizes(self):
		""" List available page sizes. """

		sizes = [x for x in dir(QPagedPaintDevice)
		         if type(getattr(QPagedPaintDevice, x)) == QPagedPaintDevice.PageSize]
		return sizes

	def savePdf(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			self.currentTab.getBaseName() + ".pdf",
			self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			title, htmltext, preview = self.currentTab.getDocumentForExport()
			if globalSettings.useWebEngine and hasattr(preview.page(), "printToPdf"):
				pageSize = self.getPageSizeByName(globalSettings.paperSize)
				if pageSize is None:
					pageSize = QPageSize(QPageSize.A4)
				margins = QMarginsF(20, 20, 13, 20)  # left, top, right, bottom (in millimeters)
				layout = QPageLayout(pageSize, QPageLayout.Portrait, margins, QPageLayout.Millimeter)
				preview.page().printToPdf(fileName, layout)  # Available since Qt 5.7
				return
			printer = self.standardPrinter(title)
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printFile(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		printer = self.standardPrinter(title)
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printPreview(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		document = self.getDocumentForPrint(title, htmltext, preview)
		if document is None:
			return
		printer = self.standardPrinter(title)
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		import shlex
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		else:
			fileName = 'out' + defaultext
		basename = '.%s.retext-temp' % self.currentTab.getBaseName()
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename + self.currentTab.getActiveMarkupClass().default_extension
			self.currentTab.writeTextToFile(tmpname)
		command = command.replace('%of', shlex.quote(fileName))
		command = command.replace('%html' if html else '%if', shlex.quote(tmpname))
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()

	def autoSaveActive(self, tab=None):
		tab = tab if tab else self.currentTab
		return bool(self.autoSaveEnabled and tab.fileName and
			    QFileInfo(tab.fileName).isWritable())

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())
			self.actionPasteImage.setEnabled(mimeData.hasImage())

	def insertFormatting(self, formatting):
		if formatting == 'table':
			dialog = InsertTableDialog(self)
			dialog.show()
			self.formattingBox.setCurrentIndex(0)
			return

		cursor = self.currentTab.editBox.textCursor()
		text = cursor.selectedText()
		moveCursorTo = None

		def c(cursor):
			nonlocal moveCursorTo
			moveCursorTo = cursor.position()

		def ensurenl(cursor):
			if not cursor.atBlockStart():
				cursor.insertText('\n\n')

		toinsert = {
			'header': (ensurenl, '# ', text),
			'italic': ('*', text, c, '*'),
			'bold': ('**', text, c, '**'),
			'underline': ('<u>', text, c, '</u>'),
			'numbering': (ensurenl, ' 1. ', text),
			'bullets': (ensurenl, '  * ', text),
			'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'),
			'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'),
			'inline code': ('`', text, c, '`'),
			'code block': (ensurenl, '    ', text),
			'blockquote': (ensurenl, '> ', text),
		}

		if formatting not in toinsert:
			return

		cursor.beginEditBlock()
		for token in toinsert[formatting]:
			if callable(token):
				token(cursor)
			else:
				cursor.insertText(token)
		cursor.endEditBlock()

		self.formattingBox.setCurrentIndex(0)
		# Bring back the focus on the editor
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

		if moveCursorTo:
			cursor.setPosition(moveCursorTo)
			self.currentTab.editBox.setTextCursor(cursor)

	def insertSymbol(self, num):
		if num:
			self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		tab = None
		for testtab in self.iterateTabs():
			if testtab.fileName == fileName:
				tab = testtab
		if tab is None:
			self.fileSystemWatcher.removePath(fileName)
			return
		if not QFile.exists(fileName):
			self.tabWidget.setCurrentWidget(tab)
			tab.editBox.document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not tab.editBox.document().isModified():
			# File was not modified in ReText, reload silently
			tab.readTextFromFile()
		else:
			self.tabWidget.setCurrentWidget(tab)
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				tab.readTextFromFile()
			else:
				self.autoSaveEnabled = False
				tab.editBox.document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		tab = self.tabWidget.widget(ind)
		if self.autoSaveActive(tab):
			tab.saveTextToFile()
			return True
		if not tab.editBox.document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFile(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for ind in range(self.tabWidget.count()):
			if not self.maybeSave(ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry:
			globalSettings.windowGeometry = self.saveGeometry()
		if globalSettings.openLastFilesOnStartup:
			files = [tab.fileName for tab in self.iterateTabs()]
			writeListToSettings("lastFileList", files)
			globalSettings.lastTabIndex = self.tabWidget.currentIndex()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(includeStyleSheet=False)
		except Exception:
			return self.printError()
		winTitle = self.currentTab.getBaseName()
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def insertImages(self):
		supportedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
		fileFilter = ' (%s);;' % ' '.join('*' + ext for ext in supportedExtensions)
		fileNames, _selectedFilter = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several images to open"), QDir.currentPath(),
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))

		cursor = self.currentTab.editBox.textCursor()

		imagesMarkup = '\n'.join(
			self.currentTab.editBox.getImageMarkup(fileName)
			for fileName in fileNames)
		cursor.insertText(imagesMarkup)

		self.formattingBox.setCurrentIndex(0)
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2020')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markupClass):
		globalSettings.defaultMarkup = markupClass.name
		for tab in self.iterateTabs():
			if not tab.fileName:
				tab.updateActiveMarkupClass()
Example #32
0
class MainView(QMainWindow):
    def __init__(self, model, controller):
        self.settings = SettingsModel()
        self.model = model
        self.canvas = self.model.canvas
        self.main_controller = controller
        self.canvas_controller = CanvasController(self.canvas)
        super(MainView, self).__init__()
        self.build_ui()
        self.center_ui()

        self.model.subscribe_update_func(self.update_ui_from_model)

    def build_ui(self):
        self.ui = Ui_Hitagi()
        self.ui.setupUi(self)

        # File menu
        self.ui.actionSet_as_wallpaper.triggered.connect(
            self.on_set_as_wallpaper)
        self.ui.actionCopy_to_clipboard.triggered.connect(self.on_clipboard)
        self.ui.actionOpen_current_directory.triggered.connect(
            self.on_current_dir)
        self.ui.actionOptions.triggered.connect(self.on_options)
        self.ui.actionExit.triggered.connect(self.on_close)

        # Folder menu
        self.ui.actionOpen_next.triggered.connect(self.on_next_item)
        self.ui.actionOpen_previous.triggered.connect(self.on_previous_item)
        self.ui.actionChange_directory.triggered.connect(
            self.on_change_directory)

        # View menu
        self.ui.actionZoom_in.triggered.connect(self.on_zoom_in)
        self.ui.actionZoom_out.triggered.connect(self.on_zoom_out)
        self.ui.actionOriginal_size.triggered.connect(self.on_zoom_original)
        self.ui.actionFit_image_width.triggered.connect(
            self.on_scale_image_to_width)
        self.ui.actionFit_image_height.triggered.connect(
            self.on_scale_image_to_height)
        self.ui.actionFile_list.triggered.connect(self.on_toggle_filelist)
        self.ui.actionFullscreen.triggered.connect(self.on_fullscreen)

        # Favorite menu
        self.ui.actionAdd_to_favorites.triggered.connect(
            self.on_add_to_favorites)
        self.ui.actionRemove_from_favorites.triggered.connect(
            self.on_remove_from_favorites)

        # Help menu
        self.ui.actionChangelog.triggered.connect(self.on_changelog)
        self.ui.actionAbout.triggered.connect(self.on_about)

        # Load stylesheet
        stylesheet_dir = "resources/hitagi.stylesheet"
        with open(stylesheet_dir, "r") as sh:
            self.setStyleSheet(sh.read())

        # File listing
        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs
                                  | QDir.Files)
        self.file_model.setNameFilters(['*.jpg', '*.png', '*.jpeg'])
        self.file_model.setNameFilterDisables(False)
        self.file_model.setRootPath(self.settings.get('Directory', 'default'))

        self.ui.treeView.setModel(self.file_model)
        self.ui.treeView.setColumnWidth(0, 200)
        self.ui.treeView.setColumnWidth(1, 200)
        self.ui.treeView.hideColumn(1)
        self.ui.treeView.hideColumn(2)

        # Double click
        self.ui.treeView.activated.connect(self.on_dir_list_activated)
        # Update file list
        self.ui.treeView.clicked.connect(self.on_dir_list_clicked)
        # Open parent
        self.ui.button_open_parent.clicked.connect(self.on_open_parent)

        # Shortcuts
        _translate = QtCore.QCoreApplication.translate
        self.ui.actionExit.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Exit')))

        self.ui.actionOpen_next.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Next')))
        self.ui.actionOpen_previous.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Previous')))
        self.ui.actionChange_directory.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Directory')))

        self.ui.actionZoom_in.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Zoom in')))
        self.ui.actionZoom_out.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Zoom out')))
        self.ui.actionOriginal_size.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys',
                                                   'Zoom original')))
        self.ui.actionFullscreen.setShortcut(
            _translate("Hitagi", self.settings.get('Hotkeys', 'Fullscreen')))

        # Load favorites in UI
        self.load_favorites()

        # Background
        self.ui.graphicsView.setBackgroundBrush(
            QBrush(QColor(self.settings.get('Look', 'background')),
                   Qt.SolidPattern))

    def load_favorites(self):
        self.favorites = FavoritesModel()
        self.ui.menuFavorites.clear()
        for item in self.favorites.items():
            self.ui.menuFavorites.addAction(item).triggered.connect(
                (lambda item: lambda: self.on_open_favorite(item))(item))

    def on_open_favorite(self, path):
        self.main_controller.change_directory(path)

    def center_ui(self):
        ui_geometry = self.frameGeometry()
        center_point = QDesktopWidget().availableGeometry().center()
        ui_geometry.moveCenter(center_point)
        self.move(ui_geometry.topLeft())

    # On resize
    def resizeEvent(self, resizeEvent):
        self.main_controller.update_canvas(self.ui.graphicsView.width(),
                                           self.ui.graphicsView.height(),
                                           self.model.get_image())

    # Additional static shortcuts
    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Escape and self.model.is_fullscreen:
            self.main_controller.toggle_fullscreen()

        # Redefine shortcuts when hiding menubar
        # somehow not working 18/2/2015
        if self.model.is_fullscreen and self.ui.menubar.isHidden():
            if e.key() == QKeySequence(self.settings.get('Hotkeys', 'Exit')):
                self.on_close()
            elif e.key() == QKeySequence(self.settings.get('Hotkeys', 'Next')):
                self.on_next_item()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Previous')):
                self.on_previous_item()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Directory')):
                self.on_change_directory()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Zoom in')):
                self.on_zoom_in()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Zoom out')):
                self.on_zoom_out()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Zoom original')):
                self.on_zoom_original()
            elif e.key() == QKeySequence(
                    self.settings.get('Hotkeys', 'Fullscreen')):
                self.main_controller.toggle_fullscreen()

    def on_open_parent(self):
        parent_index = self.file_model.parent(
            self.file_model.index(self.file_model.rootPath()))
        self.file_model.setRootPath(self.file_model.filePath(parent_index))
        self.ui.treeView.setRootIndex(parent_index)

        # Update directory path
        self.model.directory = self.file_model.filePath(parent_index)

    def on_dir_list_activated(self, index):
        if self.file_model.hasChildren(index) is not False:
            self.file_model.setRootPath(self.file_model.filePath(index))
            self.ui.treeView.setRootIndex(index)

            # Save current path
            self.model.directory = self.file_model.filePath(index)

    def on_dir_list_clicked(self, index):
        self.main_controller.open_image(self.ui.graphicsView.width(),
                                        self.ui.graphicsView.height(),
                                        self.file_model.filePath(index))

    # File menu
    def on_set_as_wallpaper(self):
        from view.WallpaperView import WallpaperDialog
        from controller.wallpaper import WallpaperController

        _image = self.model.get_image()
        if _image is not None:
            dialog = WallpaperDialog(self, None,
                                     WallpaperController(self.model), _image)
            dialog.show()

    def on_clipboard(self):
        self.main_controller.copy_to_clipboard()

    def on_current_dir(self):
        import subprocess
        # Windows
        subprocess.Popen(r'explorer /select,' + self.model.get_image_path())

    def on_options(self):
        from view.OptionsView import OptionDialog
        self.dialog = OptionDialog(self, self.ui)
        self.dialog.show()

    def on_close(self):
        self.close()

    # Folder menu
    def on_next_item(self):
        index = self.ui.treeView.moveCursor(QAbstractItemView.MoveDown,
                                            Qt.NoModifier)
        self.ui.treeView.setCurrentIndex(index)
        self.main_controller.open_image(self.ui.graphicsView.width(),
                                        self.ui.graphicsView.height(),
                                        self.file_model.filePath(index))

    def on_previous_item(self):
        index = self.ui.treeView.moveCursor(QAbstractItemView.MoveUp,
                                            Qt.NoModifier)
        self.ui.treeView.setCurrentIndex(index)
        self.main_controller.open_image(self.ui.graphicsView.width(),
                                        self.ui.graphicsView.height(),
                                        self.file_model.filePath(index))

    def on_change_directory(self):
        self.main_controller.change_directory()

    # View menu
    def on_zoom_in(self):
        self.canvas_controller.update_canvas(self.ui.graphicsView.width(),
                                             self.ui.graphicsView.height(),
                                             self.model.get_image(), 1, 1.1)

    def on_zoom_out(self):
        self.canvas_controller.update_canvas(self.ui.graphicsView.width(),
                                             self.ui.graphicsView.height(),
                                             self.model.get_image(), 1, 0.9)

    def on_zoom_original(self):
        self.canvas_controller.update_canvas(self.ui.graphicsView.width(),
                                             self.ui.graphicsView.height(),
                                             self.model.get_image(), 4)

    def on_scale_image_to_width(self):
        self.canvas_controller.update_canvas(self.ui.graphicsView.width(),
                                             self.ui.graphicsView.height(),
                                             self.model.get_image(), 2)

    def on_scale_image_to_height(self):
        self.canvas_controller.update_canvas(self.ui.graphicsView.width(),
                                             self.ui.graphicsView.height(),
                                             self.model.get_image(), 3)

    def on_toggle_filelist(self):
        if self.ui.actionFile_list.isChecked():
            self.ui.fileWidget.show()
        else:
            self.ui.fileWidget.hide()

    def on_fullscreen(self):
        self.main_controller.toggle_fullscreen()

    # Favorite menu
    def on_add_to_favorites(self):
        self.main_controller.add_to_favorites()
        self.load_favorites()

    def on_remove_from_favorites(self):
        self.main_controller.remove_from_favorites()
        self.load_favorites()

    # Help menu
    def on_changelog(self):
        webbrowser.open('https://gimu.org/hitagi-reader/docs')

    def on_about(self):
        from view.AboutView import AboutDialog
        dialog = AboutDialog(self, None, None)
        dialog.show()

    def update_ui_from_model(self):
        """Update UI from model."""
        self.settings = SettingsModel()

        # On changing directory
        self.file_model.setRootPath(self.model.directory)
        self.ui.treeView.setRootIndex(
            self.file_model.index(self.model.directory))

        #if self.model.image_path is not None:
        # self.ui.statusbar.showMessage(str(self.model.image_path) + "    " + str(self.model.image_index + 1) + " of " + str(len(self.model.image_paths)))
        self.ui.graphicsView.setScene(self.canvas.scene)

        # Fullscreen mode switching
        if self.model.is_fullscreen:
            self.showFullScreen()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.hide()
            if self.settings.get('Misc', 'hide_statusbar') == 'True':
                self.ui.statusbar.hide()
        else:
            self.showNormal()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.show()
            if self.settings.get('Misc', 'hide_statusbar') == 'True':
                self.ui.statusbar.show()