예제 #1
0
파일: layer_tree.py 프로젝트: ssec/sift
    def _init_widget(self, listbox: QTreeView):
        listbox.setModel(self)
        listbox.setItemDelegate(self.item_delegate)
        listbox.setContextMenuPolicy(Qt.CustomContextMenu)
        # listbox.customContextMenuRequested.connect(self.context_menu)
        listbox.customContextMenuRequested.connect(self.menu)
        listbox.setDragEnabled(True)
        listbox.setAcceptDrops(True)
        listbox.setDropIndicatorShown(True)
        listbox.setSelectionMode(listbox.ExtendedSelection)
        # listbox.setMovement(QTreeView.Snap)
        # listbox.setDragDropMode(QTreeView.InternalMove)
        listbox.setDragDropMode(QAbstractItemView.DragDrop)
        # listbox.setAlternatingRowColors(True)
        # listbox.setDefaultDropAction(Qt.MoveAction)
        # listbox.setDragDropOverwriteMode(True)
        # listbox.entered.connect(self.layer_entered)
        # listbox.setFont(QFont('Andale Mono', 13))

        # the various signals that may result from the user changing the selections
        # listbox.activated.connect(self.changedSelection)
        # listbox.clicked.connect(self.changedSelection)
        # listbox.doubleClicked.connect(self.changedSelection)
        # listbox.pressed.connect(self.changedSelection)
        listbox.selectionModel().selectionChanged.connect(
            self.changedSelection)

        self.widgets.append(listbox)
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Drag & Drop')

        # Даем разрешение на Drop
        self.setAcceptDrops(True)

        self.list_files = QListWidget()
        self.label_total_files = QLabel()

        model = QFileSystemModel()
        model.setRootPath(QDir.currentPath())
        model.setReadOnly(False)

        self.tree = QTreeView()
        self.tree.setModel(model)
        self.tree.setRootIndex(model.index(QDir.currentPath()))
        self.tree.setSelectionMode(QTreeView.SingleSelection)
        self.tree.setDragDropMode(QTreeView.InternalMove)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.tree)
        main_layout.addWidget(QLabel('Перетащите файл:'))
        main_layout.addWidget(self.list_files)
        main_layout.addWidget(self.label_total_files)

        central_widget = QWidget()
        central_widget.setLayout(main_layout)

        self.setCentralWidget(central_widget)

        self._update_states()

    def _update_states(self):
        self.label_total_files.setText('Files: {}'.format(
            self.list_files.count()))

    def dragEnterEvent(self, event):
        # Тут выполняются проверки и дается (или нет) разрешение на Drop

        mime = event.mimeData()

        # Если перемещаются ссылки
        if mime.hasUrls():
            # Разрешаем
            event.acceptProposedAction()

    def dropEvent(self, event):
        # Обработка события Drop

        for url in event.mimeData().urls():
            file_name = url.toLocalFile()
            self.list_files.addItem(file_name)

        self._update_states()

        return super().dropEvent(event)
