コード例 #1
0
ファイル: FileTreeView.py プロジェクト: sonnts996/XuRequest
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())
コード例 #2
0
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)
コード例 #3
0
ファイル: navigation.py プロジェクト: lheido/Mojuru
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
        )
コード例 #4
0
ファイル: data_viewer.py プロジェクト: Zajozor/gesture
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())