Ejemplo n.º 1
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)
Ejemplo n.º 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_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
        )
Ejemplo n.º 3
0
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())