예제 #3
0
class DirectoriesDialog(QMainWindow):
    def __init__(self, app, **kwargs):
        super().__init__(None, **kwargs)
        self.app = app
        self.specific_actions = set()
        self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
        self.recentFolders = Recent(self.app, "recentFolders")
        self._setupUi()
        self._updateScanTypeList()
        self.directoriesModel = DirectoriesModel(self.app.model.directory_tree,
                                                 view=self.treeView)
        self.directoriesDelegate = DirectoriesDelegate()
        self.treeView.setItemDelegate(self.directoriesDelegate)
        self._setupColumns()
        self.app.recentResults.addMenu(self.menuLoadRecent)
        self.app.recentResults.addMenu(self.menuRecentResults)
        self.recentFolders.addMenu(self.menuRecentFolders)
        self._updateAddButton()
        self._updateRemoveButton()
        self._updateLoadResultsButton()
        self._updateActionsState()
        self._setupBindings()

    def _setupBindings(self):
        self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected)
        self.showPreferencesButton.clicked.connect(
            self.app.actionPreferences.trigger)
        self.scanButton.clicked.connect(self.scanButtonClicked)
        self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
        self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
        self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked)
        self.treeView.selectionModel().selectionChanged.connect(
            self.selectionChanged)
        self.app.recentResults.itemsChanged.connect(
            self._updateLoadResultsButton)
        self.recentFolders.itemsChanged.connect(self._updateAddButton)
        self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
        self.directoriesModel.foldersAdded.connect(
            self.directoriesModelAddedFolders)
        self.app.willSavePrefs.connect(self.appWillSavePrefs)

    def _setupActions(self):
        # (name, shortcut, icon, desc, func)
        ACTIONS = [
            (
                "actionLoadResults",
                "Ctrl+L",
                "",
                tr("Load Results..."),
                self.loadResultsTriggered,
            ),
            (
                "actionShowResultsWindow",
                "",
                "",
                tr("Scan Results"),
                self.app.showResultsWindow,
            ),
            ("actionAddFolder", "", "", tr("Add Folder..."),
             self.addFolderTriggered),
            ("actionLoadDirectories", "", "", tr("Load Directories..."),
             self.loadDirectoriesTriggered),
            ("actionSaveDirectories", "", "", tr("Save Directories..."),
             self.saveDirectoriesTriggered),
        ]
        createActions(ACTIONS, self)
        if self.app.use_tabs:
            # Keep track of actions which should only be accessible from this window
            self.specific_actions.add(self.actionLoadDirectories)
            self.specific_actions.add(self.actionSaveDirectories)

    def _setupMenu(self):
        if not self.app.use_tabs:
            # we are our own QMainWindow, we need our own menu bar
            self.menubar = QMenuBar(self)
            self.menubar.setGeometry(QRect(0, 0, 42, 22))
            self.menuFile = QMenu(self.menubar)
            self.menuFile.setTitle(tr("File"))
            self.menuView = QMenu(self.menubar)
            self.menuView.setTitle(tr("View"))
            self.menuHelp = QMenu(self.menubar)
            self.menuHelp.setTitle(tr("Help"))
            self.setMenuBar(self.menubar)
            menubar = self.menubar
        else:
            # we are part of a tab widget, we populate its window's menubar instead
            self.menuFile = self.app.main_window.menuFile
            self.menuView = self.app.main_window.menuView
            self.menuHelp = self.app.main_window.menuHelp
            menubar = self.app.main_window.menubar

        self.menuLoadRecent = QMenu(self.menuFile)
        self.menuLoadRecent.setTitle(tr("Load Recent Results"))

        self.menuFile.addAction(self.actionLoadResults)
        self.menuFile.addAction(self.menuLoadRecent.menuAction())
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionClearPictureCache)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.actionLoadDirectories)
        self.menuFile.addAction(self.actionSaveDirectories)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionQuit)

        self.menuView.addAction(self.app.actionDirectoriesWindow)
        self.menuView.addAction(self.actionShowResultsWindow)
        self.menuView.addAction(self.app.actionIgnoreList)
        self.menuView.addSeparator()
        self.menuView.addAction(self.app.actionPreferences)

        self.menuHelp.addAction(self.app.actionShowHelp)
        self.menuHelp.addAction(self.app.actionOpenDebugLog)
        self.menuHelp.addAction(self.app.actionAbout)

        menubar.addAction(self.menuFile.menuAction())
        menubar.addAction(self.menuView.menuAction())
        menubar.addAction(self.menuHelp.menuAction())

        # Recent folders menu
        self.menuRecentFolders = QMenu()
        self.menuRecentFolders.addAction(self.actionAddFolder)
        self.menuRecentFolders.addSeparator()

        # Recent results menu
        self.menuRecentResults = QMenu()
        self.menuRecentResults.addAction(self.actionLoadResults)
        self.menuRecentResults.addSeparator()

    def _setupUi(self):
        self.setWindowTitle(self.app.NAME)
        self.resize(420, 338)
        self.centralwidget = QWidget(self)
        self.verticalLayout = QVBoxLayout(self.centralwidget)
        self.verticalLayout.setContentsMargins(4, 0, 4, 0)
        self.verticalLayout.setSpacing(0)
        hl = QHBoxLayout()
        label = QLabel(tr("Application Mode:"), self)
        label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hl.addWidget(label)
        self.appModeRadioBox = RadioBox(
            self,
            items=[tr("Standard"), tr("Music"),
                   tr("Picture")],
            spread=False)
        hl.addWidget(self.appModeRadioBox)
        self.verticalLayout.addLayout(hl)
        hl = QHBoxLayout()
        hl.setAlignment(Qt.AlignLeft)
        label = QLabel(tr("Scan Type:"), self)
        label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hl.addWidget(label)
        self.scanTypeComboBox = QComboBox(self)
        self.scanTypeComboBox.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.scanTypeComboBox.setMaximumWidth(400)
        hl.addWidget(self.scanTypeComboBox)
        self.showPreferencesButton = QPushButton(tr("More Options"),
                                                 self.centralwidget)
        self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed,
                                                 QSizePolicy.Fixed)
        hl.addWidget(self.showPreferencesButton)
        self.verticalLayout.addLayout(hl)
        self.promptLabel = QLabel(
            tr('Select folders to scan and press "Scan".'), self.centralwidget)
        self.verticalLayout.addWidget(self.promptLabel)
        self.treeView = QTreeView(self.centralwidget)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeView.setAcceptDrops(True)
        triggers = (QAbstractItemView.DoubleClicked
                    | QAbstractItemView.EditKeyPressed
                    | QAbstractItemView.SelectedClicked)
        self.treeView.setEditTriggers(triggers)
        self.treeView.setDragDropOverwriteMode(True)
        self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
        self.treeView.setUniformRowHeights(True)
        self.verticalLayout.addWidget(self.treeView)
        self.horizontalLayout = QHBoxLayout()
        self.removeFolderButton = QPushButton(self.centralwidget)
        self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus")))
        self.removeFolderButton.setShortcut("Del")
        self.horizontalLayout.addWidget(self.removeFolderButton)
        self.addFolderButton = QPushButton(self.centralwidget)
        self.addFolderButton.setIcon(QIcon(QPixmap(":/plus")))
        self.horizontalLayout.addWidget(self.addFolderButton)
        spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                  QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.loadResultsButton = QPushButton(self.centralwidget)
        self.loadResultsButton.setText(tr("Load Results"))
        self.horizontalLayout.addWidget(self.loadResultsButton)
        self.scanButton = QPushButton(self.centralwidget)
        self.scanButton.setText(tr("Scan"))
        self.scanButton.setDefault(True)
        self.horizontalLayout.addWidget(self.scanButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.setCentralWidget(self.centralwidget)

        self._setupActions()
        self._setupMenu()

        if self.app.prefs.directoriesWindowRect is not None:
            self.setGeometry(self.app.prefs.directoriesWindowRect)
        else:
            moveToScreenCenter(self)

    def _setupColumns(self):
        header = self.treeView.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Fixed)
        header.resizeSection(1, 100)

    def _updateActionsState(self):
        self.actionShowResultsWindow.setEnabled(
            self.app.resultWindow is not None)

    def _updateAddButton(self):
        if self.recentFolders.isEmpty():
            self.addFolderButton.setMenu(None)
        else:
            self.addFolderButton.setMenu(self.menuRecentFolders)

    def _updateRemoveButton(self):
        indexes = self.treeView.selectedIndexes()
        if not indexes:
            self.removeFolderButton.setEnabled(False)
            return
        self.removeFolderButton.setEnabled(True)

    def _updateLoadResultsButton(self):
        if self.app.recentResults.isEmpty():
            self.loadResultsButton.setMenu(None)
        else:
            self.loadResultsButton.setMenu(self.menuRecentResults)

    def _updateScanTypeList(self):
        try:
            self.scanTypeComboBox.currentIndexChanged[int].disconnect(
                self.scanTypeChanged)
        except TypeError:
            # Not connected, ignore
            pass
        self.scanTypeComboBox.clear()
        scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
        for scan_option in scan_options:
            self.scanTypeComboBox.addItem(scan_option.label)
        SCAN_TYPE_ORDER = [so.scan_type for so in scan_options]
        selected_scan_type = self.app.prefs.get_scan_type(
            self.app.model.app_mode)
        scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type)
        self.scanTypeComboBox.setCurrentIndex(scan_type_index)
        self.scanTypeComboBox.currentIndexChanged[int].connect(
            self.scanTypeChanged)
        self.app._update_options()

    # --- QWidget overrides
    def closeEvent(self, event):
        event.accept()
        if self.app.model.results.is_modified:
            title = tr("Unsaved results")
            msg = tr("You have unsaved results, do you really want to quit?")
            if not self.app.confirm(title, msg):
                event.ignore()
        if event.isAccepted():
            self.app.shutdown()

    # --- Events
    def addFolderTriggered(self):
        title = tr("Select a folder to add to the scanning list")
        flags = QFileDialog.ShowDirsOnly
        dirpath = str(
            QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder,
                                             flags))
        if not dirpath:
            return
        self.lastAddedFolder = dirpath
        self.app.model.add_directory(dirpath)
        self.recentFolders.insertItem(dirpath)

    def appModeButtonSelected(self, index):
        if index == 2:
            mode = AppMode.Picture
        elif index == 1:
            mode = AppMode.Music
        else:
            mode = AppMode.Standard
        self.app.model.app_mode = mode
        self._updateScanTypeList()

    def appWillSavePrefs(self):
        self.app.prefs.directoriesWindowRect = self.geometry()

    def directoriesModelAddedFolders(self, folders):
        for folder in folders:
            self.recentFolders.insertItem(folder)

    def loadResultsTriggered(self):
        title = tr("Select a results file to load")
        files = ";;".join(
            [tr("dupeGuru Results (*.dupeguru)"),
             tr("All Files (*.*)")])
        destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
        if destination:
            self.app.model.load_from(destination)
            self.app.recentResults.insertItem(destination)

    def loadDirectoriesTriggered(self):
        title = tr("Select a directories file to load")
        files = ";;".join(
            [tr("dupeGuru Results (*.dupegurudirs)"),
             tr("All Files (*.*)")])
        destination = QFileDialog.getOpenFileName(self, title, "", files)[0]
        if destination:
            self.app.model.load_directories(destination)

    def removeFolderButtonClicked(self):
        self.directoriesModel.model.remove_selected()

    def saveDirectoriesTriggered(self):
        title = tr("Select a file to save your directories to")
        files = tr("dupeGuru Directories (*.dupegurudirs)")
        destination, chosen_filter = QFileDialog.getSaveFileName(
            self, title, "", files)
        if destination:
            if not destination.endswith(".dupegurudirs"):
                destination = "{}.dupegurudirs".format(destination)
            self.app.model.save_directories_as(destination)

    def scanButtonClicked(self):
        if self.app.model.results.is_modified:
            title = tr("Start a new scan")
            msg = tr(
                "You have unsaved results, do you really want to continue?")
            if not self.app.confirm(title, msg):
                return
        self.app.model.start_scanning()

    def scanTypeChanged(self, index):
        scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
        self.app.prefs.set_scan_type(self.app.model.app_mode,
                                     scan_options[index].scan_type)
        self.app._update_options()

    def selectionChanged(self, selected, deselected):
        self._updateRemoveButton()
