class FileTreeView(QWidget): on_menu_select = pyqtSignal(str, str) on_dir_change = pyqtSignal() def __init__(self, parent: Application): super().__init__() self.stopped = threading.Event() self.tree = QTreeView() self.handle = self.Handler(self) self.get_data_file() self.model = QtGui.QStandardItemModel() self.item_construct = {} v_box = QVBoxLayout() v_box.addWidget(self.finder()) v_box.addWidget(self.tree_view()) self.setLayout(v_box) self.watch_dog() parent.app_close.connect(self.exit_push) self.tree.setAlternatingRowColors(True) def exit_push(self): self.stopped.set() def finder(self): w_find = QLineEdit() w_find.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) w_find.textChanged.connect(self.sort_list) w_find.setPlaceholderText("Search file..") return w_find def sort_list(self, text): list_sort = [] if text == "": self.show_tree(self.list_file) else: for data in self.list_file: if text.lower() in data.name().lower(): list_sort.append(data) self.show_tree(list_sort) def watch_dog(self): watch = ProcessRunnable(target=watch_winform, args=(get_data_folder(), self.handle, self.stopped)) watch.start() class Handler(watchdog.events.PatternMatchingEventHandler): def __init__(self, parent): super().__init__() self.parent = parent def on_created(self, event): print("Watchdog received created event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('create', event.src_path, event.is_directory)) def on_modified(self, event): print("Watchdog received modified event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('modify', event.src_path, event.is_directory)) def on_moved(self, event): print("Watchdog received move event", event.src_path, event.dest_path, sep=" : ") asyncio.run( self.parent.file_change('move', event.src_path, event.is_directory, event.dest_path)) def on_deleted(self, event): print("Watchdog received delete event", event.src_path, sep=" : ") asyncio.run( self.parent.file_change('delete', event.src_path, event.is_directory)) async def file_change(self, tpe, old, is_directory, new=""): if tpe == "move": self.import_single(self.model.invisibleRootItem(), new) self.remove_single(old) elif tpe == "delete": self.remove_single(old) elif tpe == "create": self.import_single(self.model.invisibleRootItem(), old) if is_directory: self.on_dir_change.emit() def get_data_file(self): self.list_file = [] self.create_list(get_data_folder()) def create_list(self, dir): lst = os.listdir(path=dir) for f in lst: path = os.path.join(dir, f) file = MyFile() if os.path.isdir(path): file.setParentName(os.path.basename(dir)) file.setParent(dir) file.setName(f) file.setDir(True) self.list_file.append(file) self.create_list(path) else: file.setParentName(os.path.basename(dir)) file.setParent(dir) file.setName(f) file.setDir(False) self.list_file.append(file) def tree_view(self): self.tree.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.open_menu) self.tree.doubleClicked.connect(self.open_event) # self.model.itemChanged.connect(self.data_change) self.tree.setModel(self.model) self.show_tree(self.list_file) return self.tree def show_tree(self, list_file): self.model.clear() self.model.setHorizontalHeaderLabels(['List API']) self.tree.header().setDefaultSectionSize(180) parent = self.model.invisibleRootItem() self.item_construct = {} self.import_data(parent, list_file) self.tree.expandAll() def import_data_by_path(self, parent, path: list, index): if index < len(path): full = os.sep.join(path[:index + 1]) if full in self.item_construct: item = self.item_construct[full] else: item = QStandardItem(path[index]) if os.path.isfile(os.path.join(get_data_folder(), full)): item.setToolTip( self.read_description( os.path.join(get_data_folder(), full))) item.setEditable(False) if not os.path.isdir(os.path.join(get_data_folder(), full)): item.setIcon(QIcon(get_icon_link("text_snippet.svg"))) else: item.setIcon(QIcon(get_icon_link("folder_yellow.svg"))) item.setData(full) parent.appendRow(item) self.item_construct[full] = item self.import_data_by_path(item, path, index + 1) def read_description(self, path): try: data = json.loads(open(path, encoding='utf-8').read()) json_data = APIData() json_data.construct(data) x = json_data.parseSave().description() if x.isspace() or x == "": return ".." else: return x except Exception as ex: print(ex) return ".." def import_data(self, parent, list_data): for i in list_data: self.import_single(parent, i) def import_single(self, parent, file_path): path = self.path_extract(file_path) self.import_data_by_path(parent, path, 0) def remove_single(self, file_path): path = self.path_extract(file_path) full = os.sep.join(path[:len(path)]) if full in self.item_construct: item = self.item_construct[full] (item.parent() or self.model.invisibleRootItem()).removeRow(item.row()) del self.item_construct[full] def path_extract(self, file_path): if isinstance(file_path, MyFile): path = os.path.join(file_path.parent(), file_path.name()) else: path = file_path path = path.replace(get_data_folder(), "") if path.startswith(os.sep): path = path.replace(os.sep, "", 1) path = path.split(os.sep) return path def open_menu(self, position): indexes = self.tree.selectedIndexes() level = 0 data = "" item = None if len(indexes) > 0: index = indexes[0] item = self.model.itemFromIndex(index) data = item.data() data = os.path.join(get_data_folder(), data) if os.path.isdir(data): level = 1 else: level = 2 menu = QMenu() menu.setStyleSheet(open(get_stylesheet()).read()) rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename', self) rename_action.setStatusTip('Rename') new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')), '&New Folder', self) new_action.setStatusTip('New Folder') refresh_action = QAction(QIcon(get_icon_link('refresh.svg')), '&Refresh', self) refresh_action.setStatusTip('Refresh') delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')), '&Delete', self) delete_action.setStatusTip('Delete') open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open', self) open_action.setStatusTip('Open file') expand_action = QAction(QIcon(), '&Expand', self) expand_action.setStatusTip('Expand') collapse_action = QAction(QIcon(), '&Collapse', self) collapse_action.setStatusTip('Collapse') duplicate_action = QAction(QIcon(get_icon_link('content_copy.svg')), '&Duplicate', self) duplicate_action.setStatusTip('Duplicate') copy_action = QAction(QIcon(get_icon_link('content_copy.svg')), '&Copy', self) copy_action.setStatusTip('Copy') move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')), '&Move', self) move_action.setStatusTip('Move') if level == 1: menu.addAction(rename_action) menu.addAction(new_action) menu.addSeparator() menu.addAction(refresh_action) menu.addAction(expand_action) menu.addAction(collapse_action) menu.addSeparator() menu.addAction(delete_action) elif level == 2: menu.addAction(open_action) menu.addAction(new_action) menu.addAction(refresh_action) menu.addSeparator() menu.addAction(rename_action) menu.addAction(duplicate_action) menu.addAction(copy_action) menu.addAction(move_action) menu.addSeparator() menu.addAction(delete_action) else: menu.addAction(new_action) menu.addAction(refresh_action) action = menu.exec_(self.tree.viewport().mapToGlobal(position)) if action == open_action: if data != "": self.on_menu_select.emit("open", data) elif action == refresh_action: self.get_data_file() self.show_tree(self.list_file) elif action == expand_action: if item is not None: self.tree.expand(item.index()) elif action == collapse_action: if item is not None: self.tree.collapse(item.index()) elif action == delete_action: if data != "": msg = QMessageBox() msg.setStyleSheet(open(get_stylesheet()).read()) msg.setIcon(QMessageBox.Warning) msg.setBaseSize(QSize(500, 300)) msg.setText("Delete file.") msg.setInformativeText("Are you sure to detele " + os.path.basename(data) + "?") msg.setWindowTitle("Delete Warning!!!") msg.addButton('Delete', QMessageBox.YesRole) msg.addButton('Move to Trash', QMessageBox.YesRole) msg.addButton('Cancel', QMessageBox.NoRole) rs = msg.exec_() if rs == 0: if os.path.isdir(data): shutil.rmtree(data) else: os.remove(data) elif rs == 1: send2trash(data) elif action == new_action: if data == "": data = get_data_folder() # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'New Folder', 'Folder name:') inp = QComboDialog('New Folder', 'Folder name:', QComboDialog.Text) ok = inp.exec_() if ok and inp.select: if os.path.isdir(data): try: os.mkdir(os.path.join(data, inp.select)) except Exception as ex: alert = Alert("Error", "Create folder error", str(ex)) alert.exec_() print(ex) else: new = os.path.join(os.path.dirname(data), inp.select) try: os.mkdir(new) except Exception as ex: alert = Alert("Error", "Create folder error", str(ex)) alert.exec_() print(ex) elif action == rename_action: if data != "": # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'Rename file', 'New name:') inp = QComboDialog('Rename file', 'New name:', QComboDialog.Text) ok = inp.exec_() if ok and inp.select: if os.path.isdir(data): new = os.path.join(os.path.dirname(data), inp.select) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Rename folder error", str(ex)) alert.exec_() print(ex) else: filename, file_extension = os.path.splitext(data) new = os.path.join(os.path.dirname(data), inp.select + file_extension) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Rename file error", str(ex)) alert.exec_() print(ex) elif action == move_action: if data != "": items = get_list_folder(get_data_folder(), get_data_folder()) # # item, ok = QInputDialog.getItem(self, "Select folder dialog", # "Select the destination folder", items, 0, False) inp = QComboDialog("Move", "Select the destination folder", QComboDialog.ComboBox, items) ok = inp.exec_() if ok and inp.select: folder = inp.select if inp.select.startswith(os.sep): folder = inp.select.replace(os.sep, "", 1) new = os.path.join(get_data_folder(), folder, os.path.basename(data)) try: os.rename(data, new) except Exception as ex: alert = Alert("Error", "Move file error", str(ex)) alert.exec_() print(ex) elif action == duplicate_action: if data != "": # input_name = QInputDialog() # input_name.setStyleSheet(open(get_stylesheet()).read()) # text, ok = input_name.getText(self, 'Duplicate file', 'New name:') inp = QComboDialog('Duplicate file', 'New name:', QComboDialog.Text) filename, file_extension = os.path.splitext(data) inp.set_init_text(filename) ok = inp.exec_() if ok and inp.select: new = os.path.join(os.path.dirname(data), inp.select + file_extension) try: copyfile(data, new) except Exception as ex: alert = Alert("Error", "Duplicate file error", str(ex)) alert.exec_() print(ex) elif action == copy_action: if data != "": items = get_list_folder(get_data_folder(), get_data_folder()) inp = QComboDialog("Copy", "Select the destination folder", QComboDialog.ComboBox, items) ok = inp.exec_() # item, ok = QInputDialog.getItem(self, "Select folder dialog", # "Select the destination folder", items, 0, False) if ok and inp.select: folder = inp.select if inp.select.startswith(os.sep): folder = inp.select.replace(os.sep, "", 1) new = os.path.join(get_data_folder(), folder, os.path.basename(data)) try: copyfile(data, new) except Exception as ex: alert = Alert("Error", "Copy file error", str(ex)) alert.exec_() print(ex) def open_event(self, index): item = self.model.itemFromIndex(index) if item is not None: data = item.data() data = os.path.join(get_data_folder(), data) if os.path.isdir(data): level = 1 else: level = 2 if data != "": if level == 2: self.on_menu_select.emit("open", data) elif level == 1: if not self.tree.isExpanded(item.index()): self.tree.collapse(item.index()) else: self.tree.expand(item.index())
class Navigation(QWidget): """ Navigation class definition. Provide a combobox to switch on each opened directories and display it into a tree view Provide 2 useful function (to use in alter module): - add_action(name, shortcut, callback) - callback take 2 arguments : file_info and parent - add_separator() """ SETTINGS_DIRECTORIES = 'navigation_dirs' SETTINGS_CURRENT_DIR = 'navigation_current_dir' onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated") onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated") def __init__(self, parent=None): super(Navigation, self).__init__(parent) self.setObjectName("Navigation") self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) self.menu_button = QPushButton('Select directory', self) self.menu_button.setFlat(True) # self.menu_button.clicked.connect(self.on_menu_button_clicked) self.menu = QMenu(self) self.menu_button.setMenu(self.menu) self.menu_directories = QMenu(self) self.menu_directories.setTitle('Directories') self.menu_add_action('Open directory', self.open_directory, None, QKeySequence.Open) self.menu_add_separator() self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh) # @TODO invoke_all self.menu_add_separator() self.menu.addMenu(self.menu_directories) self.tree = QTreeView(self) self.model = FileSystemModel(self) self.tree.setModel(self.model) self.tree.setColumnHidden(1, True) self.tree.setColumnHidden(2, True) self.tree.setColumnHidden(3, True) self.tree.setHeaderHidden(True) # only to expand directory or activated with one click self.tree.clicked.connect(self.on_item_clicked) # else, for file use activated signal self.tree.activated.connect(self.on_item_activated) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.on_context_menu) self.widgets = collections.OrderedDict() self.widgets['menu_button'] = self.menu_button self.widgets['tree'] = self.tree # @ToDo: Alter.invoke_all('add_widget', self.widgets) for name, widget in self.widgets.items(): if name == 'menu_button': self.layout.addWidget(widget, 0, Qt.AlignLeft) else: self.layout.addWidget(widget) self.context_menu = QMenu(self) self.add_action('New file', QKeySequence.New, FileSystemHelper.new_file) self.add_separator() self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy) self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut) self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste) self.add_separator() self.add_action('Delete', QKeySequence.Delete, FileSystemHelper.delete) # @ToDo Alter.invoke_all('navigation_add_action', self) #restore previous session and data dirs = ModuleManager.core['settings'].Settings.value( self.SETTINGS_DIRECTORIES, None, True) for directory_path in dirs: name = os.path.basename(directory_path) self.menu_add_directory(name, directory_path) current_dir = ModuleManager.core['settings'].Settings.value( self.SETTINGS_CURRENT_DIR, '') if current_dir: for action in self.menu_directories.actions(): if action.data() == current_dir: action.trigger() self.menu_button.setFocusPolicy(Qt.NoFocus) self.menu_button.setFocusProxy(self.tree) def reset(self, file_info): self.model.beginResetModel() current_dir = ModuleManager.core['settings'].Settings.value( self.SETTINGS_CURRENT_DIR, '') if current_dir: for action in self.menu_directories.actions(): if action.data() == current_dir: action.trigger() def on_menu_button_clicked(self): pos = self.mapToGlobal(self.menu_button.pos()) menu_width = self.menu.sizeHint().width() pos.setY(pos.y() + self.menu_button.height()) # pos.setX(pos.x() + self.menu_button.width() - menu_width) if len(self.menu.actions()) > 0: self.menu.exec(pos) def menu_add_action(self, name, callback, data=None, shortcut=None, icon=None): action = QAction(name, self) if icon: action.setIcon(icon) if shortcut: action.setShortcut(shortcut) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) if data: action.setData(data) action.triggered.connect(callback) self.addAction(action) self.menu.addAction(action) def menu_add_directory(self, name, data): action = QAction(name, self) action.setData(data) action.triggered.connect(self.on_menu_action_triggered) self.menu_directories.addAction(action) return action def menu_add_separator(self): self.menu.addSeparator() def add_action(self, name, shortcut, callback, icon=None): """ Ajoute une action au context menu et au widget navigation lui même. Créer une fonction à la volé pour fournir des arguments aux fonctions associé aux actions. """ action = QAction(name, self) if icon: action.setIcon(icon) action.setShortcut(shortcut) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.__wrapper(callback)) self.addAction(action) self.context_menu.addAction(action) def add_separator(self): """Simple abstraction of self.context_menu.addSeparator()""" self.context_menu.addSeparator() def __wrapper(self, callback): def __new_function(): """ __new_function représente la forme de tous les callbacks connecté à une action pour pouvoir utiliser les raccourcis en même temps que le menu contextuel. """ action = self.sender() file_info = action.data() if not file_info: indexes = self.tree.selectedIndexes() if indexes: model_index = indexes[0] file_info = self.model.fileInfo(model_index) callback(file_info, self) elif action.shortcut() == QKeySequence.New: file_info = self.model.fileInfo(self.tree.rootIndex()) callback(file_info, self) else: callback(file_info, self) action.setData(None) return __new_function def question(self, text, informative_text=None): message_box = QMessageBox(self) message_box.setText(text) if informative_text: message_box.setInformativeText(informative_text) message_box.setStandardButtons(QMessageBox.No | QMessageBox.Yes) message_box.setDefaultButton(QMessageBox.No) return message_box.exec() def on_context_menu(self, point): model_index = self.tree.indexAt(point) file_info = self.model.fileInfo(model_index) # pour chaque action on met a jour les data (file_info) # puis on altère les actions (ex enabled) for action in self.context_menu.actions(): if not action.isSeparator(): action.setData(file_info) action.setEnabled(model_index.isValid()) if action.shortcut() == QKeySequence.New: action.setEnabled(True) if not model_index.isValid(): file_info = self.model.fileInfo(self.tree.rootIndex()) action.setData(file_info) if action.shortcut() == QKeySequence.Paste: enable = FileSystemHelper.ready() and model_index.isValid() action.setEnabled(enable) if action.shortcut() == QKeySequence.Delete: # remove directory only if is an empty directory if model_index.isValid() and file_info.isDir(): path = file_info.absoluteFilePath() # QDir(path).count() always contains '.' and '..' action.setEnabled(QDir(path).count() == 2) # @ToDo #Alter.invoke_all( # 'navigation_on_menu_action', # model_index, file_info, action, self) if len(self.context_menu.actions()) > 0: self.context_menu.exec(self.tree.mapToGlobal(point)) # reset action data, sinon y a des problèmes dans _new_function for action in self.context_menu.actions(): action.setData(None) def on_item_activated(self, index): qFileInfo = self.model.fileInfo(index) if qFileInfo.isDir(): self.onDirItemActivated.emit(qFileInfo) else: self.onFileItemActivated.emit(qFileInfo) def on_item_clicked(self, index): qFileInfo = self.model.fileInfo(index) if qFileInfo.isDir(): self.onDirItemActivated.emit(qFileInfo) self.tree.setExpanded(index, not self.tree.isExpanded(index)) else: self.onFileItemActivated.emit(qFileInfo) def open_directory(self): path = QFileDialog.getExistingDirectory(self, "Open Directory", ".") if path: name = os.path.basename(path) action = self.menu_add_directory(name, path) self.save_directories_path() action.trigger() def on_menu_action_triggered(self): action = self.sender() path = action.data() if path: self.model.setRootPath(path) self.tree.setRootIndex(self.model.index(path)) self.menu_button.setText(os.path.basename(path)) self.save_current_dir(path) def save_directories_path(self): ModuleManager.core['settings'].Settings.set_value( self.SETTINGS_DIRECTORIES, [action.data() for action in self.menu_directories.actions()]) def save_current_dir(self, path): ModuleManager.core['settings'].Settings.set_value( self.SETTINGS_CURRENT_DIR, path)
class Navigation(QWidget): """ Navigation class definition. Provide a combobox to switch on each opened directories and display it into a tree view Provide 2 useful function (to use in alter module): - add_action(name, shortcut, callback) - callback take 2 arguments : file_info and parent - add_separator() """ SETTINGS_DIRECTORIES = 'navigation_dirs' SETTINGS_CURRENT_DIR = 'navigation_current_dir' onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated") onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated") def __init__(self, parent=None): super(Navigation, self).__init__(parent) self.setObjectName("Navigation") self.layout = QVBoxLayout(self) self.layout.setSpacing(0) self.layout.setContentsMargins(0,0,0,0) self.menu_button = QPushButton('Select directory', self) self.menu_button.setFlat(True) # self.menu_button.clicked.connect(self.on_menu_button_clicked) self.menu = QMenu(self) self.menu_button.setMenu(self.menu) self.menu_directories = QMenu(self) self.menu_directories.setTitle('Directories') self.menu_add_action( 'Open directory', self.open_directory, None, QKeySequence.Open) self.menu_add_separator() self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh) # @TODO invoke_all self.menu_add_separator() self.menu.addMenu(self.menu_directories) self.tree = QTreeView(self) self.model = FileSystemModel(self) self.tree.setModel(self.model) self.tree.setColumnHidden(1, True) self.tree.setColumnHidden(2, True) self.tree.setColumnHidden(3, True) self.tree.setHeaderHidden(True) # only to expand directory or activated with one click self.tree.clicked.connect(self.on_item_clicked) # else, for file use activated signal self.tree.activated.connect(self.on_item_activated) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.tree.customContextMenuRequested.connect(self.on_context_menu) self.widgets = collections.OrderedDict() self.widgets['menu_button'] = self.menu_button self.widgets['tree'] = self.tree # @ToDo: Alter.invoke_all('add_widget', self.widgets) for name, widget in self.widgets.items(): if name == 'menu_button': self.layout.addWidget(widget, 0, Qt.AlignLeft) else: self.layout.addWidget(widget) self.context_menu = QMenu(self) self.add_action('New file', QKeySequence.New, FileSystemHelper.new_file) self.add_action('New Directory', '', FileSystemHelper.new_directory) self.add_separator() self.add_action('Rename', '', FileSystemHelper.rename) self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy) self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut) self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste) self.add_separator() self.add_action('Delete', QKeySequence.Delete, FileSystemHelper.delete) # @ToDo Alter.invoke_all('navigation_add_action', self) #restore previous session and data dirs = ModuleManager.core['settings'].Settings.value( self.SETTINGS_DIRECTORIES, None, True) for directory_path in dirs: name = os.path.basename(directory_path) self.menu_add_directory(name, directory_path) current_dir = ModuleManager.core['settings'].Settings.value( self.SETTINGS_CURRENT_DIR, '') if current_dir: for action in self.menu_directories.actions(): if action.data() == current_dir: action.trigger() self.menu_button.setFocusPolicy(Qt.NoFocus) self.menu_button.setFocusProxy(self.tree) def reset(self, file_info): self.model.beginResetModel() current_dir = ModuleManager.core['settings'].Settings.value( self.SETTINGS_CURRENT_DIR, '') if current_dir: for action in self.menu_directories.actions(): if action.data() == current_dir: action.trigger() def on_menu_button_clicked(self): pos = self.mapToGlobal(self.menu_button.pos()) menu_width = self.menu.sizeHint().width() pos.setY(pos.y() + self.menu_button.height()) # pos.setX(pos.x() + self.menu_button.width() - menu_width) if len(self.menu.actions()) > 0: self.menu.exec(pos) def menu_add_action(self, name, callback, data=None, shortcut=None, icon=None): action = QAction(name, self) if icon: action.setIcon(icon) if shortcut: action.setShortcut(shortcut) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) if data: action.setData(data) action.triggered.connect(callback) self.addAction(action) self.menu.addAction(action) def menu_add_directory(self, name, data): action = QAction(name, self) action.setData(data) action.triggered.connect(self.on_menu_action_triggered) self.menu_directories.addAction(action) return action def menu_add_separator(self): self.menu.addSeparator() def add_action(self, name, shortcut, callback, icon = None): """ Ajoute une action au context menu et au widget navigation lui même. Créer une fonction à la volé pour fournir des arguments aux fonctions associé aux actions. """ action = QAction(name, self) if icon: action.setIcon(icon) action.setShortcut(shortcut) action.setShortcutContext(Qt.WidgetWithChildrenShortcut) action.triggered.connect(self.__wrapper(callback)) self.addAction(action) self.context_menu.addAction(action) def add_separator(self): """Simple abstraction of self.context_menu.addSeparator()""" self.context_menu.addSeparator() def __wrapper(self, callback): def __new_function(): """ __new_function représente la forme de tous les callbacks connecté à une action pour pouvoir utiliser les raccourcis en même temps que le menu contextuel. """ action = self.sender() file_info = action.data() if not file_info: indexes = self.tree.selectedIndexes() if indexes: model_index = indexes[0] file_info = self.model.fileInfo(model_index) callback(file_info, self) elif action.shortcut() == QKeySequence.New: file_info = self.model.fileInfo(self.tree.rootIndex()) callback(file_info, self) else: callback(file_info, self) action.setData(None) return __new_function def question(self, text, informative_text = None): message_box = QMessageBox(self) message_box.setText(text) if informative_text: message_box.setInformativeText(informative_text) message_box.setStandardButtons( QMessageBox.No | QMessageBox.Yes) message_box.setDefaultButton(QMessageBox.No) return message_box.exec() def on_context_menu(self, point): model_index = self.tree.indexAt(point) file_info = self.model.fileInfo(model_index) # pour chaque action on met a jour les data (file_info) # puis on altère les actions (ex enabled) for action in self.context_menu.actions(): if not action.isSeparator(): action.setData(file_info) action.setEnabled(model_index.isValid()) if action.shortcut() == QKeySequence.New: action.setEnabled(True) if not model_index.isValid(): file_info = self.model.fileInfo(self.tree.rootIndex()) action.setData(file_info) if action.shortcut() == QKeySequence.Paste: enable = FileSystemHelper.ready() and model_index.isValid() action.setEnabled(enable) if action.shortcut() == QKeySequence.Delete: # remove directory only if is an empty directory if model_index.isValid() and file_info.isDir(): path = file_info.absoluteFilePath() # QDir(path).count() always contains '.' and '..' action.setEnabled(QDir(path).count() == 2) # @ToDo #Alter.invoke_all( # 'navigation_on_menu_action', # model_index, file_info, action, self) if len(self.context_menu.actions()) > 0: self.context_menu.exec(self.tree.mapToGlobal(point)) # reset action data, sinon y a des problèmes dans _new_function for action in self.context_menu.actions(): action.setData(None) def on_item_activated(self, index): qFileInfo = self.model.fileInfo(index) if qFileInfo.isDir(): self.onDirItemActivated.emit(qFileInfo) else: self.onFileItemActivated.emit(qFileInfo) def on_item_clicked(self, index): qFileInfo = self.model.fileInfo(index) if qFileInfo.isDir(): self.onDirItemActivated.emit(qFileInfo) self.tree.setExpanded(index, not self.tree.isExpanded(index)) else: self.onFileItemActivated.emit(qFileInfo) def open_directory(self): project = ModuleManager.core['settings'].Settings.value( self.SETTINGS_CURRENT_DIR, '') path = QFileDialog.getExistingDirectory(self, "Open Directory", project) if path: name = os.path.basename(path) action = self.menu_add_directory(name, path) self.save_directories_path() action.trigger() def on_menu_action_triggered(self): action = self.sender() path = action.data() if path: self.model.setRootPath(path) self.tree.setRootIndex(self.model.index(path)) self.menu_button.setText(os.path.basename(path)) self.save_current_dir(path) def save_directories_path(self): ModuleManager.core['settings'].Settings.set_value( self.SETTINGS_DIRECTORIES, [action.data() for action in self.menu_directories.actions()] ) def save_current_dir(self, path): ModuleManager.core['settings'].Settings.set_value( self.SETTINGS_CURRENT_DIR, path )
class DataViewer(QWidget): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setMinimumSize(500, 400) main_layout = QHBoxLayout() main_layout.setSpacing(5) self.setLayout(main_layout) control_column = QVBoxLayout() main_layout.addLayout(control_column, stretch=1) refresh_button = QPushButton('🔄 Refresh') refresh_button.setFont(cn.EMOJI_FONT) refresh_button.clicked.connect(self.refresh_list) control_column.addWidget(refresh_button) self.gesture_tree_view = QTreeView() self.gesture_tree_view.setMinimumWidth(250) self.gesture_tree_view.header().hide() self.gesture_tree_view.setEditTriggers( QAbstractItemView.NoEditTriggers) self.gesture_tree_view.clicked.connect(self.show_selected) self.gesture_tree_view.setContextMenuPolicy(Qt.CustomContextMenu) self.gesture_tree_view.customContextMenuRequested.connect( self.gesture_context_menu) self.gesture_model = QStandardItemModel() self.gesture_tree_view.setModel(self.gesture_model) self.gesture_tree_view.setAnimated(True) control_column.addWidget(self.gesture_tree_view) self.displayed_gestures_layout = QVBoxLayout() display_column = QVBoxLayout() close_all_button = QPushButton('❌ Close all opened') close_all_button.setFont(cn.EMOJI_FONT) def close_all_displayed_gestures(): for i in range(self.displayed_gestures_layout.count()): self.displayed_gestures_layout.itemAt(i).widget().close() close_all_button.clicked.connect(close_all_displayed_gestures) control_column.addWidget(close_all_button) display_column.addLayout( VerticalScrollableExtension(self.displayed_gestures_layout)) main_layout.addLayout(display_column, stretch=2) self.refresh_list() def refresh_list(self): gestures = sorted(os.listdir(cn.DATA_FOLDER)) gesture_tree = {} for gesture in gestures: parts = gesture.split(cn.FILE_NAME_SEPARATOR) if parts[0] == cn.SESSION_PREFIX: continue if len(parts) < 3 or parts[0] != cn.GESTURE_PREFIX: logger.debug(f'Skipping file {gesture}, unknown naming.') continue index = int(parts[1]) if index < 0 or index >= len(cn.GESTURES): logger.debug(f'Invalid index on {gesture}, skipping.') continue gesture = cn.GESTURES[index] parts[1] = str(gesture) current_node = gesture_tree for part in parts[1:]: current_node = current_node.setdefault(part, {}) self.gesture_model.clear() root = self.gesture_model.invisibleRootItem() def add_tree(tree: dict, node: QStandardItem): for item in tree: child_node = QStandardItem(item) node.appendRow(child_node) add_tree(tree[item], child_node) add_tree(gesture_tree, root) @staticmethod def get_filename(model_index): name = [] node = model_index while node.isValid(): name.append(node.data()) node = node.parent() name.append(cn.GESTURE_PREFIX) # TODO this could be nicer for i, gesture_spec in enumerate(cn.GESTURES): if str(gesture_spec) == name[-2]: name[-2] = str(i) return cn.FILE_NAME_SEPARATOR.join(name[::-1]) def show_selected(self, model_index): is_leaf = not model_index.child(0, 0).isValid() if not is_leaf: self.gesture_tree_view.setExpanded( model_index, not self.gesture_tree_view.isExpanded(model_index)) return filename = DataViewer.get_filename(model_index) selected_file = cn.DATA_FOLDER / filename data = np.load(selected_file) signal_widget = StaticSignalWidget() signal_widget.plot_data(data) widget = NamedExtension(filename, signal_widget) widget = BlinkExtension(widget) widget = ClosableExtension(widget) widget.setMinimumWidth(600) widget.setFixedHeight(200) self.displayed_gestures_layout.addWidget(widget) def gesture_context_menu(self, point): model_index = self.gesture_tree_view.indexAt(point) is_leaf = not model_index.child(0, 0).isValid() if not is_leaf: self.gesture_tree_view.setExpanded( model_index, not self.gesture_tree_view.isExpanded(model_index)) return menu = QMenu() def move_dialog(): Renamer(DataViewer.get_filename(model_index)).exec() menu.addAction('Move', move_dialog) def trash_and_remove_from_tree(): if Renamer.trash_gesture(DataViewer.get_filename(model_index)): self.gesture_model.removeRow(model_index.row(), model_index.parent()) menu.addAction('Trash', trash_and_remove_from_tree) menu.exec(QCursor.pos())