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)
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)
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_())
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())
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
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
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
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)
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
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
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)
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)
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())
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))
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')
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')
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)
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)
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')
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))
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>" "⁣" "</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)
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)
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')
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))
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()
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()