예제 #4
0
class DirectoriesDialog(QMainWindow):
    def __init__(self, app, **kwargs):
        super().__init__(None, **kwargs)
        self.app = app
        self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
        self.recentFolders = Recent(self.app, 'recentFolders')
        self._setupUi()
        self.directoriesModel = DirectoriesModel(self.app.model.directory_tree,
                                                 view=self.treeView)
        self.directoriesDelegate = DirectoriesDelegate()
        self.treeView.setItemDelegate(self.directoriesDelegate)
        self._setupColumns()
        self.app.recentResults.addMenu(self.menuLoadRecent)
        self.app.recentResults.addMenu(self.menuRecentResults)
        self.recentFolders.addMenu(self.menuRecentFolders)
        self._updateAddButton()
        self._updateRemoveButton()
        self._updateLoadResultsButton()
        self._setupBindings()

    def _setupBindings(self):
        self.scanButton.clicked.connect(self.scanButtonClicked)
        self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
        self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
        self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked)
        self.treeView.selectionModel().selectionChanged.connect(
            self.selectionChanged)
        self.app.recentResults.itemsChanged.connect(
            self._updateLoadResultsButton)
        self.recentFolders.itemsChanged.connect(self._updateAddButton)
        self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
        self.directoriesModel.foldersAdded.connect(
            self.directoriesModelAddedFolders)
        self.app.willSavePrefs.connect(self.appWillSavePrefs)

    def _setupActions(self):
        # (name, shortcut, icon, desc, func)
        ACTIONS = [
            ('actionLoadResults', 'Ctrl+L', '', tr("Load Results..."),
             self.loadResultsTriggered),
            ('actionShowResultsWindow', '', '', tr("Results Window"),
             self.app.showResultsWindow),
            ('actionAddFolder', '', '', tr("Add Folder..."),
             self.addFolderTriggered),
        ]
        createActions(ACTIONS, self)

    def _setupMenu(self):
        self.menubar = QMenuBar(self)
        self.menubar.setGeometry(QRect(0, 0, 42, 22))
        self.menuFile = QMenu(self.menubar)
        self.menuFile.setTitle(tr("File"))
        self.menuView = QMenu(self.menubar)
        self.menuView.setTitle(tr("View"))
        self.menuHelp = QMenu(self.menubar)
        self.menuHelp.setTitle(tr("Help"))
        self.menuLoadRecent = QMenu(self.menuFile)
        self.menuLoadRecent.setTitle(tr("Load Recent Results"))
        self.setMenuBar(self.menubar)

        self.menuFile.addAction(self.actionLoadResults)
        self.menuFile.addAction(self.menuLoadRecent.menuAction())
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionQuit)
        self.menuView.addAction(self.app.actionPreferences)
        self.menuView.addAction(self.actionShowResultsWindow)
        self.menuView.addAction(self.app.actionIgnoreList)
        self.menuHelp.addAction(self.app.actionShowHelp)
        self.menuHelp.addAction(self.app.actionOpenDebugLog)
        self.menuHelp.addAction(self.app.actionAbout)

        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuView.menuAction())
        self.menubar.addAction(self.menuHelp.menuAction())

        # Recent folders menu
        self.menuRecentFolders = QMenu()
        self.menuRecentFolders.addAction(self.actionAddFolder)
        self.menuRecentFolders.addSeparator()

        # Recent results menu
        self.menuRecentResults = QMenu()
        self.menuRecentResults.addAction(self.actionLoadResults)
        self.menuRecentResults.addSeparator()

    def _setupUi(self):
        self.setWindowTitle(self.app.NAME)
        self.resize(420, 338)
        self.centralwidget = QWidget(self)
        self.verticalLayout = QVBoxLayout(self.centralwidget)
        self.promptLabel = QLabel(
            tr("Select folders to scan and press \"Scan\"."),
            self.centralwidget)
        self.verticalLayout.addWidget(self.promptLabel)
        self.treeView = QTreeView(self.centralwidget)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeView.setAcceptDrops(True)
        triggers = QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed\
            |QAbstractItemView.SelectedClicked
        self.treeView.setEditTriggers(triggers)
        self.treeView.setDragDropOverwriteMode(True)
        self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
        self.treeView.setUniformRowHeights(True)
        self.verticalLayout.addWidget(self.treeView)
        self.horizontalLayout = QHBoxLayout()
        self.removeFolderButton = QPushButton(self.centralwidget)
        self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus")))
        self.removeFolderButton.setShortcut("Del")
        self.horizontalLayout.addWidget(self.removeFolderButton)
        self.addFolderButton = QPushButton(self.centralwidget)
        self.addFolderButton.setIcon(QIcon(QPixmap(":/plus")))
        self.horizontalLayout.addWidget(self.addFolderButton)
        spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                  QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.loadResultsButton = QPushButton(self.centralwidget)
        self.loadResultsButton.setText(tr("Load Results"))
        self.horizontalLayout.addWidget(self.loadResultsButton)
        self.scanButton = QPushButton(self.centralwidget)
        self.scanButton.setText(tr("Scan"))
        self.scanButton.setDefault(True)
        self.horizontalLayout.addWidget(self.scanButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.setCentralWidget(self.centralwidget)

        self._setupActions()
        self._setupMenu()

        if self.app.prefs.directoriesWindowRect is not None:
            self.setGeometry(self.app.prefs.directoriesWindowRect)
        else:
            moveToScreenCenter(self)

    def _setupColumns(self):
        header = self.treeView.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Fixed)
        header.resizeSection(1, 100)

    def _updateAddButton(self):
        if self.recentFolders.isEmpty():
            self.addFolderButton.setMenu(None)
        else:
            self.addFolderButton.setMenu(self.menuRecentFolders)

    def _updateRemoveButton(self):
        indexes = self.treeView.selectedIndexes()
        if not indexes:
            self.removeFolderButton.setEnabled(False)
            return
        self.removeFolderButton.setEnabled(True)

    def _updateLoadResultsButton(self):
        if self.app.recentResults.isEmpty():
            self.loadResultsButton.setMenu(None)
        else:
            self.loadResultsButton.setMenu(self.menuRecentResults)

    #--- QWidget overrides
    def closeEvent(self, event):
        event.accept()
        if self.app.model.results.is_modified:
            title = tr("Unsaved results")
            msg = tr("You have unsaved results, do you really want to quit?")
            if not self.app.confirm(title, msg):
                event.ignore()
        if event.isAccepted():
            QApplication.quit()

    #--- Events
    def addFolderTriggered(self):
        title = tr("Select a folder to add to the scanning list")
        flags = QFileDialog.ShowDirsOnly
        dirpath = str(
            QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder,
                                             flags))
        if not dirpath:
            return
        self.lastAddedFolder = dirpath
        self.app.model.add_directory(dirpath)
        self.recentFolders.insertItem(dirpath)

    def appWillSavePrefs(self):
        self.app.prefs.directoriesWindowRect = self.geometry()

    def directoriesModelAddedFolders(self, folders):
        for folder in folders:
            self.recentFolders.insertItem(folder)

    def loadResultsTriggered(self):
        title = tr("Select a results file to load")
        files = ';;'.join(
            [tr("dupeGuru Results (*.dupeguru)"),
             tr("All Files (*.*)")])
        destination = QFileDialog.getOpenFileName(self, title, '', files)
        if destination:
            self.app.model.load_from(destination)
            self.app.recentResults.insertItem(destination)

    def removeFolderButtonClicked(self):
        self.directoriesModel.model.remove_selected()

    def scanButtonClicked(self):
        if self.app.model.results.is_modified:
            title = tr("Start a new scan")
            msg = tr(
                "You have unsaved results, do you really want to continue?")
            if not self.app.confirm(title, msg):
                return
        self.app.model.start_scanning()

    def selectionChanged(self, selected, deselected):
        self._updateRemoveButton()
예제 #5
0
class DirectoriesDialog(QMainWindow):
    def __init__(self, app, **kwargs):
        super().__init__(None, **kwargs)
        self.app = app
        self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
        self.recentFolders = Recent(self.app, 'recentFolders')
        self._setupUi()
        self._updateScanTypeList()
        self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
        self.directoriesDelegate = DirectoriesDelegate()
        self.treeView.setItemDelegate(self.directoriesDelegate)
        self._setupColumns()
        self.app.recentResults.addMenu(self.menuLoadRecent)
        self.app.recentResults.addMenu(self.menuRecentResults)
        self.recentFolders.addMenu(self.menuRecentFolders)
        self._updateAddButton()
        self._updateRemoveButton()
        self._updateLoadResultsButton()
        self._updateActionsState()
        self._setupBindings()

    def _setupBindings(self):
        self.appModeRadioBox.itemSelected.connect(self.appModeButtonSelected)
        self.showPreferencesButton.clicked.connect(self.app.actionPreferences.trigger)
        self.scanButton.clicked.connect(self.scanButtonClicked)
        self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
        self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
        self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked)
        self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged)
        self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton)
        self.recentFolders.itemsChanged.connect(self._updateAddButton)
        self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
        self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders)
        self.app.willSavePrefs.connect(self.appWillSavePrefs)

    def _setupActions(self):
        # (name, shortcut, icon, desc, func)
        ACTIONS = [
            ('actionLoadResults', 'Ctrl+L', '', tr("Load Results..."), self.loadResultsTriggered),
            ('actionShowResultsWindow', '', '', tr("Results Window"), self.app.showResultsWindow),
            ('actionAddFolder', '', '', tr("Add Folder..."), self.addFolderTriggered),
        ]
        createActions(ACTIONS, self)

    def _setupMenu(self):
        self.menubar = QMenuBar(self)
        self.menubar.setGeometry(QRect(0, 0, 42, 22))
        self.menuFile = QMenu(self.menubar)
        self.menuFile.setTitle(tr("File"))
        self.menuView = QMenu(self.menubar)
        self.menuView.setTitle(tr("View"))
        self.menuHelp = QMenu(self.menubar)
        self.menuHelp.setTitle(tr("Help"))
        self.menuLoadRecent = QMenu(self.menuFile)
        self.menuLoadRecent.setTitle(tr("Load Recent Results"))
        self.setMenuBar(self.menubar)

        self.menuFile.addAction(self.actionLoadResults)
        self.menuFile.addAction(self.menuLoadRecent.menuAction())
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionClearPictureCache)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionQuit)
        self.menuView.addAction(self.app.actionPreferences)
        self.menuView.addAction(self.actionShowResultsWindow)
        self.menuView.addAction(self.app.actionIgnoreList)
        self.menuHelp.addAction(self.app.actionShowHelp)
        self.menuHelp.addAction(self.app.actionOpenDebugLog)
        self.menuHelp.addAction(self.app.actionAbout)

        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuView.menuAction())
        self.menubar.addAction(self.menuHelp.menuAction())

        # Recent folders menu
        self.menuRecentFolders = QMenu()
        self.menuRecentFolders.addAction(self.actionAddFolder)
        self.menuRecentFolders.addSeparator()

        # Recent results menu
        self.menuRecentResults = QMenu()
        self.menuRecentResults.addAction(self.actionLoadResults)
        self.menuRecentResults.addSeparator()

    def _setupUi(self):
        self.setWindowTitle(self.app.NAME)
        self.resize(420, 338)
        self.centralwidget = QWidget(self)
        self.verticalLayout = QVBoxLayout(self.centralwidget)
        hl = QHBoxLayout()
        label = QLabel(tr("Application Mode:"), self)
        label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hl.addWidget(label)
        self.appModeRadioBox = RadioBox(
            self,
            items=[tr("Standard"), tr("Music"), tr("Picture")],
            spread=False
        )
        hl.addWidget(self.appModeRadioBox)
        self.verticalLayout.addLayout(hl)
        hl = QHBoxLayout()
        hl.setAlignment(Qt.AlignLeft)
        label = QLabel(tr("Scan Type:"), self)
        label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hl.addWidget(label)
        self.scanTypeComboBox = QComboBox(self)
        self.scanTypeComboBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        self.scanTypeComboBox.setMaximumWidth(400)
        hl.addWidget(self.scanTypeComboBox)
        self.showPreferencesButton = QPushButton(tr("More Options"), self.centralwidget)
        self.showPreferencesButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hl.addWidget(self.showPreferencesButton)
        self.verticalLayout.addLayout(hl)
        self.promptLabel = QLabel(tr("Select folders to scan and press \"Scan\"."), self.centralwidget)
        self.verticalLayout.addWidget(self.promptLabel)
        self.treeView = QTreeView(self.centralwidget)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeView.setAcceptDrops(True)
        triggers = QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed\
            | QAbstractItemView.SelectedClicked
        self.treeView.setEditTriggers(triggers)
        self.treeView.setDragDropOverwriteMode(True)
        self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
        self.treeView.setUniformRowHeights(True)
        self.verticalLayout.addWidget(self.treeView)
        self.horizontalLayout = QHBoxLayout()
        self.removeFolderButton = QPushButton(self.centralwidget)
        self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus")))
        self.removeFolderButton.setShortcut("Del")
        self.horizontalLayout.addWidget(self.removeFolderButton)
        self.addFolderButton = QPushButton(self.centralwidget)
        self.addFolderButton.setIcon(QIcon(QPixmap(":/plus")))
        self.horizontalLayout.addWidget(self.addFolderButton)
        spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.loadResultsButton = QPushButton(self.centralwidget)
        self.loadResultsButton.setText(tr("Load Results"))
        self.horizontalLayout.addWidget(self.loadResultsButton)
        self.scanButton = QPushButton(self.centralwidget)
        self.scanButton.setText(tr("Scan"))
        self.scanButton.setDefault(True)
        self.horizontalLayout.addWidget(self.scanButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.setCentralWidget(self.centralwidget)

        self._setupActions()
        self._setupMenu()

        if self.app.prefs.directoriesWindowRect is not None:
            self.setGeometry(self.app.prefs.directoriesWindowRect)
        else:
            moveToScreenCenter(self)

    def _setupColumns(self):
        header = self.treeView.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Fixed)
        header.resizeSection(1, 100)

    def _updateActionsState(self):
        self.actionShowResultsWindow.setEnabled(self.app.resultWindow is not None)

    def _updateAddButton(self):
        if self.recentFolders.isEmpty():
            self.addFolderButton.setMenu(None)
        else:
            self.addFolderButton.setMenu(self.menuRecentFolders)

    def _updateRemoveButton(self):
        indexes = self.treeView.selectedIndexes()
        if not indexes:
            self.removeFolderButton.setEnabled(False)
            return
        self.removeFolderButton.setEnabled(True)

    def _updateLoadResultsButton(self):
        if self.app.recentResults.isEmpty():
            self.loadResultsButton.setMenu(None)
        else:
            self.loadResultsButton.setMenu(self.menuRecentResults)

    def _updateScanTypeList(self):
        try:
            self.scanTypeComboBox.currentIndexChanged[int].disconnect(self.scanTypeChanged)
        except TypeError:
            # Not connected, ignore
            pass
        self.scanTypeComboBox.clear()
        scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
        for scan_option in scan_options:
            self.scanTypeComboBox.addItem(scan_option.label)
        SCAN_TYPE_ORDER = [so.scan_type for so in scan_options]
        selected_scan_type = self.app.prefs.get_scan_type(self.app.model.app_mode)
        scan_type_index = SCAN_TYPE_ORDER.index(selected_scan_type)
        self.scanTypeComboBox.setCurrentIndex(scan_type_index)
        self.scanTypeComboBox.currentIndexChanged[int].connect(self.scanTypeChanged)
        self.app._update_options()

    #--- QWidget overrides
    def closeEvent(self, event):
        event.accept()
        if self.app.model.results.is_modified:
            title = tr("Unsaved results")
            msg = tr("You have unsaved results, do you really want to quit?")
            if not self.app.confirm(title, msg):
                event.ignore()
        if event.isAccepted():
            QApplication.quit()

    #--- Events
    def addFolderTriggered(self):
        title = tr("Select a folder to add to the scanning list")
        flags = QFileDialog.ShowDirsOnly
        dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
        if not dirpath:
            return
        self.lastAddedFolder = dirpath
        self.app.model.add_directory(dirpath)
        self.recentFolders.insertItem(dirpath)

    def appModeButtonSelected(self, index):
        if index == 2:
            mode = AppMode.Picture
        elif index == 1:
            mode = AppMode.Music
        else:
            mode = AppMode.Standard
        self.app.model.app_mode = mode
        self._updateScanTypeList()

    def appWillSavePrefs(self):
        self.app.prefs.directoriesWindowRect = self.geometry()

    def directoriesModelAddedFolders(self, folders):
        for folder in folders:
            self.recentFolders.insertItem(folder)

    def loadResultsTriggered(self):
        title = tr("Select a results file to load")
        files = ';;'.join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
        destination = QFileDialog.getOpenFileName(self, title, '', files)
        if destination:
            self.app.model.load_from(destination)
            self.app.recentResults.insertItem(destination)

    def removeFolderButtonClicked(self):
        self.directoriesModel.model.remove_selected()

    def scanButtonClicked(self):
        if self.app.model.results.is_modified:
            title = tr("Start a new scan")
            msg = tr("You have unsaved results, do you really want to continue?")
            if not self.app.confirm(title, msg):
                return
        self.app.model.start_scanning()

    def scanTypeChanged(self, index):
        scan_options = self.app.model.SCANNER_CLASS.get_scan_options()
        self.app.prefs.set_scan_type(self.app.model.app_mode, scan_options[index].scan_type)
        self.app._update_options()

    def selectionChanged(self, selected, deselected):
        self._updateRemoveButton()
예제 #6
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """

    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))
예제 #7
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """
    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files",
                            QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(
            self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(
            self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(
            self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(
            self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(
            core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action(
            "mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu(
        ).aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))
예제 #8
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """

    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

        # Add auto-hide capability.
        self._waitForCtrlRelease = False
        core.actionManager().action("mNavigation/aNext").triggered.connect(
          self._setWaitForCtrlRelease)
        core.actionManager().action("mNavigation/aPrevious").triggered.connect(
          self._setWaitForCtrlRelease)
        QApplication.instance().installEventFilter(self)

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")
        QApplication.instance().removeEventFilter(self)

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))

    def _setWaitForCtrlRelease(self):
        # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten
        # by the QAction and don't even show up in the event filter below. We
        # want to avoid waiting for a Ctrl release if the menu item brought us
        # here. As a workaround, check that Ctrl is pressed. If so, it's
        # unlikely to be the menu item.
        if QApplication.instance().keyboardModifiers() & Qt.ControlModifier:
            self._waitForCtrlRelease = True
            self.show()
        else:
            # If this was a menu selection, then update the MRU list. We can't
            # do this now, since the current document hasn't been changed yet.
            QTimer.singleShot(0, self.model.sortDocuments)

    def eventFilter(self, obj, event):
        """An event filter that looks for ctrl key releases and focus out
           events."""
        # Wait for the user to release the Ctrl key.
        if ( self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and
          event.key() == Qt.Key_Control and
          event.modifiers() == Qt.NoModifier):
            self.model.sortDocuments()
            self._waitForCtrlRelease = False
            if not self.isPinned():
                self.hide()
        # Look for a focus out event sent by the containing widget's focus
        # proxy.
        if event.type() == QEvent.FocusOut and obj == self.focusProxy():
            self.model.sortDocuments()
        return QObject.eventFilter(self, obj, event)
예제 #9
0
class DirectoriesDialog(QMainWindow):
    def __init__(self, app, **kwargs):
        super().__init__(None, **kwargs)
        self.app = app
        self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
        self.recentFolders = Recent(self.app, "recentFolders")
        self._setupUi()
        self.directoriesModel = DirectoriesModel(self.app.model.directory_tree, view=self.treeView)
        self.directoriesDelegate = DirectoriesDelegate()
        self.treeView.setItemDelegate(self.directoriesDelegate)
        self._setupColumns()
        self.app.recentResults.addMenu(self.menuLoadRecent)
        self.app.recentResults.addMenu(self.menuRecentResults)
        self.recentFolders.addMenu(self.menuRecentFolders)
        self._updateAddButton()
        self._updateRemoveButton()
        self._updateLoadResultsButton()
        self._setupBindings()

    def _setupBindings(self):
        self.scanButton.clicked.connect(self.scanButtonClicked)
        self.loadResultsButton.clicked.connect(self.actionLoadResults.trigger)
        self.addFolderButton.clicked.connect(self.actionAddFolder.trigger)
        self.removeFolderButton.clicked.connect(self.removeFolderButtonClicked)
        self.treeView.selectionModel().selectionChanged.connect(self.selectionChanged)
        self.app.recentResults.itemsChanged.connect(self._updateLoadResultsButton)
        self.recentFolders.itemsChanged.connect(self._updateAddButton)
        self.recentFolders.mustOpenItem.connect(self.app.model.add_directory)
        self.directoriesModel.foldersAdded.connect(self.directoriesModelAddedFolders)
        self.app.willSavePrefs.connect(self.appWillSavePrefs)

    def _setupActions(self):
        # (name, shortcut, icon, desc, func)
        ACTIONS = [
            ("actionLoadResults", "Ctrl+L", "", tr("Load Results..."), self.loadResultsTriggered),
            ("actionShowResultsWindow", "", "", tr("Results Window"), self.app.showResultsWindow),
            ("actionAddFolder", "", "", tr("Add Folder..."), self.addFolderTriggered),
        ]
        createActions(ACTIONS, self)

    def _setupMenu(self):
        self.menubar = QMenuBar(self)
        self.menubar.setGeometry(QRect(0, 0, 42, 22))
        self.menuFile = QMenu(self.menubar)
        self.menuFile.setTitle(tr("File"))
        self.menuView = QMenu(self.menubar)
        self.menuView.setTitle(tr("View"))
        self.menuHelp = QMenu(self.menubar)
        self.menuHelp.setTitle(tr("Help"))
        self.menuLoadRecent = QMenu(self.menuFile)
        self.menuLoadRecent.setTitle(tr("Load Recent Results"))
        self.setMenuBar(self.menubar)

        self.menuFile.addAction(self.actionLoadResults)
        self.menuFile.addAction(self.menuLoadRecent.menuAction())
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.app.actionQuit)
        self.menuView.addAction(self.app.actionPreferences)
        self.menuView.addAction(self.actionShowResultsWindow)
        self.menuView.addAction(self.app.actionIgnoreList)
        self.menuHelp.addAction(self.app.actionShowHelp)
        self.menuHelp.addAction(self.app.actionOpenDebugLog)
        self.menuHelp.addAction(self.app.actionAbout)

        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuView.menuAction())
        self.menubar.addAction(self.menuHelp.menuAction())

        # Recent folders menu
        self.menuRecentFolders = QMenu()
        self.menuRecentFolders.addAction(self.actionAddFolder)
        self.menuRecentFolders.addSeparator()

        # Recent results menu
        self.menuRecentResults = QMenu()
        self.menuRecentResults.addAction(self.actionLoadResults)
        self.menuRecentResults.addSeparator()

    def _setupUi(self):
        self.setWindowTitle(self.app.NAME)
        self.resize(420, 338)
        self.centralwidget = QWidget(self)
        self.verticalLayout = QVBoxLayout(self.centralwidget)
        self.promptLabel = QLabel(tr('Select folders to scan and press "Scan".'), self.centralwidget)
        self.verticalLayout.addWidget(self.promptLabel)
        self.treeView = QTreeView(self.centralwidget)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeView.setAcceptDrops(True)
        triggers = (
            QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed | QAbstractItemView.SelectedClicked
        )
        self.treeView.setEditTriggers(triggers)
        self.treeView.setDragDropOverwriteMode(True)
        self.treeView.setDragDropMode(QAbstractItemView.DropOnly)
        self.treeView.setUniformRowHeights(True)
        self.verticalLayout.addWidget(self.treeView)
        self.horizontalLayout = QHBoxLayout()
        self.removeFolderButton = QPushButton(self.centralwidget)
        self.removeFolderButton.setIcon(QIcon(QPixmap(":/minus")))
        self.removeFolderButton.setShortcut("Del")
        self.horizontalLayout.addWidget(self.removeFolderButton)
        self.addFolderButton = QPushButton(self.centralwidget)
        self.addFolderButton.setIcon(QIcon(QPixmap(":/plus")))
        self.horizontalLayout.addWidget(self.addFolderButton)
        spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem1)
        self.loadResultsButton = QPushButton(self.centralwidget)
        self.loadResultsButton.setText(tr("Load Results"))
        self.horizontalLayout.addWidget(self.loadResultsButton)
        self.scanButton = QPushButton(self.centralwidget)
        self.scanButton.setText(tr("Scan"))
        self.scanButton.setDefault(True)
        self.horizontalLayout.addWidget(self.scanButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.setCentralWidget(self.centralwidget)

        self._setupActions()
        self._setupMenu()

        if self.app.prefs.directoriesWindowRect is not None:
            self.setGeometry(self.app.prefs.directoriesWindowRect)
        else:
            moveToScreenCenter(self)

    def _setupColumns(self):
        header = self.treeView.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Fixed)
        header.resizeSection(1, 100)

    def _updateAddButton(self):
        if self.recentFolders.isEmpty():
            self.addFolderButton.setMenu(None)
        else:
            self.addFolderButton.setMenu(self.menuRecentFolders)

    def _updateRemoveButton(self):
        indexes = self.treeView.selectedIndexes()
        if not indexes:
            self.removeFolderButton.setEnabled(False)
            return
        self.removeFolderButton.setEnabled(True)

    def _updateLoadResultsButton(self):
        if self.app.recentResults.isEmpty():
            self.loadResultsButton.setMenu(None)
        else:
            self.loadResultsButton.setMenu(self.menuRecentResults)

    # --- QWidget overrides
    def closeEvent(self, event):
        event.accept()
        if self.app.model.results.is_modified:
            title = tr("Unsaved results")
            msg = tr("You have unsaved results, do you really want to quit?")
            if not self.app.confirm(title, msg):
                event.ignore()
        if event.isAccepted():
            QApplication.quit()

    # --- Events
    def addFolderTriggered(self):
        title = tr("Select a folder to add to the scanning list")
        flags = QFileDialog.ShowDirsOnly
        dirpath = str(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
        if not dirpath:
            return
        self.lastAddedFolder = dirpath
        self.app.model.add_directory(dirpath)
        self.recentFolders.insertItem(dirpath)

    def appWillSavePrefs(self):
        self.app.prefs.directoriesWindowRect = self.geometry()

    def directoriesModelAddedFolders(self, folders):
        for folder in folders:
            self.recentFolders.insertItem(folder)

    def loadResultsTriggered(self):
        title = tr("Select a results file to load")
        files = ";;".join([tr("dupeGuru Results (*.dupeguru)"), tr("All Files (*.*)")])
        destination = QFileDialog.getOpenFileName(self, title, "", files)
        if destination:
            self.app.model.load_from(destination)
            self.app.recentResults.insertItem(destination)

    def removeFolderButtonClicked(self):
        self.directoriesModel.model.remove_selected()

    def scanButtonClicked(self):
        if self.app.model.results.is_modified:
            title = tr("Start a new scan")
            msg = tr("You have unsaved results, do you really want to continue?")
            if not self.app.confirm(title, msg):
                return
        self.app.model.start_scanning()

    def selectionChanged(self, selected, deselected):
        self._updateRemoveButton()
예제 #10
0
    def createGUI(self):
        root = TagManager.createSimpleTree()

        model = TagTreeModel(root,
                             self._tag_checker,
                             self._color_helper,
                             parent=self)
        model.invalidValueSetted.connect(
            self.invalidValueSettedByUserToTreeViewModel)
        #model.dataChanged.connect(dataChanged)

        self.setMinimumSize(300, 150)
        self.setWindowTitle('My Tag Manager')
        layout = QVBoxLayout(self)
        ## Menu Layout
        menu_layout = QHBoxLayout(self)
        but_save = QPushButton('Save', self)
        but_save.setIcon(QApplication.style().standardIcon(
            QStyle.SP_DialogSaveButton))
        menu_layout.addWidget(but_save)
        but_save.clicked.connect(self.but_save_clicked)

        but_load = QPushButton('Load', self)
        but_load.setIcon(QApplication.style().standardIcon(
            QStyle.SP_DirOpenIcon))
        menu_layout.addWidget(but_load)
        but_load.clicked.connect(self.but_load_clicked)

        but_new_f = QPushButton('New tag collections', self)
        but_new_f.setIcon(QApplication.style().standardIcon(
            QStyle.SP_FileIcon))
        but_new_f.setToolTip('New tag collections')
        menu_layout.addWidget(but_new_f)
        but_new_f.clicked.connect(self.but_new_tree_clicked)
        layout.addLayout(menu_layout)
        ## Menu Layout END

        tv = QTreeView(self)
        tv.setDragDropMode(QAbstractItemView.InternalMove)
        tv.setDragEnabled(True)
        tv.setAcceptDrops(True)
        tv.setDropIndicatorShown(True)
        tv.setContextMenuPolicy(Qt.CustomContextMenu)
        tv.customContextMenuRequested.connect(
            self.customContextMenuRequestedForTreeView)

        tv.setModel(model)
        #tv.setAlternatingRowColors(True)
        layout.addWidget(tv)
        self._widget_tv = tv

        but = QPushButton('Remove Selected', self)
        but.setIcon(QApplication.style().standardIcon(QStyle.SP_TrashIcon))
        layout.addWidget(but)
        but.clicked.connect(self.but_remove_clicked)

        but_add = QPushButton('Add', self)
        layout.addWidget(but_add)
        but_add.clicked.connect(self.but_add_clicked)

        but_add_to_root = QPushButton('Add new element to root')
        but_add_to_root.setToolTip("Add new element to root of tree.")
        layout.addWidget(but_add_to_root)
        but_add_to_root.clicked.connect(self.but_add_to_root_clicked)

        but_item_up = QPushButton("Up")
        but_item_up.setToolTip("Up selected item.")
        but_item_up.clicked.connect(self.but_item_up_clicked)
        layout.addWidget(but_item_up)

        but_item_down = QPushButton("Down")
        but_item_down.setToolTip("Down selected item.")
        but_item_down.clicked.connect(self.but_item_down_clicked)
        layout.addWidget(but_item_down)

        but_font = QPushButton('Set Font', self)
        layout.addWidget(but_font)
        but_font.clicked.connect(self.but_font_dialog_clicked)
        self.layout().deleteLater()  # Remove default layout
        self.setLayout(layout)
예제 #11
0
class Editor(QMainWindow):
    """This is the main class.
    """

    FORMATS = ("Aiken (*.txt);;Cloze (*.cloze);;GIFT (*.gift);;JSON (*.json)"
               ";;LaTex (*.tex);;Markdown (*.md);;PDF (*.pdf);;XML (*.xml)")

    SHORTCUTS = {
        "Create file": Qt.CTRL + Qt.Key_N,
        "Find questions": Qt.CTRL + Qt.Key_F,
        "Read file": Qt.CTRL + Qt.Key_O,
        "Read folder": Qt.CTRL + Qt.SHIFT + Qt.Key_O,
        "Save": Qt.CTRL + Qt.Key_S,
        "Save as": Qt.CTRL + Qt.SHIFT + Qt.Key_S,
        "Add hint": Qt.CTRL + Qt.SHIFT + Qt.Key_H,
        "Remove hint": Qt.CTRL + Qt.SHIFT + Qt.Key_Y,
        "Add answer": Qt.CTRL + Qt.SHIFT + Qt.Key_A,
        "Remove answer": Qt.CTRL + Qt.SHIFT + Qt.Key_Q,
        "Open datasets": Qt.CTRL + Qt.SHIFT + Qt.Key_D
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle("QAS Editor GUI")

        self._items: List[QWidget] = []
        self._main_editor = None
        self.path: str = None
        self.top_quiz = Category()
        self.cxt_menu = QMenu(self)
        self.cxt_item: QStandardItem = None
        self.cxt_data: _Question | Category= None
        self.cur_question: _Question = None
        self.tagbar: GTagBar = None
        self.main_editor: GTextEditor = None
        self.is_open_find = self.is_open_dataset = False

        with resources.open_text("qas_editor.gui", "stylesheet.css") as ifile:
            self.setStyleSheet(ifile.read())

        self._add_menu_bars()

        # Left side
        self.data_view = QTreeView()
        self.data_view.setIconSize(QSize(18, 18))
        xframe_vbox = self._block_datatree()
        left = QWidget()
        left.setLayout(xframe_vbox)

        # Right side
        self.cframe_vbox = QVBoxLayout()
        self._block_general_data()
        self._block_answer()
        self._block_hints()
        self._block_units()
        self._block_zones()
        self._block_solution()
        self._block_template()
        self.cframe_vbox.addStretch()
        self.cframe_vbox.setSpacing(5)
        for value in self._items:
            value.setEnabled(False)

        frame = QFrame()
        frame.setLineWidth(2)
        frame.setLayout(self.cframe_vbox)
        right = QScrollArea()
        right.setWidget(frame)
        right.setWidgetResizable(True)

        # Create main window divider for the splitter
        splitter = QSplitter()
        splitter.addWidget(left)
        splitter.addWidget(right)
        splitter.setStretchFactor(1, 1)
        splitter.setSizes([250, 100])
        self.setCentralWidget(splitter)

        # Create lower status bar.
        status = QStatusBar()
        self.setStatusBar(status)
        self.cat_name = QLabel()
        status.addWidget(self.cat_name)

        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()
        self.setGeometry(50, 50, 1200, 650)
        self.show()

    def _debug_me(self):
        self.path = "./test_lib/datasets/moodle/all.xml"
        self.top_quiz = Category.read_files(["./test_lib/datasets/moodle/all.xml"])
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    def _add_menu_bars(self):
        file_menu = self.menuBar().addMenu("&File")
        tmp = QAction("New file", self)
        tmp.setStatusTip("New file")
        tmp.triggered.connect(self._create_file)
        tmp.setShortcut(self.SHORTCUTS["Create file"])
        file_menu.addAction(tmp)
        tmp = QAction("Open file", self)
        tmp.setStatusTip("Open file")
        tmp.triggered.connect(self._read_file)
        tmp.setShortcut(self.SHORTCUTS["Read file"])
        file_menu.addAction(tmp)
        tmp = QAction("Open folder", self)
        tmp.setStatusTip("Open folder")
        tmp.triggered.connect(self._read_folder)
        tmp.setShortcut(self.SHORTCUTS["Read folder"])
        file_menu.addAction(tmp)
        tmp = QAction("Save", self)
        tmp.setStatusTip("Save top category to specified file on disk")
        tmp.triggered.connect(lambda: self._write_file(False))
        tmp.setShortcut(self.SHORTCUTS["Save"])
        file_menu.addAction(tmp)
        tmp = QAction("Save As...", self)
        tmp.setStatusTip("Save top category to specified file on disk")
        tmp.triggered.connect(lambda: self._write_file(True))
        tmp.setShortcut(self.SHORTCUTS["Save as"])
        file_menu.addAction(tmp)

        file_menu = self.menuBar().addMenu("&Edit")
        tmp = QAction("Shortcuts", self)
        #tmp.setShortcut(self.SHORTCUTS["Read file"])
        file_menu.addAction(tmp)
        tmp = QAction("Datasets", self)
        tmp.triggered.connect(self._open_dataset_popup)
        tmp.setShortcut(self.SHORTCUTS["Open datasets"])
        file_menu.addAction(tmp)
        tmp = QAction("Find Question", self)
        tmp.triggered.connect(self._open_find_popup)
        tmp.setShortcut(self.SHORTCUTS["Find questions"])
        file_menu.addAction(tmp)
        
        self.toolbar = GTextToolbar(self)
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)

    def _add_new_category(self):
        popup = PopupName(self, True)
        popup.show()
        if not popup.exec():
            return
        self.cxt_data.add_subcat(popup.data)
        self._new_item(popup.data, self.cxt_item, "question")

    def _add_new_question(self):
        popup = PopupQuestion(self, self.cxt_data)
        popup.show()
        if not popup.exec():
            return
        self._new_item(popup.question, self.cxt_item, "question")

    @action_handler
    def _append_category(self):
        path, _ = QFileDialog.getOpenFileName(self, "Open file", "",
                                              self.FORMATS)
        if not path:
            return
        quiz = Category.read_files([path], path.rsplit("/", 1)[-1])
        self.cxt_data[quiz.name] = quiz
        self._update_tree_item(quiz, self.cxt_item)

    def _block_answer(self) -> None:
        frame = GCollapsible(self, "Answers")
        self.cframe_vbox.addLayout(frame)
        self._items.append(GOptions(self.toolbar, self.main_editor))
        _shortcut = QShortcut(self.SHORTCUTS["Add answer"], self)
        _shortcut.activated.connect(self._items[-1].add)
        _shortcut = QShortcut(self.SHORTCUTS["Remove answer"], self)
        _shortcut.activated.connect(self._items[-1].pop)
        frame.setLayout(self._items[-1])

    def _block_datatree(self) -> QVBoxLayout:
        self.data_view.setStyleSheet("margin: 5px 5px 0px 5px")
        self.data_view.setHeaderHidden(True)
        self.data_view.doubleClicked.connect(self._update_item)
        self.data_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.data_view.customContextMenuRequested.connect(self._data_view_cxt)
        self.data_view.setDragEnabled(True)
        self.data_view.setAcceptDrops(True)
        self.data_view.setDropIndicatorShown(True)
        self.data_view.setDragDropMode(QAbstractItemView.InternalMove)
        self.data_view.original_dropEvent = self.data_view.dropEvent
        self.data_view.dropEvent = self._dataview_dropevent
        self.root_item = QStandardItemModel(0, 1)
        self.root_item.setHeaderData(0, Qt.Horizontal, "Classification")
        self.data_view.setModel(self.root_item)

        xframe_vbox = QVBoxLayout()
        xframe_vbox.addWidget(self.data_view)
        return xframe_vbox

    def _block_general_data(self) -> None:
        clayout = GCollapsible(self, "Question Header")
        self.cframe_vbox.addLayout(clayout, 1)

        grid = QVBoxLayout()    # No need of parent. It's inside GCollapsible
        grid.setSpacing(2)

        self.main_editor = GTextEditor(self.toolbar, "question")
        self._items.append(self.main_editor)
        self._items[-1].setToolTip("Question's description text")
        self._items[-1].setMinimumHeight(200)
        grid.addWidget(self._items[-1], 1)
        self.tagbar = GTagBar(self)
        self.tagbar.setToolTip("List of tags used by the question.")
        self._items.append(self.tagbar)
        grid.addWidget(self._items[-1], 0)

        others = QHBoxLayout()  # No need of parent. It's inside GCollapsible
        grid.addLayout(others, 0)

        group_box = QGroupBox("General", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GField("dbid", self, int))
        self._items[-1].setToolTip("Optional ID for the question.")
        self._items[-1].setFixedWidth(50)
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("default_grade", self, int))
        self._items[-1].setToolTip("Default grade.")
        self._items[-1].setFixedWidth(50)
        self._items[-1].setText("1.0")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("penalty", self, str))
        self._items[-1].setToolTip("Penalty")
        self._items[-1].setFixedWidth(50)
        self._items[-1].setText("0.0")
        _content.addWidget(self._items[-1], 0)
        _content.addStretch()

        others.addWidget(group_box, 0)

        group_box = QGroupBox("Unit Handling", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("grading_type", self, Grading))
        self._items[-1].setToolTip("Grading")
        self._items[-1].setMinimumWidth(80)
        _content.addWidget(self._items[-1], 0)
        self._items.append(GDropbox("show_units", self, ShowUnits))
        self._items[-1].setToolTip("Show units")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("unit_penalty", self, float))
        self._items[-1].setToolTip("Unit Penalty")
        self._items[-1].setText("0.0")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("left", "Left side", self))
        _content.addWidget(self._items[-1], 0)
        others.addWidget(group_box, 1)

        group_box = QGroupBox("Multichoices", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("numbering", self, Numbering))
        self._items[-1].setToolTip("How options will be enumerated")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("show_instr", "Instructions", self))
        self._items[-1].setToolTip("If the structions 'select one (or more "
                                   " options)' should be shown")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("single", "Multi answer", self))
        self._items[-1].setToolTip("If there is just a single or multiple "
                                   "valid answers")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("shuffle", "Shuffle", self))
        self._items[-1].setToolTip("If answers should be shuffled (e.g. order "
                                   "of options will change each time)")
        _content.addWidget(self._items[-1], 0)
        others.addWidget(group_box, 1)

        group_box = QGroupBox("Documents", self)
        _content = QGridLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("rsp_format", self, ResponseFormat))
        self._items[-1].setToolTip("The format to be used in the reponse.")
        _content.addWidget(self._items[-1], 0, 0, 1, 2)
        self._items.append(GCheckBox("rsp_required",
                                     "Required", self))
        self._items[-1].setToolTip("Require the student to enter some text.")
        _content.addWidget(self._items[-1], 0, 2)
        self._items.append(GField("min_words", self, int))
        self._items[-1].setToolTip("Minimum word limit")
        self._items[-1].setText("0")
        _content.addWidget(self._items[-1], 1, 0)
        self._items.append(GField("max_words", self, int))
        self._items[-1].setToolTip("Maximum word limit")
        self._items[-1].setText("10000")
        _content.addWidget(self._items[-1], 2, 0)
        self._items.append(GField("attachments", self, int))
        self._items[-1].setToolTip("Number of attachments allowed. 0 is none."
                                   " -1 is unlimited. Should be bigger than "
                                   "field below.")
        self._items[-1].setText("-1")
        _content.addWidget(self._items[-1], 1, 1)
        self._items.append(GField("atts_required", self, int))
        self._items[-1].setToolTip("Number of attachments required. 0 is none."
                                   " -1 is unlimited. Should be smaller than "
                                   "field above.")
        self._items[-1].setText("0")
        _content.addWidget(self._items[-1], 2, 1)
        self._items.append(GField("lines", self, int))
        self._items[-1].setToolTip("Input box size.")
        self._items[-1].setText("15")
        _content.addWidget(self._items[-1], 1, 2)
        self._items.append(GField("max_bytes", self, int))
        self._items[-1].setToolTip("Maximum file size.")
        self._items[-1].setText("1Mb")
        _content.addWidget(self._items[-1], 2, 2)
        self._items.append(GField("file_types", self, str))
        self._items[-1].setToolTip("Accepted file types (comma separeted).")
        self._items[-1].setText(".txt, .pdf")
        _content.addWidget(self._items[-1], 3, 0, 1, 3)
        others.addWidget(group_box, 1)

        _wrapper = QVBoxLayout()  # No need of parent. It's inside GCollapsible
        group_box = QGroupBox("Random", self)
        _wrapper.addWidget(group_box)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GCheckBox("subcats", "Subcats", self))
        self._items[-1].setToolTip("If questions wshould be choosen from "
                                   "subcategories too.")
        _content.addWidget(self._items[-1])
        self._items.append(GField("choose", self, int))
        self._items[-1].setToolTip("Number of questions to select.")
        self._items[-1].setText("5")
        self._items[-1].setFixedWidth(85)
        _content.addWidget(self._items[-1])

        group_box = QGroupBox("Fill-in", self)
        _wrapper.addWidget(group_box)
        _content = QVBoxLayout(group_box)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GCheckBox("use_case", "Match case", self))
        self._items[-1].setToolTip("If text is case sensitive.")
        _content.addWidget(self._items[-1])

        others.addLayout(_wrapper, 0)

        group_box = QGroupBox("Datasets", self)
        _content = QGridLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GList("datasets", self))
        self._items[-1].setFixedHeight(70)
        self._items[-1].setToolTip("List of datasets used by this question.")
        _content.addWidget(self._items[-1], 0, 0, 1, 2)
        self._items.append(GDropbox("synchronize", self, Synchronise))
        self._items[-1].setToolTip("How should the databases be synchronized.")
        self._items[-1].setMinimumWidth(70)
        _content.addWidget(self._items[-1], 1, 0)
        _gen = QPushButton("Gen", self)
        _gen.setToolTip("Generate new items based on the max, min and decimal "
                        "values of the datasets, and the current solution.")
        _gen.clicked.connect(self._gen_items)
        _content.addWidget(_gen, 1, 1)
        others.addWidget(group_box, 2)

        others.addStretch()
        clayout.setLayout(grid)
        clayout._toggle()

    def _block_hints(self) -> None:
        clayout = GCollapsible(self, "Hints")
        self.cframe_vbox.addLayout(clayout)
        self._items.append(GHintsList(None, self.toolbar))
        _shortcut = QShortcut(self.SHORTCUTS["Add hint"], self)
        _shortcut.activated.connect(self._items[-1].add)
        _shortcut = QShortcut(self.SHORTCUTS["Remove hint"], self)
        _shortcut.activated.connect(self._items[-1].pop)
        clayout.setLayout(self._items[-1])

    def _block_solution(self) -> None:
        collapsible = GCollapsible(self, "Solution and Feedback")
        self.cframe_vbox.addLayout(collapsible)
        layout = QVBoxLayout()
        collapsible.setLayout(layout)
        self._items.append(GTextEditor(self.toolbar, "feedback"))
        self._items[-1].setMinimumHeight(100)
        self._items[-1].setToolTip("General feedback for the question. May "
                                   "also be used to describe solutions.")
        layout.addWidget(self._items[-1])
        sframe = QFrame(self)
        sframe.setStyleSheet(".QFrame{border:1px solid rgb(41, 41, 41);"
                             "background-color: #e4ebb7}")
        layout.addWidget(sframe)
        _content = QGridLayout(sframe)
        self._items.append(GTextEditor(self.toolbar, "if_correct"))
        self._items[-1].setToolTip("Feedback for correct answer")
        _content.addWidget(self._items[-1], 0, 0)
        self._items.append(GTextEditor(self.toolbar, "if_incomplete"))
        self._items[-1].setToolTip("Feedback for incomplete answer")
        _content.addWidget(self._items[-1], 0, 1)
        self._items.append(GTextEditor(self.toolbar, "if_incorrect"))
        self._items[-1].setToolTip("Feedback for incorrect answer")
        _content.addWidget(self._items[-1], 0, 2)
        self._items.append(GCheckBox("show_num", "Show the number of correct "
                                     "responses once the question has finished"
                                     , self))
        _content.addWidget(self._items[-1], 2, 0, 1, 3)
        _content.setColumnStretch(3, 1)

    def _block_template(self) -> None:
        collapsible = GCollapsible(self, "Templates")
        self.cframe_vbox.addLayout(collapsible)
        layout = QVBoxLayout()
        collapsible.setLayout(layout)
        self._items.append(GTextEditor(self.toolbar, "template"))
        self._items[-1].setMinimumHeight(70)
        self._items[-1].setToolTip("Text displayed in the response input box "
                                    "when a new attempet is started.")
        layout.addWidget(self._items[-1])
        self._items.append(GTextEditor(self.toolbar, "grader_info"))
        self._items[-1].setMinimumHeight(50)
        self._items[-1].setToolTip("Information for graders.")
        layout.addWidget(self._items[-1])

    def _block_units(self):
        collapsible = GCollapsible(self, "Units")
        self.cframe_vbox.addLayout(collapsible)

    def _block_zones(self):
        collapsible = GCollapsible(self, "Background and Zones")
        self.cframe_vbox.addLayout(collapsible)

    @action_handler
    def _clone_shallow(self) -> None:
        new_data = copy.copy(self.cxt_data)
        self._new_item(new_data, self.cxt_item.parent(), "question")

    @action_handler
    def _clone_deep(self) -> None:
        new_data = copy.deepcopy(self.cxt_data)
        self._new_item(new_data, self.cxt_itemparent(), "question")

    @action_handler
    def _create_file(self, *_):
        self.top_quiz = Category()
        self.path = None
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)

    @action_handler
    def _dataview_dropevent(self, event: QDropEvent):
        from_obj = self.data_view.selectedIndexes()[0].data(257)
        to_obj = self.data_view.indexAt(event.pos()).data(257)
        if isinstance(to_obj, Category):
            if isinstance(from_obj, _Question):
                to_obj.add_subcat(from_obj)
            else:
                to_obj.add_question(from_obj)
        else:
            event.ignore()
        self.data_view.original_dropEvent(event)

    def _data_view_cxt(self, event):
        model_idx = self.data_view.indexAt(event)
        self.cxt_item = self.root_item.itemFromIndex(model_idx)
        self.cxt_data = model_idx.data(257)
        self.cxt_menu.clear()
        rename = QAction("Rename", self)
        rename.triggered.connect(self._rename_category)
        self.cxt_menu.addAction(rename)
        if self.cxt_item != self.root_item.item(0):
            tmp = QAction("Delete", self)
            tmp.triggered.connect(self._delete_item)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Clone (Shallow)", self)
            tmp.triggered.connect(self._clone_shallow)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Clone (Deep)", self)
            tmp.triggered.connect(self._clone_deep)
            self.cxt_menu.addAction(tmp)
        if isinstance(self.cxt_data, Category):
            tmp = QAction("Save as", self)
            tmp.triggered.connect(lambda: self._write_quiz(self.cxt_data, True))
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Append", self)
            tmp.triggered.connect(self._append_category)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Sort", self)
            #tmp.triggered.connect(self._add_new_category)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("New Question", self)
            tmp.triggered.connect(self._add_new_question)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("New Category", self)
            tmp.triggered.connect(self._add_new_category)
            self.cxt_menu.addAction(tmp)
        self.cxt_menu.popup(self.data_view.mapToGlobal(event))

    @action_handler
    def _delete_item(self, *_):
        self.cxt_item.parent().removeRow(self.cxt_item.index().row())
        cat = self.cxt_data.parent
        if isinstance(self.cxt_data, _Question):
            cat.pop_question(self.cxt_data)
        elif isinstance(self.cxt_data, Category):
            cat.pop_subcat(self.cxt_data)

    @action_handler
    def _gen_items(self, _):
        pass

    def _new_item(self, data: Category, parent: QStandardItem, title: str):

        name = f"{data.__class__.__name__}_icon.png".lower()
        item = None
        with resources.path("qas_editor.images", name) as path:
            item = QStandardItem(QIcon(path.as_posix()), data.name)
            item.setEditable(False)
            item.setData(QVariant(data))
            parent.appendRow(item)
        return item

    @action_handler
    def _open_dataset_popup(self, _):
        if not self.is_open_dataset:
            popup = PopupDataset(self, self.top_quiz)
            popup.show()
            self.is_open_dataset = True

    @action_handler
    def _open_find_popup(self, _):
        if not self.is_open_find:
            popup = PopupFind(self, self.top_quiz, self.tagbar.cat_tags)
            popup.show()
            self.is_open_find = True

    @action_handler
    def _read_file(self, _):
        files, _ = QFileDialog.getOpenFileNames(self, "Open file", "",
                                                self.FORMATS)
        if not files:
            return
        if len(files) == 1:
            self.path = files[0]
        self.top_quiz = Category.read_files(files)
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    @action_handler
    def _read_folder(self, _):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.FileMode.Directory)
        if not dialog.exec():
            return
        self.top_quiz = Category()
        self.path = None
        for folder in dialog.selectedFiles():
            cat = folder.rsplit("/", 1)[-1]
            quiz = Category.read_files(glob.glob(f"{folder}/*"), cat)
            self.top_quiz.add_subcat(quiz)
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    @action_handler
    def _rename_category(self, *_):
        popup = PopupName(self, False)
        popup.show()
        if not popup.exec():
            return
        self.cxt_data.name = popup.data
        self.cxt_item.setText(popup.data)

    @action_handler
    def _update_item(self, model_index: QModelIndex) -> None:
        item = model_index.data(257)
        if isinstance(item, _Question):
            for key in self._items:
                attr = key.get_attr()
                if attr in item.__dict__:
                    key.setEnabled(True)
                    key.from_obj(item)
                else:
                    key.setEnabled(False)
            self.cur_question = item
        path = [f" ({item.__class__.__name__})"]
        while item.parent:
            path.append(item.name)
            item = item.parent
        path.append(item.name)
        path.reverse()
        self.cat_name.setText(" > ".join(path[:-1]) + path[-1])

    def _update_tree_item(self, data: Category, parent: QStandardItem) -> None:
        item = self._new_item(data, parent, "category")
        for k in data.questions:
            self._new_item(k, item, "question")
        for k in data:
            self._update_tree_item(data[k], item)

    @action_handler
    def _write_quiz(self, quiz: Category, save_as: bool):
        if save_as or self.path is None:
            path, _ = QFileDialog.getSaveFileName(self, "Save file", "",
                                                  self.FORMATS)
            if not path:
                return None
        else:
            path = self.path
        ext = path.rsplit('.', 1)[-1]
        getattr(quiz, quiz.SERIALIZERS[ext][1])(path)
        return path

    def _write_file(self, save_as: bool) -> None:
        path = self._write_quiz(self.top_quiz, save_as)
        if path:
            self.path = path
예제 #12
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """
    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files",
                            QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(
            self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(
            self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(
            self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

        # Add auto-hide capability.
        self._waitForCtrlRelease = False
        core.actionManager().action("mNavigation/aNext").triggered.connect(
            self._setWaitForCtrlRelease)
        core.actionManager().action("mNavigation/aPrevious").triggered.connect(
            self._setWaitForCtrlRelease)
        QApplication.instance().installEventFilter(self)

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")
        QApplication.instance().removeEventFilter(self)

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(
            self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(
            core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action(
            "mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu(
        ).aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))

    def _setWaitForCtrlRelease(self):
        # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten
        # by the QAction and don't even show up in the event filter below. We
        # want to avoid waiting for a Ctrl release if the menu item brought us
        # here. As a workaround, check that Ctrl is pressed. If so, it's
        # unlikely to be the menu item.
        if QApplication.instance().keyboardModifiers() & Qt.ControlModifier:
            self._waitForCtrlRelease = True
            self.show()
        else:
            # If this was a menu selection, then update the MRU list. We can't
            # do this now, since the current document hasn't been changed yet.
            QTimer.singleShot(0, self.model.sortDocuments)

    def eventFilter(self, obj, event):
        """An event filter that looks for ctrl key releases and focus out
           events."""
        # Wait for the user to release the Ctrl key.
        if (self._waitForCtrlRelease and event.type() == QEvent.KeyRelease
                and event.key() == Qt.Key_Control
                and event.modifiers() == Qt.NoModifier):
            self.model.sortDocuments()
            self._waitForCtrlRelease = False
            if not self.isPinned():
                self.hide()
        # Look for a focus out event sent by the containing widget's focus
        # proxy.
        if event.type() == QEvent.FocusOut and obj == self.focusProxy():
            self.model.sortDocuments()
        return QObject.eventFilter(self, obj, event)