예제 #1
0
파일: layer_tree.py 프로젝트: ssec/sift
 def current_selected_uuids(self, lbox: QTreeView = None):
     lbox = self.current_set_listbox if lbox is None else lbox
     if lbox is None:
         LOG.error('not sure which list box is active! oh pooh.')
         return
     for q in lbox.selectedIndexes():
         yield self.doc.uuid_for_current_layer(q.row())
예제 #2
0
class FileSystemView(QWidget):
    def __init__(self, dir_path):
        super().__init__()
        appWidth = 800
        appHeight = 300
        self.setWindowTitle('File System Viewer')
        self.setGeometry(300, 300, appWidth, appHeight)

        self.model = QFileSystemModel()
        self.model.setRootPath(dir_path)
        self.tree = QTreeView()
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(dirPath))
        self.tree.setColumnWidth(0, 250)
        self.tree.setAlternatingRowColors(True)

        layout = QVBoxLayout()
        self.photo = QLabel('Hola' + chr(10) + 'Caracola')
        layout.addWidget(self.tree)
        layout.addWidget(self.photo)
        self.setLayout(layout)
        self.tree.selectionModel().selectionChanged.connect(self.select)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return:
            index = self.tree.selectedIndexes()[0]
            crawler = index.model().filePath(index)
            print(crawler)
        #self.tree.keyPressEvent(self, event)

    def select(self, index1):
        index = self.tree.selectedIndexes()[0]
        file_sel = index.model().filePath(index)
        if os.path.isfile(file_sel) and file_sel[-3:].upper() in 'JPG PNG':
            pixmap = QPixmap(file_sel)
            self.photo.setPixmap(pixmap)
            print(file_sel)
예제 #3
0
class Window(QWidget):
    def __init__(self):

        QWidget.__init__(self)

        self.treeView = QTreeView()
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.openMenu)

        self.model = QStandardItemModel()
        self.addItems(self.model, data)
        self.treeView.setModel(self.model)

        self.model.setHorizontalHeaderLabels([self.tr("Object")])

        layout = QVBoxLayout()
        layout.addWidget(self.treeView)
        self.setLayout(layout)

    def addItems(self, parent, elements):

        for text, children in elements:
            item = QStandardItem(text)
            parent.appendRow(item)
            if children:
                self.addItems(item, children)

    def openMenu(self, position):

        indexes = self.treeView.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1

        menu = QMenu()
        if level == 0:
            menu.addAction(self.tr("Edit person"))
        elif level == 1:
            menu.addAction(self.tr("Edit object/container"))
        elif level == 2:
            menu.addAction(self.tr("Edit object"))

        menu.exec_(self.treeView.viewport().mapToGlobal(position))
예제 #4
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)

        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)

        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()

        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)

        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)

        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)

        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a

        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))

        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)

        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)

        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)

        # set shortcut action
        a = self.shortcutAction = act(
            self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)

        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)

        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)

        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)

        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())

        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)

        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())

        # complete on snippet variables
        self.searchEntry.setCompleter(
            QCompleter([
                ':icon', ':indent', ':menu', ':name', ':python', ':selection',
                ':set', ':symbol', ':template', ':template-run'
            ], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)

    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()

    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass  # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(
                key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(
                key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(
            _("Enter text to search in the snippets list.\n"
              "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(
            map("<p>{0}</p>\n".format, (
                _("Enter text to search in the snippets list, and "
                  "press Enter to apply the currently selected snippet."),
                _("If the search text fully matches the value of the '{name}' variable "
                  "of a snippet, that snippet is selected.").format(
                      name="name"),
                _("If the search text starts with a colon ':', the rest of the "
                  "search text filters snippets that define the given variable. "
                  "After a space a value can also be entered, snippets will then "
                  "match if the value of the given variable contains the text after "
                  "the space."),
                _("E.g. entering {menu} will show all snippets that are displayed "
                  "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))

    def sizeHint(self):
        return self.parent().mainwindow().size() / 4

    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))

    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide()  # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()

    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)

    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)

    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)

    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(
                self, cb, (collection, name))

            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()

    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()),
                      reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()

    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)

    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename,
                                               filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)

    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [
            row for row in range(model.model().rowCount())
            if not self.treeView.isRowHidden(row, QModelIndex())
        ]
        selectedrows = [
            i.row() for i in self.treeView.selectedIndexes()
            if i.column() == 0 and i.row() in allrows
        ]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]

        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        n = len(names)
        caption = app.caption(
            _("dialog title", "Export {num} Snippet", "Export {num} Snippets",
              n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None,
                                               filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(
                    self, _("Error"),
                    _("Can't write to destination:\n\n{url}\n\n{error}").
                    format(url=filename, error=e.strerror))

    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)

    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")

    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None
                                                  ) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(
                    i, QItemSelectionModel.SelectCurrent
                    | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()

    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)

    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
예제 #5
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()
class LogInspectorWindow(QMainWindow):
    def __init__(self, configFilePath, parent=None):
        super(LogInspectorWindow, self).__init__(parent)
        self.initMatPlotLib()
        self.configFilePath = configFilePath

        folder = os.path.dirname(self.configFilePath)
        if not os.path.exists(folder):
            os.makedirs(folder)

        if os.path.exists(self.configFilePath):
            # config.yaml found.  Read from file.
            file = open(self.configFilePath, 'r')
            self.config = yaml.safe_load(file)
            file.close()
        else:
            # config.yaml not found.  Create new file.
            self.config = {}
            self.config['logs_directory'] = os.path.join(
                os.path.expanduser("~"), "Documents", "Inertial_Sense", "Logs")
            self.config['directory'] = ""
            self.config['serials'] = ["ALL"]
            file = open(self.configFilePath, 'w')
            yaml.dump(self.config, file)
            file.close()

        self.currently_selected = 'posNEDMap'
        self.downsample = 5
        self.plotargs = None
        self.log = None
        self.plotter = None

    def initMatPlotLib(self):
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)
        self.figure.subplots_adjust(left=0.05,
                                    right=0.99,
                                    bottom=0.05,
                                    top=0.91,
                                    wspace=0.2,
                                    hspace=0.2)

    def addButton(self, name, function, multithreaded=True, layout=None):
        setattr(self, name + "button", QPushButton(name))
        # if multithreaded:
        # setattr(self, name+"buttonThread", Thread(target=function))
        # getattr(self, name+"button").pressed.connect(self.startLoadingIndicator)
        # getattr(self, name+"button").released.connect(getattr(self, name+"buttonThread").start)
        # else:
        getattr(self, name + "button").clicked.connect(function)
        # getattr(self, name + "button").setMinimumWidth(220)
        if layout is None:
            if self.buttonLayoutRightCol.count(
            ) < self.buttonLayoutMiddleCol.count():
                self.buttonLayoutRightCol.addWidget(
                    getattr(self, name + "button"))
            elif self.buttonLayoutMiddleCol.count(
            ) < self.buttonLayoutLeftCol.count():
                self.buttonLayoutMiddleCol.addWidget(
                    getattr(self, name + "button"))
            else:
                self.buttonLayoutLeftCol.addWidget(
                    getattr(self, name + "button"))
        else:
            layout.addWidget(getattr(self, name + "button"))

    def updatePlot(self):
        self.plot(self.currently_selected, self.plotargs)

    def updateWindowTitle(self):
        if np.shape(self.log.data[0, DID_DEV_INFO])[0] != 0:
            info = self.log.data[0, DID_DEV_INFO][0]
            infoStr = 'SN' + str(info[1]) + ', H:' + verArrayToString(
                info[2]) + ', F:' + verArrayToString(
                    info[3]) + ' build ' + str(
                        info[4]) + ', ' + dateTimeArrayToString(
                            info[8], info[9]) + ', ' + info[10].decode('UTF-8')
            self.setWindowTitle("LogInspector  -  " + infoStr)

    def choose_directory(self):
        log_dir = config['logs_directory']
        directory = QFileDialog.getExistingDirectory(
            parent=self, caption='Choose Log Directory', directory=log_dir)

        if directory != '':
            try:
                self.load(directory)
            except Exception as e:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Critical)
                msg.setText("Unable to load log: " + e.__str__())
                msg.setDetailedText(traceback.format_exc())
                msg.exec()

    def load(self, directory):
        print("loading files from " + directory)
        # if self.log is None:
        self.log = Log()
        self.log.load(directory)
        print("done loading")
        self.plotter = logPlot(False, False, 'svg', self.log)
        self.plotter.setDownSample(self.downsample)
        # str = ''
        # if self.log.navMode:
        #     str += 'NAV '
        # if self.log.rtk:
        #     str += 'RTK '
        # if self.log.compassing:
        #     str += 'Comp '
        # self.statusLabel.setText(str)
        self.updatePlot()
        self.updateWindowTitle()
        self.stopLoadingIndicator()

    def setupUi(self):
        self.setObjectName("LogInspector")
        self.setWindowTitle("LogInspector")
        self.resize(1280, 900)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowSystemMenuHint
                            | QtCore.Qt.WindowMinMaxButtonsHint)
        self.setWindowIcon(QIcon("assets/Magnifying_glass_icon.png"))

        # MainWindow.showMaximized()

        self.createFileTree()
        self.createButtonColumn()
        self.formatButtonColumn()
        self.createBottomToolbar()

        self.figureLayout = QVBoxLayout()
        self.figureLayout.addWidget(self.canvas)
        self.figureLayout.addLayout(self.toolLayout)
        self.figureLayout.setStretchFactor(self.canvas, 1)

        layout = QHBoxLayout()
        layout.addLayout(self.controlLayout)
        layout.addLayout(self.figureLayout)
        layout.setStretch(1, 1)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        # self.resize(1280, 900)
        self.resize(1450, 1000)
        self.setAcceptDrops(True)

    def createButtonColumn(self):
        self.controlLayout = QVBoxLayout()
        self.buttonLayoutLeftCol = QVBoxLayout()
        self.buttonLayoutMiddleCol = QVBoxLayout()
        self.buttonLayoutRightCol = QVBoxLayout()
        self.addButton('Pos NED Map', lambda: self.plot('posNEDMap'))
        self.addButton('Pos NED', lambda: self.plot('posNED'))
        self.addButton('Pos LLA', lambda: self.plot('posLLA'))
        self.addButton('GPS LLA', lambda: self.plot('llaGps'))
        self.addButton('Vel NED', lambda: self.plot('velNED'))
        self.addButton('Vel UVW', lambda: self.plot('velUVW'))
        self.addButton('Attitude', lambda: self.plot('attitude'))
        self.addButton('Heading', lambda: self.plot('heading'))
        self.addButton('INS Status', lambda: self.plot('insStatus'))
        self.addButton('HDW Status', lambda: self.plot('hdwStatus'))
        self.addButton('GPS 1 Stats', lambda: self.plot('gpsStats'))
        self.addButton('GPS 2 Stats', lambda: self.plot('gps2Stats'))
        self.addButton('RTK Pos Stats', lambda: self.plot('rtkPosStats'))
        self.addButton('RTK Cmp Stats', lambda: self.plot('rtkCmpStats'))
        self.addButton('Flash Config', lambda: self.showFlashConfig())
        self.addButton('Device Info', lambda: self.showDeviceInfo())
        self.addButton('IMU PQR', lambda: self.plot('imuPQR'))
        self.addButton('IMU Accel', lambda: self.plot('imuAcc'))
        self.addButton('PSD PQR', lambda: self.plot('gyroPSD'))
        self.addButton('PSD Accel', lambda: self.plot('accelPSD'))
        self.addButton('Magnetometer', lambda: self.plot('magnetometer'))
        self.addButton('Temp', lambda: self.plot('temp'))

    def formatButtonColumn(self):
        self.buttonLayoutLeftCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonLayoutMiddleCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonLayoutRightCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonColumnLayout = QHBoxLayout()
        self.buttonColumnLayout.addLayout(self.buttonLayoutLeftCol)
        self.buttonColumnLayout.addLayout(self.buttonLayoutMiddleCol)
        self.buttonColumnLayout.addLayout(self.buttonLayoutRightCol)
        self.controlLayout.addLayout(self.buttonColumnLayout)
        self.controlDirLayout = QHBoxLayout()
        self.controlDirLayout.addWidget(self.dirLineEdit)
        self.controlLayout.addLayout(self.controlDirLayout)
        self.controlLayout.addWidget(self.fileTree)
        # self.buttonLayout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        # self.addButton('load', self.choose_directory, multithreaded=False)

    def createBottomToolbar(self):
        self.toolLayout = QHBoxLayout()
        self.toolLayout.addWidget(self.toolbar)

        self.loadingIndictator = QLabel()
        self.loadingMovie = QMovie('assets/loader.gif')
        self.emptyLoadingPicture = QPicture()
        self.emptyLoadingPicture.load('assets/empty_loader.png')
        self.stopLoadingIndicator()
        self.toolLayout.addWidget(self.loadingIndictator)

        self.toolLayout.addItem(
            QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        # self.toolLayout.addWidget(QSpacerItem(150, 10, QSizePolicy.Expanding))

        self.copyImagePushButton = QPushButton()
        # self.copyImagePushButton.setText("Copy")
        # self.copyImagePushButton.setMinimumWidth(1)
        # self.copyImagePushButton.style().standardIcon(QStyle.SP_DialogOpenButton)
        self.copyImagePushButton.setIcon(self.style().standardIcon(
            QStyle.SP_DialogSaveButton))
        self.toolLayout.addWidget(self.copyImagePushButton)
        self.copyImagePushButton.clicked.connect(self.copyPlotToClipboard)

        downsampleLabel = QLabel()
        downsampleLabel.setText("DS")
        self.downSampleInput = QSpinBox()
        self.downSampleInput.setValue(self.downsample)
        self.toolLayout.addWidget(downsampleLabel)
        self.toolLayout.addWidget(self.downSampleInput)
        self.downSampleInput.valueChanged.connect(self.changeDownSample)

        self.statusLabel = QLabel()
        self.toolLayout.addWidget(self.statusLabel)

    def changeDownSample(self, val):
        self.downsample = val
        self.plotter.setDownSample(self.downsample)
        self.updatePlot()

    def copyPlotToClipboard(self):
        # pixmap = QPixmap.grabWidget(self.canvas)
        # QApplication.clipboard().setPixmap(pixmap)
        # pixmap.save('test.png')

        # store the image in a buffer using savefig(), this has the
        # advantage of applying all the default savefig parameters
        # such as background color; those would be ignored if you simply
        # grab the canvas using Qt
        buf = io.BytesIO()
        self.figure.savefig(buf)

        QApplication.clipboard().setImage(QImage.fromData(buf.getvalue()))
        buf.close()

    def startLoadingIndicator(self):
        self.loadingIndictator.setMovie(self.loadingMovie)
        self.loadingMovie.start()

    def dragEnterEvent(self, e):
        if (e.mimeData().hasUrls()):
            e.acceptProposedAction()

    def dropEvent(self, e):
        try:
            directory = e.mimeData().urls()[0].toLocalFile()
            self.load(directory)
        except Exception as e:
            self.showError(e)

    def showError(self, e):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setText("Unable to load log: " + e.__str__())
        msg.setDetailedText(traceback.format_exc())
        msg.exec()

    def createFileTree(self):
        self.dirModel = QFileSystemModel()
        self.dirModel.setRootPath(self.config["logs_directory"])
        self.dirModel.setFilter(QtCore.QDir.Dirs | QtCore.QDir.NoDotAndDotDot)
        self.dirLineEdit = QLineEdit()
        self.dirLineEdit.setText(self.config["logs_directory"])
        self.dirLineEdit.setFixedHeight(25)
        self.dirLineEdit.returnPressed.connect(self.handleTreeDirChange)
        self.fileTree = QTreeView()
        self.fileTree.setModel(self.dirModel)
        self.fileTree.setRootIndex(
            self.dirModel.index(self.config['logs_directory']))
        self.fileTree.setColumnHidden(1, True)
        self.fileTree.setColumnHidden(2, True)
        self.fileTree.setColumnHidden(3, True)
        self.fileTree.setMinimumWidth(300)
        self.fileTree.clicked.connect(self.handleTreeViewClick)
        self.fileTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection)
        self.fileTree.customContextMenuRequested.connect(
            self.handleTreeViewRightClick)
        # self.populateRMSCheck(self.config['logs_directory'])

    def updateFileTree(self):
        self.dirModel.setRootPath(self.config["logs_directory"])
        self.fileTree.setRootIndex(
            self.dirModel.index(self.config['logs_directory']))

    def populateRMSCheck(self, directory):
        for subdir in os.listdir(directory):
            path = os.path.join(directory, subdir)
            if os.path.isdir(path):
                self.populateRMSCheck(path)
            elif 'RMS' in subdir:
                f = open(path)
                rms_report = f.read()
                p = re.compile(r'(?<=^PASS/FAIL).*\n', re.M)
                line = re.search(p, rms_report).group()
                failed = True if "FAIL" in line else False
                if failed:
                    pass
                else:
                    pass

    def handleTreeDirChange(self):
        self.config["logs_directory"] = self.dirLineEdit.text()
        self.updateFileTree()

        file = open(self.configFilePath, 'w')
        yaml.dump(self.config, file)
        file.close()

    def handleTreeViewClick(self):
        selected_directory = self.fileTree.model().filePath(
            self.fileTree.selectedIndexes()[0])
        for fname in os.listdir(selected_directory):
            if fname.endswith('.dat'):
                try:
                    self.load(selected_directory)
                except Exception as e:
                    self.showError(e)
                break

    def handleTreeViewRightClick(self, event):
        selected_directory = os.path.normpath(self.fileTree.model().filePath(
            self.fileTree.selectedIndexes()[0]))
        menu = QMenu(self)
        copyAction = menu.addAction("Copy path")
        nppActionHot = menu.addAction("Run NPP, HOT start")
        nppActionCold = menu.addAction("Run NPP, COLD start")
        nppActionFactory = menu.addAction("Run NPP, FACTORY start")
        setDataInfoDirHotAction = menu.addAction(
            "Set dataInfo.json directory, HOT start")
        setDataInfoDirColdAction = menu.addAction(
            "Set dataInfo.json directory, COLD start")
        setDataInfoDirFactoryAction = menu.addAction(
            "Set dataInfo.json directory, FACTORY start")
        exploreAction = menu.addAction("Explore folder")
        cleanFolderAction = menu.addAction("Clean folder")
        deleteFolderAction = menu.addAction("Delete folder")
        action = menu.exec_(self.fileTree.viewport().mapToGlobal(event))
        if action == copyAction:
            cb = QApplication.clipboard()
            cb.clear(mode=cb.Clipboard)
            cb.setText(selected_directory, mode=cb.Clipboard)
        if action == nppActionHot:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_HOT)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory, self.config['serials'])
            spp.run()
        if action == nppActionCold:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_COLD)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory,
                           self.config['serials'],
                           startMode=START_MODE_COLD)
            spp.run()
        if action == nppActionFactory:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_FACTORY)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory,
                           self.config['serials'],
                           startMode=START_MODE_FACTORY)
            spp.run()
        if action == setDataInfoDirHotAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_HOT)
        if action == setDataInfoDirColdAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_COLD)
        if action == setDataInfoDirFactoryAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_FACTORY)
        if action == exploreAction:
            openFolderWithFileBrowser(selected_directory)
        if action == cleanFolderAction:
            cleanFolder(selected_directory)
        if action == deleteFolderAction:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setText("Are you sure you want to delete this folder?\n\n" +
                        selected_directory)
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            result = msg.exec()
            if result == QMessageBox.Yes:
                removeDirectory(selected_directory)

    def stopLoadingIndicator(self):
        self.loadingMovie.stop()
        self.loadingIndictator.clear()
        self.loadingIndictator.setPicture(self.emptyLoadingPicture)

    def showDeviceInfo(self):
        dlg = DeviceInfoDialog(self.log, self)
        dlg.show()
        dlg.exec_()

    def showFlashConfig(self):
        dlg = FlashConfigDialog(self.log, self)
        dlg.show()
        dlg.exec_()

    def plot(self, func, args=None):
        print("plotting " + func)
        self.currently_selected = func
        self.plotargs = args

        self.figure.clear()

        if hasattr(self, 'plotter'):
            if args is not None:
                getattr(self.plotter, func)(*args, self.figure)
            else:
                getattr(self.plotter, func)(self.figure)

        self.canvas.draw()
        self.stopLoadingIndicator()
        print("done plotting")
예제 #7
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()
예제 #8
0
파일: navigation.py 프로젝트: lheido/Mojuru
class Navigation(QWidget):
    """
    Navigation class definition.
    
    Provide a combobox to switch on each opened directories and display it into
    a tree view
    
    Provide 2 useful function (to use in alter module):
      - add_action(name, shortcut, callback)
         - callback take 2 arguments : file_info and parent
      - add_separator()
    
    """
    
    SETTINGS_DIRECTORIES = 'navigation_dirs'
    SETTINGS_CURRENT_DIR = 'navigation_current_dir'
    
    onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated")
    onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated")
    
    def __init__(self, parent=None):
        super(Navigation, self).__init__(parent)
        self.setObjectName("Navigation")
        
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0,0,0,0)
        
        self.menu_button = QPushButton('Select directory', self)
        self.menu_button.setFlat(True)
#        self.menu_button.clicked.connect(self.on_menu_button_clicked)
        self.menu = QMenu(self)
        self.menu_button.setMenu(self.menu)
        self.menu_directories = QMenu(self)
        self.menu_directories.setTitle('Directories')
        self.menu_add_action(
            'Open directory', self.open_directory, None, QKeySequence.Open)
        self.menu_add_separator()
        self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh)
        # @TODO invoke_all
        self.menu_add_separator()
        self.menu.addMenu(self.menu_directories)
        
        self.tree = QTreeView(self)
        self.model = FileSystemModel(self)
        self.tree.setModel(self.model)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)
        self.tree.setHeaderHidden(True)
        # only to expand directory or activated with one click
        self.tree.clicked.connect(self.on_item_clicked)
        # else, for file use activated signal
        self.tree.activated.connect(self.on_item_activated)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.on_context_menu)
        
        self.widgets = collections.OrderedDict()
        self.widgets['menu_button'] = self.menu_button
        self.widgets['tree'] = self.tree
        
        # @ToDo: Alter.invoke_all('add_widget', self.widgets)
        
        for name, widget in self.widgets.items():
            if name == 'menu_button':
                self.layout.addWidget(widget, 0, Qt.AlignLeft)
            else:
                self.layout.addWidget(widget)
        
        self.context_menu = QMenu(self)
        self.add_action('New file', QKeySequence.New, 
                        FileSystemHelper.new_file)
        self.add_action('New Directory', '', 
                        FileSystemHelper.new_directory)
        self.add_separator()
        self.add_action('Rename', '', FileSystemHelper.rename)
        self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy)
        self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut)
        self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste)
        self.add_separator()
        self.add_action('Delete', QKeySequence.Delete, 
                        FileSystemHelper.delete)
        
        # @ToDo Alter.invoke_all('navigation_add_action', self)
        
        #restore previous session and data
        dirs = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_DIRECTORIES, None, True)
        for directory_path in dirs:
            name = os.path.basename(directory_path)
            self.menu_add_directory(name, directory_path)
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()
        
        self.menu_button.setFocusPolicy(Qt.NoFocus)
        self.menu_button.setFocusProxy(self.tree)
    
    def reset(self, file_info):
        self.model.beginResetModel()
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()
    
    def on_menu_button_clicked(self):
        pos = self.mapToGlobal(self.menu_button.pos())
        menu_width = self.menu.sizeHint().width()
        pos.setY(pos.y() + self.menu_button.height())
#        pos.setX(pos.x() + self.menu_button.width() - menu_width)
        if len(self.menu.actions()) > 0:
            self.menu.exec(pos)
    
    def menu_add_action(self, name, callback, data=None, shortcut=None, icon=None):
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        if shortcut:
            action.setShortcut(shortcut)
            action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        if data:
            action.setData(data)
        action.triggered.connect(callback)
        self.addAction(action)
        self.menu.addAction(action)
    
    def menu_add_directory(self, name, data):
        action = QAction(name, self)
        action.setData(data)
        action.triggered.connect(self.on_menu_action_triggered)
        self.menu_directories.addAction(action)
        return action
    
    def menu_add_separator(self):
        self.menu.addSeparator()
    
    def add_action(self, name, shortcut, callback, icon = None):
        """
        Ajoute une action au context menu et au widget navigation lui même.
        Créer une fonction à la volé pour fournir des arguments aux fonctions
        associé aux actions.
        """
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        action.setShortcut(shortcut)
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.__wrapper(callback))
        self.addAction(action)
        self.context_menu.addAction(action)
    
    def add_separator(self):
        """Simple abstraction of self.context_menu.addSeparator()"""
        self.context_menu.addSeparator()
    
    def __wrapper(self, callback):
        def __new_function():
            """
            __new_function représente la forme de tous les callbacks connecté
            à une action pour pouvoir utiliser les raccourcis en même temps que
            le menu contextuel.
            """
            action = self.sender()
            file_info = action.data()
            if not file_info:
                indexes = self.tree.selectedIndexes()
                if indexes:
                    model_index = indexes[0]
                    file_info = self.model.fileInfo(model_index)
                    callback(file_info, self)
                elif action.shortcut() == QKeySequence.New:
                    file_info = self.model.fileInfo(self.tree.rootIndex())
                    callback(file_info, self)
            else:
                callback(file_info, self)
                action.setData(None)
        return __new_function
    
    def question(self, text, informative_text = None):
        message_box = QMessageBox(self)
        message_box.setText(text)
        if informative_text:
            message_box.setInformativeText(informative_text)
        message_box.setStandardButtons(
            QMessageBox.No | QMessageBox.Yes)
        message_box.setDefaultButton(QMessageBox.No)
        return message_box.exec()
    
    def on_context_menu(self, point):
        model_index = self.tree.indexAt(point)
        file_info = self.model.fileInfo(model_index)
        # pour chaque action on met a jour les data (file_info)
        # puis on altère les actions (ex enabled)
        for action in self.context_menu.actions():
            if not action.isSeparator():
                action.setData(file_info)
                action.setEnabled(model_index.isValid())
                if action.shortcut() == QKeySequence.New:
                    action.setEnabled(True)
                    if not model_index.isValid():
                        file_info = self.model.fileInfo(self.tree.rootIndex())
                        action.setData(file_info)
                if action.shortcut() == QKeySequence.Paste:
                    enable = FileSystemHelper.ready() and model_index.isValid()
                    action.setEnabled(enable)
                if action.shortcut() == QKeySequence.Delete:
                    # remove directory only if is an empty directory
                    if model_index.isValid() and file_info.isDir():
                        path = file_info.absoluteFilePath()
                        # QDir(path).count() always contains '.' and '..'
                        action.setEnabled(QDir(path).count() == 2)
                # @ToDo 
                #Alter.invoke_all(
                #    'navigation_on_menu_action', 
                #    model_index, file_info, action, self)
        if len(self.context_menu.actions()) > 0:
            self.context_menu.exec(self.tree.mapToGlobal(point))
        # reset action data, sinon y a des problèmes dans _new_function
        for action in self.context_menu.actions():
            action.setData(None)
    
    def on_item_activated(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
        else:
            self.onFileItemActivated.emit(qFileInfo)
    
    def on_item_clicked(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
            self.tree.setExpanded(index, not self.tree.isExpanded(index))
        else:
            self.onFileItemActivated.emit(qFileInfo)
    
    def open_directory(self):
        project = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        path = QFileDialog.getExistingDirectory(self, "Open Directory", project)
        if path:
            name = os.path.basename(path)
            action = self.menu_add_directory(name, path)
            self.save_directories_path()
            action.trigger()
    
    def on_menu_action_triggered(self):
        action = self.sender()
        path = action.data()
        if path:
            self.model.setRootPath(path)
            self.tree.setRootIndex(self.model.index(path))
            self.menu_button.setText(os.path.basename(path))
            self.save_current_dir(path)
    
    def save_directories_path(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_DIRECTORIES,
            [action.data() for action in self.menu_directories.actions()]    
        )
    
    def save_current_dir(self, path):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_CURRENT_DIR,
            path
        )
예제 #9
0
class PYCFScape(QMainWindow):
    def __init__(self):
        super().__init__()

        # We determine where the script is placed, for acessing files we use (such as the icon)
        self.we_live_in = sys.argv[0]
        self.we_live_in = os.path.split(self.we_live_in)[0]

        self.build_interface()

        self.opened_file = None  # stores the filepath
        self.opened_vpk = None  # stores the opened vpk
        self.internal_directory_understanding = {
        }  # a dictionary version of the paths
        self.vpk_loaded = False  # whether or not we have a vpk file open
        self.export_paths = [
        ]  # Paths we want to export when file->export is selected

    def build_interface(self):
        self.setWindowTitle("PYCFScape")
        self.setWindowIcon(
            QIcon(os.path.join(self.we_live_in, 'res/Icon64.ico')))

        self.main_content = QWidget()
        self.main_content_layout = QVBoxLayout()
        self.main_content.setLayout(self.main_content_layout)

        # set up the interface parts
        self.output_console = QTextEdit()  # Will basically just copy stdout
        self.output_console.setReadOnly(True)
        sys.stdout = bob_logger(self.output_console, False, sys.stdout)
        sys.stderr = bob_logger(self.output_console, True, sys.stderr)

        self.vpk_tree = QTreeView()  # Displays the tree of the vpk's content
        self.vpk_tree_model = QStandardItemModel(self.vpk_tree)
        self.vpk_tree.setModel(self.vpk_tree_model)
        self.vpk_tree._mousePressEvent = self.vpk_tree.mousePressEvent  # store it so we can call it
        self.vpk_tree.mousePressEvent = self.vpk_tree_item_clicked

        self.vpk_tree.setHeaderHidden(True)

        # We use a QTreeView to also display headers.
        # We however, still treat it as a list view.
        self.dir_list = QTreeView(
        )  # Displays the list of the current vpk's directory's content
        self.dir_list_model = QStandardItemModel(self.dir_list)
        self.dir_list.setModel(self.dir_list_model)
        self.dir_list.doubleClicked.connect(self.dir_list_item_double_clicked)
        self.dir_list_model.itemChanged.connect(self.dir_list_item_updated)
        self.dir_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.dir_list.customContextMenuRequested.connect(
            self.dir_list_context_menu)

        self.dir_list_model.setColumnCount(2)
        self.dir_list_model.setHorizontalHeaderLabels(["Name", "Type"])
        self.dir_list.header().resizeSection(0, 250)

        # The tool bar - WARNING: MESSY CODE!
        self.actions_toolbar = self.addToolBar("")
        # OPEN BUTTON
        self.open_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("document-open"), "Open File")
        self.open_button.triggered.connect(self.open_file)

        self.actions_toolbar.addSeparator()

        self.back_button = QToolButton()  # GO BACK BUTTON
        self.back_button.setIcon(QIcon.fromTheme("go-previous"))
        self.back_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.back_button)

        self.forward_button = QToolButton()  # GO FORWARD BUTTON
        self.forward_button.setIcon(QIcon.fromTheme("go-next"))
        self.forward_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.forward_button)

        self.up_button = QToolButton()  # GO UP BUTTON
        self.up_button.setIcon(QIcon.fromTheme("go-up"))
        self.up_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.up_button)

        self.actions_toolbar.addSeparator()

        # FIND BUTTON
        self.search_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("system-search"), "Find in file")
        self.search_button.setDisabled(True)
        self.search_button.triggered.connect(self.search_for_file)

        self.actions_toolbar.addSeparator()

        # EXPORT BUTTON
        self.export_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("extract-archive"), "Export Selection")
        self.export_button.setDisabled(False)
        self.export_button.triggered.connect(self.export_selection)

        self.main_content_layout.addWidget(self.actions_toolbar)

        # now we want the menubar
        self.menu_bar = self.menuBar()
        self.file_menu = self.menu_bar.addMenu("&File")
        self.edit_menu = self.menu_bar.addMenu("&Edit")
        self.menu_bar.addSeparator()
        self.help_menu = self.menu_bar.addMenu("&Help")

        self.file_menu.addActions([self.open_button])

        self.close_button = self.file_menu.addAction(
            QIcon.fromTheme("document-close"),
            "Close File"  # bit redundant, i actually see no use
        )
        self.close_button.triggered.connect(self.close_vpk)

        self.file_menu.addSeparator()

        self.file_menu.addAction(QIcon.fromTheme("application-exit"),
                                 "Exit").triggered.connect(self.close)

        self.edit_menu.addActions([self.search_button])

        self.help_menu.addAction(QIcon.fromTheme("help-about"),
                                 "About && License").triggered.connect(
                                     self.about)

        # the statusbar
        self.status_bar = self.statusBar()

        # set up the splitters

        # horizontal
        self.horz_splitter_container = QWidget()
        self.horz_splitter_container_layout = QVBoxLayout()
        self.horz_splitter_container.setLayout(
            self.horz_splitter_container_layout)

        self.horz_splitter = QSplitter(Qt.Horizontal)

        self.horz_splitter.addWidget(self.vpk_tree)
        self.horz_splitter.addWidget(self.dir_list)

        self.horz_splitter.setSizes([50, 200])

        self.horz_splitter_container_layout.addWidget(self.horz_splitter)

        # vertical
        self.vert_splitter = QSplitter(Qt.Vertical)

        self.vert_splitter.addWidget(self.horz_splitter_container)
        self.vert_splitter.addWidget(self.output_console)

        self.vert_splitter.setSizes([200, 50])

        self.main_content_layout.addWidget(self.vert_splitter)
        self.setCentralWidget(self.main_content)

        self.show()

    ##
    # Update Functions
    ##
    def update_console(self, text, is_stdout):
        colour = Qt.Red if not is_stdout else Qt.black
        self.output_console.setTextColor(colour)
        self.output_console.moveCursor(QTextCursor.End)
        self.output_console.insertPlainText(text)

    def update_interface(self):

        # update the tree view
        self.update_vpk_tree()

        # update the list view
        self.update_dir_list()

        self.search_button.setDisabled(not self.vpk_loaded)

    def update_vpk_tree(self):
        self.vpk_tree_model.removeRows(0, self.vpk_tree_model.rowCount())
        if self.opened_vpk:
            self.tree_magic(self.internal_directory_understanding)

    def update_dir_list(self):
        self.dir_list_model.removeRows(0, self.dir_list_model.rowCount(
        ))  # remove anyway (for instances such as opening a new file)

        selected = self.vpk_tree.selectedIndexes()
        if not selected:
            return
        selected = selected[0]
        selected_item = self.vpk_tree_model.itemFromIndex(selected)
        if not selected_item:
            return

        if not selected_item.is_dir:
            return

        path = selected_item.path
        understanding = self.get_understanding_from(
            self.internal_directory_understanding, path)
        self.list_magic(understanding, path)

    ##
    # Events
    ##
    def vpk_tree_item_clicked(self, event):
        self.vpk_tree._mousePressEvent(event)

        index = self.vpk_tree.indexAt(event.pos())

        # We can rather safely assume any items in the vpk tree will have OUR special attributes
        if index.isValid():
            item = self.vpk_tree_model.itemFromIndex(index)
            print("selected", item.path)

            if item.is_dir:
                self.update_dir_list()

    def dir_list_item_double_clicked(self, index):

        item = self.dir_list_model.itemFromIndex(index)
        if not item.column() == 0:
            return
        print("double clicked", item.path)

        if item.is_dir:
            print("is dir")
            # this is probably a REALLY **REALLY** awful way of doing this, but you're welcome to PR a better way. :)
            index_in_tree = self.find_in_model(self.vpk_tree_model, item.path)

            if index_in_tree.isValid():
                self.vpk_tree.setCurrentIndex(index_in_tree)
                self.update_dir_list()
        else:
            self.status_bar.showMessage(MSG_EXPORT)

            # we clearly wanna export the file and open it
            bits = self.opened_vpk[item.path[1:]].read()

            path = compat.write_to_temp(bits, os.path.split(item.path)[1])
            print("Wrote to", path)
            compat.tell_os_open(path)

            self.status_bar.clearMessage()

    def dir_list_item_updated(self, item):
        if item.checkState() == Qt.Checked:
            if not item.is_dir:
                self.export_paths.append(item.path)
            else:
                index_in_tree = self.find_in_model(self.vpk_tree_model,
                                                   item.path)

                if index_in_tree.isValid():
                    paths = self.recursively_get_paths_from_dir_index_item(
                        index_in_tree, self.vpk_tree_model)

                    self.export_paths += paths

        elif item.checkState() == Qt.Unchecked:
            if not item.is_dir:
                if item.path in self.export_paths:
                    self.export_paths.remove(item.path)
            else:
                index_in_tree = self.find_in_model(self.vpk_tree_model,
                                                   item.path)

                if index_in_tree.isValid():
                    paths = self.recursively_get_paths_from_dir_index_item(
                        index_in_tree, self.vpk_tree_model)

                    for path in paths:
                        if path in self.export_paths:
                            self.export_paths.remove(path)

    ##
    # The next 3 functions are the original pathtodir but merged with the program
    ##
    def nested_dict(self):
        return defaultdict(self.nested_dict)

    def nested_dict_to_regular(self, d):
        if not isinstance(d, defaultdict):
            return d
        return {k: self.nested_dict_to_regular(v) for k, v in d.items()}

    def understand_directories(self, list_of_paths):
        use_dict = defaultdict(self.nested_dict)

        for path in list_of_paths:
            parts = path.split('/')
            if parts:
                marcher = use_dict
                for key in parts[:-1]:
                    marcher = marcher[key]
                marcher[parts[-1]] = parts[-1]

        return dict(use_dict)

    def get_understanding_from(self, understanding, path):
        keys = path.split('/')
        if keys[0] == '':
            keys = keys[1:]

        if keys:
            marcher = understanding
            for key in keys:
                marcher = marcher[key]

            # we can now assume marcher is the understanding we want
            return marcher

    ##
    # Utility
    ##
    def tree_magic(self, dict_of_things, parent=None, root=''):

        if not parent:
            parent = self.vpk_tree_model

        # Stack overflow 14478170
        for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)):

            path = root + '/{}'.format(thing)

            thing_item = QStandardItem()
            thing_item.setText(thing)
            thing_item.setEditable(False)

            thing_item.path = path
            thing_item.is_dir = False

            icon, _ = self.get_info_from_path(path)

            thing_item.setIcon(icon)

            if isinstance(dict_of_things[thing], dict):
                thing_item.setIcon(QIcon.fromTheme("folder"))
                self.tree_magic(dict_of_things[thing], thing_item, path)

                thing_item.is_dir = True

            parent.appendRow(thing_item)

    def list_magic(self, dict_of_things, root=''):
        # like tree_magic but operates on dir_list

        for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)):

            path = root + '/{}'.format(thing)

            thing_item = QStandardItem()
            thing_item.setText(thing)
            thing_item.setEditable(False)
            thing_item.setCheckable(True)

            thing_item_type = QStandardItem(
            )  # This doesn't actually do anything but convey more information to the user
            thing_item_type.setEditable(False)

            if path in self.export_paths:
                thing_item.setCheckState(Qt.Checked)

            thing_item.path = path
            thing_item.is_dir = False

            icon, desc = self.get_info_from_path(path)

            thing_item.setIcon(icon)
            thing_item_type.setText(desc)

            if isinstance(dict_of_things[thing], dict):
                thing_item.setIcon(QIcon.fromTheme("folder"))
                thing_item.is_dir = True
                thing_item_type.setText("Directory")

            self.dir_list_model.appendRow([thing_item, thing_item_type])

    def get_info_from_path(self,
                           path):  # returns the icon AND description string

        icon = None
        desc = None

        # first we test against mimetype
        # probably bad code, but it works!

        thing_mimetype = mimetypes.guess_type(path)[0]

        if thing_mimetype:
            if thing_mimetype[:6] == "audio/":
                icon = QIcon.fromTheme("audio-x-generic")
            elif thing_mimetype[:12] == "application/":
                icon = QIcon.fromTheme("application-x-generic")
            elif thing_mimetype[:5] == "text/":
                icon = QIcon.fromTheme("text-x-generic")
            elif thing_mimetype[:6] == "image/":
                icon = QIcon.fromTheme("image-x-generic")
            elif thing_mimetype[:6] == "video/":
                icon = QIcon.fromTheme("video-x-generic")

            desc = thing_mimetype

        # well ok, maybe that didn't work, let's test the filepath ourselves.

        file_ext = os.path.splitext(path)[1]

        if file_ext:
            if file_ext in [".vtf"]:
                icon = QIcon.fromTheme("image-x-generic")
                desc = "Valve Texture File"
            elif file_ext in [".vmt"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Material File"
            elif file_ext in [
                    ".pcf"
            ]:  # we can safely assume they are not fonts in this context, but rather
                icon = QIcon.fromTheme("text-x-script")
                desc = "Valve DMX Implementation"  # TODO: is there a better name
            elif file_ext in [".bsp"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Binary Space Partition"
            elif file_ext in [".res"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Key Value"
            elif file_ext in [".vcd"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Choreography Data"

        if not icon:  # If all else fails, display SOMETHING
            icon = QIcon.fromTheme("text-x-generic")
        if not desc:
            desc = "File"

        return icon, desc

    def find_in_model(self, model: QStandardItemModel, path):
        for i in range(0, model.rowCount()):
            index_in_tree = model.index(i, 0)

            if model.itemFromIndex(index_in_tree).path == path:
                return index_in_tree

            if model.itemFromIndex(index_in_tree).is_dir:
                index_in_tree = self.find_in_model_parent(model,
                                                          path,
                                                          parent=index_in_tree)

                if not index_in_tree.isValid():
                    continue

                if model.itemFromIndex(index_in_tree).path == path:
                    return index_in_tree

    def find_in_model_parent(self, model: QStandardItemModel, path, parent):
        for i in range(0, model.rowCount(parent)):
            index_in_tree = model.index(i, 0, parent)

            if model.itemFromIndex(index_in_tree).path == path:
                return index_in_tree

            if model.itemFromIndex(index_in_tree).is_dir:
                index_in_tree = self.find_in_model_parent(model,
                                                          path,
                                                          parent=index_in_tree)

                if not index_in_tree.isValid():
                    continue

                if model.itemFromIndex(index_in_tree).path == path:
                    return index_in_tree

        return QModelIndex()

    def export_file(self, path, out_dir):
        filepath = os.path.split(path)[0]

        if not os.path.isdir('{}{}'.format(out_dir, filepath)):
            os.makedirs('{}{}'.format(out_dir, filepath))

        print("Attempting to export to", "{}{}".format(out_dir, filepath),
              "from", path[1:], "in the vpk")
        outcontents = self.opened_vpk.get_file(path[1:]).read()

        outfile = open('{}{}'.format(out_dir, path), 'wb')
        outfile.write(outcontents)
        outfile.close()

    def recursively_get_paths_from_dir_index_item(self, index_in_tree, model):
        paths = []

        for i in range(
                self.vpk_tree_model.itemFromIndex(index_in_tree).rowCount()):
            index = self.vpk_tree_model.index(i, 0, index_in_tree)
            index_item = self.vpk_tree_model.itemFromIndex(index)

            if not index_item.is_dir:
                paths.append(index_item.path)
            else:
                paths += self.recursively_get_paths_from_dir_index_item(
                    index, model)

        return paths

    def close_vpk(self):  # We trash everything!
        self.vpk_loaded = False
        self.opened_file = None
        self.opened_vpk = {}
        self.export_paths = []
        self.internal_directory_understanding = {}

        self.status_bar.showMessage(MSG_UPDATE_UI)
        self.update_interface()
        self.status_bar.clearMessage()

    def open_vpk(self, vpk_path):
        if self.vpk_loaded:  # if we already have a file open, close it.
            self.close_vpk()

        self.status_bar.showMessage(MSG_OPEN_VPK)

        if not os.path.exists(vpk_path):
            print(
                "Attempted to open {}, which doesn't exist.".format(vpk_path))
            return

        self.opened_file = vpk_path

        try:
            self.opened_vpk = vpk.open(vpk_path)
        except Exception as e:
            print("Ran into an error from the VPK Library.")
            sys.stdout.write(str(e))
            self.error_box(str(e))
            return

        self.vpk_loaded = True

        self.status_bar.showMessage(MSG_UNDERSTAND_VPK)

        # Now we attempt to understand the vpk
        self.internal_directory_understanding = self.understand_directories(
            self.opened_vpk)

        self.status_bar.showMessage(MSG_UPDATE_UI)
        self.update_interface()
        self.status_bar.clearMessage()

    ##
    # Dialogs
    ##
    def open_dialog(self):
        fn = QFileDialog.getOpenFileName(None,
                                         "Open Package",
                                         str(pathlib.Path.home()),
                                         filter=("Valve Pak Files (*.vpk)"))

        return fn

    def open_dir_dialog(self, title="Open Directory"):
        fn = QFileDialog.getExistingDirectory(None, title,
                                              str(pathlib.Path.home()))

        return fn

    def error_box(self, text="...", title="Error"):
        box = QMessageBox()
        box.setIcon(QMessageBox.Critical)
        box.setText(text)
        box.setWindowTitle(title)
        box.setStandardButtons(QMessageBox.Ok)

        return box.exec()

    def info_box(self, text="...", title="Info"):
        box = QMessageBox()
        box.setIcon(QMessageBox.Information)
        box.setText(text)
        box.setWindowTitle(title)
        box.setStandardButtons(QMessageBox.Ok)

        return box.exec()

    def dir_list_context_menu(self, event):
        menu = QMenu(self)

        selected = self.dir_list.selectedIndexes()
        if not selected:
            return
        selected = selected[0]
        selected_item = self.dir_list_model.itemFromIndex(selected)
        path = selected_item.path

        extract = menu.addAction(QIcon.fromTheme("extract-archive"), "Extract")
        validate = menu.addAction(QIcon.fromTheme("view-certificate"),
                                  "Validate")  # TODO: better validation icon
        menu.addSeparator()
        gotodirectory = menu.addAction(QIcon.fromTheme("folder"),
                                       "Go To Directory")
        menu.addSeparator()
        properties = menu.addAction(QIcon.fromTheme("settings-configure"),
                                    "Properties")

        extract.setDisabled(selected_item.is_dir)
        validate.setDisabled(selected_item.is_dir)

        action = menu.exec_(self.dir_list.mapToGlobal(event))
        if action == extract:
            self.export_selected_file(path)
        elif action == validate:
            self.validate_file(path)
        elif action in [gotodirectory, properties]:
            self.info_box(
                "I'm not sure what this does in the original GCFScape.\nIf you know, please make an issue on github!"
            )

    def about(self):
        box = QMessageBox()
        box.setWindowTitle(
            "About PYCFScape")  # TODO: what do we version and how
        box.setText("""PYCFScape
Version 0
MIT LICENSE V1.00 OR LATER
Python {}
QT {}
AUTHORS (Current Version):
ACBob - https://acbob.gitlab.io

Project Homepage
https://github.com/acbob/pycfscape""".format(sys.version, QT_VERSION_STR))

        box.setIcon(QMessageBox.Information)

        box.exec()

    ##
    # Actions
    ##
    def open_file(self):
        self.status_bar.showMessage(MSG_USER_WAIT)

        fn = self.open_dialog()[0]
        if not fn:
            self.status_bar.clearMessage()
            return

        self.open_vpk(fn)

    def search_for_file(self, event):
        print(event)

    def export_selection(self):
        if not self.export_paths:
            self.info_box(
                "You can't export nothing!\n(Please select some items to export.)"
            )
            return

        self.status_bar.showMessage(MSG_USER_WAIT)
        output_dir = self.open_dir_dialog("Export to...")

        if output_dir:
            self.status_bar.showMessage(MSG_EXPORT)
            print("attempting export to", output_dir)

            for file in self.export_paths:
                self.export_file(file, output_dir)

        self.status_bar.clearMessage()

    def export_selected_file(self, file):
        self.status_bar.showMessage(MSG_USER_WAIT)
        output_dir = self.open_dir_dialog("Export to...")

        if output_dir:
            self.status_bar.showMessage(MSG_EXPORT)
            print("attempting export to", output_dir)

            self.export_file(file, output_dir)

        self.status_bar.clearMessage()

    def validate_file(self, file):
        filetoverify = self.opened_vpk.get_file(file[1:])

        if filetoverify:
            verified = filetoverify.verify()
            if verified:
                self.info_box("{} is a perfectly healthy file.".format(file),
                              "All's good.")
            else:
                self.error_box("{} is not valid!".format(file), "Uh oh.")
        else:
            print("What? file doesn't exist? HOW IS THIS MAN")
예제 #10
0
class ZeroConfExplorer(QWidget):
    """
    create a zeroconf qgroubbox with a qlist view 
    """
    def __init__(self, name):
        super(ZeroConfExplorer, self).__init__()
        # init explorer to None
        self.oscquery_device = None
        if not name:
            name = 'OSCJSON thru TCP Explorer'
        # create the view
        self.explorer = QTreeView()
        # Hide Useless Header
        self.explorer.header().hide()
        self.panel = Panel()
        # create right-click menu
        self.explorer.setContextMenuPolicy(Qt.CustomContextMenu)
        self.explorer.customContextMenuRequested.connect(self.contextual_menu)
        # create the model
        self.devices_model = QStandardItemModel()
        # link model to the view
        self.explorer.setModel(self.devices_model)
        self.explorer.selectionModel().selectionChanged.connect(
            self.selection_updated)
        # set selection
        self.device_selection_model = self.explorer.selectionModel()
        # set layout and group
        Layout = QGridLayout()
        # add the view to the layout
        Layout.addWidget(self.explorer, 0, 0)
        Layout.addWidget(self.panel, 0, 1)
        # add the layout to the GroupBox
        self.setLayout(Layout)
        #self.setMinimumSize(300, 300)
        self.explorer.setFixedSize(300, 300)
        # start zeroconf services
        zeroconf = Zeroconf()
        # start the callback, it will create items
        listener = ZeroConfListener(self.devices_model)
        listener.add_device.connect(self.connect_device)
        browser = ServiceBrowser(zeroconf, "_oscjson._tcp.local.", listener)
        self.current_remote = None

    def connect_device(self, device):
        self.panel.device = device

    def contextual_menu(self, position):

        indexes = self.explorer.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1
            node = self.devices_model.itemFromIndex(index)
            menu = QMenu()
            if level == 0:
                menu.addAction("Refresh Device Namespace", node.update)
            elif level > 0:
                menu.addAction("Refresh Node", node.update)
            menu.exec_(self.explorer.viewport().mapToGlobal(position))

    def selection_updated(self, *args, **kwargs):
        """
        called when device selection is updated
        we will disconnect our ossia.OSCQueryDevice from the previous device if there was one
        and then we will reconnect it to the current instance of the Device model
        """
        index = self.device_selection_model.selectedIndexes()
        # we consider unique selection
        modelIndex = index[0]
        if modelIndex:
            if self.current_remote:
                self.panel.layout.removeWidget(self.current_remote)
                self.current_remote.deleteLater()
                del self.current_remote
                self.current_remote = None
            node = self.devices_model.itemFromIndex(modelIndex).node
            if node.__class__.__name__ == 'OSCQueryDevice':
                print('Device')
            else:
                if node.parameter:
                    self.current_remote = self.panel.add_remote(node.parameter)
                else:
                    self.current_remote = self.panel.add_inspector(node)
        else:
            print('no node selected')
예제 #11
0
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.srvList = ["", "opcUA", "modbusTCP"]

        if (os.name == 'nt'):

            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            self.imgPath = self.appPath + "img\\"
            self.dbPath = self.appPath + "db\\webDb.db"
            #import self.appPath+srvconf

        else:
            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            self.imgPath = self.appPath + "img/"
            self.dbPath = self.appPath + "db/webDb.db"

        self.versionPr = 'ScadaPy Web JSON Сервер v.3.14'
        self.setGeometry(300, 100, 1500, 820)
        self.setWindowTitle(self.versionPr)
        self.setWindowIcon(QIcon(self.imgPath + 'Globe.png'))
        self.h = self.frameGeometry().height()
        self.w = self.frameGeometry().width()
        self.setStyleSheet("background-color: #FFF8E7;")

        font = QFont()
        font.setPointSize(12)

        self.label0 = QLabel(self)
        self.label0.setFont(font)
        self.label0.move(400, 60)
        self.label0.resize(300, 25)
        self.label0.setText("ID сервера")
        self.label00 = QLabel(self)
        self.label00.setFont(font)
        self.label00.move(550, 60)
        self.label00.resize(300, 25)
        self.label00.setText("")

        self.label1 = QLabel(self)
        self.label1.setFont(font)
        self.label1.move(400, 90)
        self.label1.resize(140, 25)
        self.label1.setText("Название сервера")
        self.srvName = QLineEdit(self)
        self.srvName.setToolTip('Пример: Сервер Маш.Зала №1')
        self.srvName.move(550, 90)
        self.srvName.setFont(font)
        self.srvName.resize(300, 25)

        self.label2 = QLabel(self)
        self.label2.setFont(font)
        self.label2.move(400, 120)
        self.label2.resize(140, 25)
        self.label2.setText("Http IP address")
        self.slaveIP = QLineEdit(self)
        self.slaveIP.setToolTip('Пример: 192.168.0.111')
        self.slaveIP.move(550, 120)
        self.slaveIP.setFont(font)
        self.slaveIP.resize(300, 25)

        self.label2 = QLabel(self)
        self.label2.setFont(font)
        self.label2.move(400, 150)
        self.label2.resize(140, 25)
        self.label2.setText("Http Port")
        self.slavePort = QLineEdit(self)
        self.slavePort.setToolTip('Пример : 8080')
        self.slavePort.move(550, 150)
        self.slavePort.setFont(font)
        self.slavePort.resize(100, 25)

        self.label7 = QLabel(self)
        self.label7.setFont(font)
        self.label7.move(680, 150)
        self.label7.resize(140, 25)
        self.label7.setText("Timeout")

        self.serverTimeout = QLineEdit(self)
        self.serverTimeout.setToolTip('Пример ms: 1 ')
        self.serverTimeout.move(750, 150)
        self.serverTimeout.setFont(font)
        self.serverTimeout.resize(100, 25)

        self.label8 = QLabel(self)
        self.label8.setFont(font)
        self.label8.move(400, 180)
        self.label8.resize(140, 25)
        self.label8.setText("Login")
        self.serverLogin = QLineEdit(self)
        self.serverLogin.setToolTip('Имя пользователя')
        self.serverLogin.move(550, 180)
        self.serverLogin.setFont(font)
        self.serverLogin.resize(100, 25)

        self.label9 = QLabel(self)
        self.label9.setFont(font)
        self.label9.move(680, 180)
        self.label9.resize(140, 25)
        self.label9.setText("Password")
        self.serverPassword = QLineEdit(self)
        self.serverPassword.setToolTip('Пароль')
        self.serverPassword.move(750, 180)
        self.serverPassword.setFont(font)
        self.serverPassword.resize(100, 25)

        exitAction = QAction(QIcon(self.imgPath + 'exit.png'), '&Выход', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Выход из программы')
        exitAction.triggered.connect(qApp.quit)

        addServerAction = QAction(QIcon(self.imgPath + 'add.png'), '&Добавить',
                                  self)
        addServerAction.setStatusTip('Добавить сервер')
        addServerAction.triggered.connect(self.addNewServer)

        delServerAction = QAction(QIcon(self.imgPath + 'button_cancel.png'),
                                  '&Удалить', self)
        delServerAction.setStatusTip('Удалить сервер')
        delServerAction.triggered.connect(self.delServer)

        saveServerAction = QAction(QIcon(self.imgPath + 'filesave.png'),
                                   '&Сохранить', self)
        saveServerAction.setStatusTip('Сохранить сервер')
        saveServerAction.triggered.connect(self.saveServer)

        saveScr = QAction(QIcon(self.imgPath + 'bottom.png'),
                          '&Сохранить скрипт', self)
        saveScr.setStatusTip('Сохранить скрипт')
        saveScr.triggered.connect(self.saveScr)

        runScr = QAction(QIcon(self.imgPath + 'run.png'), '&Запустить скрипт',
                         self)
        runScr.setStatusTip('Запустить скрипт')
        runScr.triggered.connect(self.runScr)

        runConf = QAction(QIcon(self.imgPath + 'app.png'),
                          '&Запустить конфигуратор', self)
        runConf.setStatusTip('Запустить конфигуратор')
        runConf.triggered.connect(self.runConf)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&Команды')
        fileMenu.addAction(addServerAction)
        fileMenu.addAction(delServerAction)
        fileMenu.addAction(saveServerAction)
        fileMenu.addAction(saveScr)
        fileMenu.addAction(runScr)
        fileMenu.addAction(exitAction)

        self.toolbar = self.addToolBar('Выход')
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(addServerAction)
        self.toolbar.addAction(delServerAction)
        self.toolbar.addAction(saveServerAction)
        self.toolbar.addAction(saveScr)
        self.toolbar.addAction(runScr)
        self.toolbar.addAction(runConf)

        # self.statusBar().showMessage('Загрузка данных')

        self.treeView = QTreeView(self)
        self.treeView.setFont(font)
        self.treeView.setObjectName("treeView")
        self.model = QStandardItemModel()
        self.treeView.setModel(self.model)
        self.treeView.setStyleSheet("background-color: white;")
        self.header = ['Название сервера']
        self.model.setHorizontalHeaderLabels(self.header)

        self.sqlLoad()
        self.treeView.clicked.connect(self.onClickItem)

        self.frameTable = QFrame(self)
        self.frameTable.setVisible(True)

        self.addRow = QPushButton(self.frameTable)
        self.addRow.setIcon(QIcon(self.imgPath + 'add.png'))
        self.addRow.move(10, 10)
        self.addRow.resize(30, 30)
        self.addRow.clicked.connect(self.addItemTree)

        self.saveTable = QPushButton(self.frameTable)
        self.saveTable.setIcon(QIcon(self.imgPath + 'filesave.png'))
        self.saveTable.resize(30, 30)
        self.saveTable.move(50, 10)
        self.saveTable.clicked.connect(self.saveData)

        ####################################################################

        self.treeTable = QTableWidget(self.frameTable)
        self.treeTable.setStyleSheet("background-color: white;")
        fontTable = QFont()
        fontTable.setPointSize(10)
        self.treeTable.setFont(fontTable)

        #        self.show()
        self.showMaximized()

    def addItemTree(self):
        items = ("modbus_tcp", "opc_ua")
        item, okPressed = QInputDialog.getItem(self, "Добавить тип сервера",
                                               "Тип:", items, 0, False)
        if okPressed and item:
            self.treeTable.insertRow(self.treeTable.rowCount())
            self.treeTable.setItem(self.treeTable.rowCount() - 1, 0,
                                   QTableWidgetItem(item))

    def getData(self):
        self.treeTable.clear()
        self.treeTable.setColumnCount(10)
        #self.treeTable.setRowCount(1)
        self.treeTable.setHorizontalHeaderLabels([
            'Тип сервера', 'Имя переменной', 'Имя параметра', 'IP адрес',
            'Порт', 'Логин', 'Пароль', 'Timeout', 'Адрес ячейки', 'Количество'
        ])
        self.treeTable.resizeColumnsToContents()
        self.treeTable.setColumnWidth(0, 120)
        self.treeTable.setColumnWidth(1, 170)
        self.treeTable.setColumnWidth(2, 170)
        self.treeTable.setColumnWidth(3, 140)
        self.treeTable.setColumnWidth(4, 80)
        self.treeTable.setColumnWidth(5, 120)
        self.treeTable.setColumnWidth(6, 120)
        self.treeTable.setColumnWidth(7, 80)
        self.treeTable.setColumnWidth(8, 120)
        self.treeTable.setColumnWidth(9, 80)

        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        cursor.execute(
            "select type,var,param,ip,port,login,password,t,regadr,regcount from  master where serverId = "
            + self.label00.text())
        dt = cursor.fetchall()
        self.treeTable.setRowCount(len(dt))
        for i in range(0, len(dt)):
            self.treeTable.setItem(i, 0, QTableWidgetItem(dt[i][0]))
            self.treeTable.setItem(i, 1, QTableWidgetItem(dt[i][1]))
            self.treeTable.setItem(i, 2, QTableWidgetItem(dt[i][2]))
            self.treeTable.setItem(i, 3, QTableWidgetItem(dt[i][3]))
            self.treeTable.setItem(i, 4, QTableWidgetItem(dt[i][4]))
            self.treeTable.setItem(i, 5, QTableWidgetItem(dt[i][5]))
            self.treeTable.setItem(i, 6, QTableWidgetItem(dt[i][6]))
            self.treeTable.setItem(i, 7, QTableWidgetItem(dt[i][7]))
            self.treeTable.setItem(i, 8, QTableWidgetItem(dt[i][8]))
            self.treeTable.setItem(i, 9, QTableWidgetItem(dt[i][9]))

            i += 1

    def saveData(self):
        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        cursor.execute("delete from master where serverId= '" +
                       self.label00.text() + "'")
        connDb.commit()

        for i in range(0, self.treeTable.rowCount()):
            try:
                if (len(self.treeTable.item(i, 0).text()) > 0
                        and len(self.treeTable.item(i, 1).text()) > 0
                        and len(self.treeTable.item(i, 2).text()) > 0
                        and len(self.treeTable.item(i, 3).text()) > 0
                        and len(self.treeTable.item(i, 4).text()) > 0
                        and len(self.treeTable.item(i, 5).text()) > 0
                        and len(self.treeTable.item(i, 6).text()) > 0
                        and len(self.treeTable.item(i, 7).text()) > 0
                        and len(self.treeTable.item(i, 8).text()) > 0):

                    cursor.execute(
                        "INSERT INTO master(serverId,type,var,param,ip,port,login,password,t,regadr,regcount,valid)\
                                 VALUES('" + self.label00.text() + "',\
                                 '" + self.treeTable.item(i, 0).text() + "',\
                                 '" + self.treeTable.item(i, 1).text() + "',\
                                 '" + self.treeTable.item(i, 2).text() + "',\
                                 '" + self.treeTable.item(i, 3).text() + "',\
                                 '" + self.treeTable.item(i, 4).text() + "',\
                                 '" + self.treeTable.item(i, 5).text() + "',\
                                 '" + self.treeTable.item(i, 6).text() + "',\
                                 '" + self.treeTable.item(i, 7).text() + "',\
                                 '" + self.treeTable.item(i, 8).text() + "',\
                                 '" + self.treeTable.item(i, 9).text() + "',\
                                 1)")
                    connDb.commit()
            except Exception as e:
                print(e)
                pass
            i += 1
        self.getData()

    def truePanel(self):
        self.getData()

    def onClickItem(self):
        self.treeTable.clear()
        self.treeTable.setRowCount(0)
        self.srvName.setText("")
        self.label00.setText("")
        self.slaveIP.setText("")
        self.slavePort.setText("")
        self.serverTimeout.setText("")
        self.serverLogin.setText("")
        self.serverPassword.setText("")

        try:
            self.slaveIP.setEnabled(True)
            self.slavePort.setEnabled(True)
            index_list = [i.data() for i in self.treeView.selectedIndexes()]
            s = index_list[0].split(':')
            self.srvName.setText(s[1])
            self.label00.setText(s[0])

            connDb = sqlite3.connect(self.dbPath)
            cursor = connDb.cursor()
            cursor.execute(
                "select ip, port,timeout,login,password from  servers where id = "
                + s[0])
            dt = cursor.fetchone()
            self.slaveIP.setText(dt[0])
            self.slavePort.setText(dt[1])
            self.serverTimeout.setText(dt[2])
            self.serverLogin.setText(dt[3])
            self.serverPassword.setText(dt[4])
            self.truePanel()

        except Exception as e:
            index_list = [i.data() for i in self.treeView.selectedIndexes()]
            self.srvName.setText(index_list[0])
            self.slaveIP.setEnabled(False)
            self.slavePort.setEnabled(False)
            print(e)

    def addNewServer(self):
        sender = self.sender()
        self.statusBar().showMessage('Добавление нового сервера')
        self.model.appendRow(QStandardItem("Новый Сервер "))

    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Сообщение',
                                     "Вы уверены, что хотите выйти?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def resizeEvent(self, event):
        self.h = self.frameGeometry().height()
        self.treeView.setGeometry(QtCore.QRect(0, 60, 350, self.h - 120))
        self.treeTable.setGeometry(
            QtCore.QRect(10, 50, self.w - 270, self.h - 320))
        self.frameTable.setGeometry(
            QtCore.QRect(350, 210, self.w - 250, self.h - 260))

    def sqlLoad(self):
        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

    def saveServer(self):
        self.treeTable.clear()

        connDb = sqlite3.connect(self.dbPath)
        cursor = connDb.cursor()
        if (len(self.label00.text()) > 0 and int(self.label00.text()) > 0):
            if (len(self.srvName.text()) > 1):
                connDb.execute("update servers set name ='" +
                               self.srvName.text() + "', ip = '" +
                               self.slaveIP.text() + "', port = '" +
                               self.slavePort.text() + "'\
                 ,timeout='" + self.serverTimeout.text() + "', login='******',password='******' where id = " +
                               self.label00.text() + " ")
                connDb.commit()
                cursor.execute(
                    "select ip,port,timeout,login,password from  servers where id = "
                    + self.label00.text())
                dt = cursor.fetchone()
                self.slaveIP.setText(dt[0])
                self.slavePort.setText(dt[1])
                self.serverTimeout.setText(dt[2])
                self.serverLogin.setText(dt[3])
                self.serverPassword.setText(dt[4])

                self.truePanel()
        else:
            try:
                if (len(self.srvName.text()) > 1):
                    cursor.execute(
                        "INSERT INTO servers(name,valid)  VALUES( '" +
                        self.srvName.text() + "',1)")

                    connDb.commit()
            except Exception as e:
                #print(e)
                pass

        self.model.clear()
        self.model.setHorizontalHeaderLabels(self.header)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

    #  self.truePanel()

    def saveScr(self):

        try:
            pathFolder = self.appPath
            if (os.name == 'nt'):
                slash = '\\'
                bat = '.bat'
                rem = 'rem '
                command = 'start '
            else:
                slash = '/'
                bat = '.sh'
                rem = '# '
                command = ''

            f = open(
                pathFolder + 'scr' + slash + 'web_' + self.label00.text() +
                bat, 'w')
            print(pathFolder + 'scr' + slash + 'web_' + self.label00.text() +
                  bat)
            f.write(rem + 'Скрипт создан в программе \'' + self.versionPr +
                    '\'\n')
            f.write(rem + 'Сервер Web \'' + self.srvName.text() + '\'\n')
            f.write(rem + 'Http адрес \'' + self.slaveIP.text() + '\'\n')
            f.write(rem + 'Http порт \'' + self.slavePort.text() + '\'\n')

            f.write(command + sys.executable + ' ' + pathFolder + 'source' +
                    slash + 'websrv.py ' + self.label00.text() + ' ' +
                    pathFolder + 'db' + slash + 'webDb.db')
            f.close()
            ret = pathFolder + '' + slash + 'scr' + slash + 'web_' + self.label00.text(
            ) + bat

        except Exception as e:
            print(e)

        return ret

    def runScr(self):

        if (os.name == 'nt'):
            os.system(self.saveScr())
        else:
            os.system('chmod 777 ' + self.saveScr())
            os.system('xfce4-terminal --command=\'sudo ' + self.saveScr() +
                      '\'')

    def runConf(self):
        if (os.name == 'nt'):

            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            os.system(sys.executable + " " + self.appPath + "srvconf.py")

        else:
            self.appPath = os.path.abspath(sys.argv[0]).replace(
                os.path.basename(__file__), '')
            os.system(sys.executable + " " + self.appPath + "srvconf.py")

    def delServer(self):
        self.treeTable.clear()
        self.treeTable.setRowCount(0)
        connDb = sqlite3.connect(self.dbPath)

        if (len(self.label00.text()) > 0 and int(self.label00.text()) > 0):
            if (len(self.srvName.text()) > 1):
                connDb.execute("update servers set valid=0 where id = " +
                               self.label00.text() + " ")
                connDb.commit()

        self.model.clear()
        self.model.setHorizontalHeaderLabels(self.header)
        cursor = connDb.cursor()
        for row in cursor.execute(
                'SELECT name, id FROM servers where valid=1 ORDER BY id'):
            self.model.appendRow(QStandardItem(str(row[1]) + ":" + row[0]))

        self.slaveIP.setText("")
        self.slavePort.setText("")
        # self.label3.setText("")
        self.srvName.setText("")
예제 #12
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
예제 #13
0
class TapeWidget(QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)

        self._main_layout    = QVBoxLayout(self)
        self._search_box     = QLineEdit(self)
        self._button_layout  = QHBoxLayout()

        self._view = QTreeView()
        self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self._view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._view.setHeaderHidden(True)

        self._add_note_button = QPushButton(self)
        self._add_note_button.setText("New note")

        self._add_child_button = QPushButton(self)
        self._add_child_button.setText("New child")

        self._add_sibling_button = QPushButton(self)
        self._add_sibling_button.setText("New sibling")

        self._delete_note_button = QPushButton(self)
        self._delete_note_button.setText("Delete note")

        self._button_layout.addWidget(self._add_note_button)
        self._button_layout.addWidget(self._add_sibling_button)
        self._button_layout.addWidget(self._add_child_button)
        self._button_layout.addWidget(self._delete_note_button)
        self._button_layout.addStretch()

        self._main_layout.addWidget(self._search_box)
        self._main_layout.addLayout(self._button_layout)
        self._main_layout.addWidget(self._view)

        self._tape_filter_proxy_model = TapeFilterProxyModel()
        self._note_delegate           = NoteDelegate()

        self._tape_model = QStandardItemModel()
        self.set_model(self._tape_model)
        self._view.setItemDelegate(self._note_delegate)
        self._view.setModel(self._tape_filter_proxy_model)

        self._add_note_button.clicked.connect(lambda checked: self.add_and_focus_note())
        self._add_sibling_button.clicked.connect(self._new_sibling_handler)
        self._add_child_button.clicked.connect(self._new_child_handler)
        self._delete_note_button.clicked.connect(self.delete_selected_notes)
        self._search_box.textChanged.connect(self._tape_filter_proxy_model.setFilterFixedString)

    def model(self):
        """ Returns the model that contains all notes managed by the tape.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_model

    def proxy_model(self):
        """ Returns the model that contains notes matching current filter.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_filter_proxy_model

    def set_model(self, model):
        assert (
            len(set([item_to_id(item) for item in all_items(model) if item_to_id(item) != None])) ==
            len(    [item_to_id(item) for item in all_items(model) if item_to_id(item) != None])
        )

        # NOTE: If there's an exception in setSourceModel(), we can hope that the source model
        # remains unchanged. That's why we assing to _tape_model only if that instruction succeeds.
        self._tape_filter_proxy_model.setSourceModel(model)
        self._tape_model = model

    def notes(self):
        return all_notes(self._tape_model)

    def assign_ids(self):
        assign_note_ids(self._tape_model)

    def create_empty_note(self):
        return Note(
            body       = "",
            tags       = [],
            created_at = datetime.utcnow()
        )

    def add_note(self, note = None, parent_index = None):
        # NOTE: Remember to use indexes from _tape_model, not _tape_filter_proxy_model here
        assert parent_index == None or self._tape_model.itemFromIndex(parent_index) != None and parent_index.isValid()

        root_item = self._tape_model.invisibleRootItem()
        if parent_index == None:
            parent_item = root_item
        else:
            parent_item = self._tape_model.itemFromIndex(parent_index)

        if note != None:
            assert note not in self.notes()
        else:
            note = self.create_empty_note()

        item = QStandardItem()
        set_item_note(item, note)
        parent_item.appendRow(item)

    def add_and_focus_note(self, parent_proxy_index = None):
        if parent_proxy_index != None:
            parent_index = self._tape_filter_proxy_model.mapToSource(parent_proxy_index)
        else:
            parent_index = None

        self.add_note(parent_index = parent_index)

        if parent_proxy_index != None:
            self._view.expand(parent_proxy_index)

            parent_item = self._tape_model.itemFromIndex(parent_index)
        else:
            parent_item = self._tape_model.invisibleRootItem()

        # NOTE: It's likely that the new note does not match the filter and won't not be present
        # in the proxy model. We want to select it and focus on it so the filter must be cleared.
        # And it must be cleared before taking the index in the proxy because changing the filter
        # may change the set of notes present in the proxy and invalidate the index.
        self.set_filter('')

        new_note_index       = parent_item.child(parent_item.rowCount() - 1).index()
        new_note_proxy_index = self._tape_filter_proxy_model.mapFromSource(new_note_index)

        self.clear_selection()
        self.set_note_selection(new_note_proxy_index, True)
        self._view.scrollTo(new_note_proxy_index)

    def remove_notes(self, indexes):
        remove_items(self._tape_model, indexes)

    def clear(self):
        self._tape_model.clear()

    def set_filter(self, text):
        # NOTE: This triggers textChanged() signal which applies the filter
        self._search_box.setText(text)

    def get_filter(self):
        return self._search_box.text()

    def selected_proxy_indexes(self):
        return self._view.selectedIndexes()

    def selected_indexes(self):
        return [self._tape_filter_proxy_model.mapToSource(proxy_index) for proxy_index in self.selected_proxy_indexes()]

    def set_note_selection(self, proxy_index, select):
        assert proxy_index != None and proxy_index.isValid()
        assert self._tape_model.itemFromIndex(self._tape_filter_proxy_model.mapToSource(proxy_index)) != None

        self._view.selectionModel().select(
            QItemSelection(proxy_index, proxy_index),
            QItemSelectionModel.Select if select else QItemSelectionModel.Deselect
        )

    def clear_selection(self):
        self._view.selectionModel().clear()

    def delete_selected_notes(self):
        self.remove_notes(self.selected_indexes())

    def _new_sibling_handler(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) > 1:
            self.clear_selection()
            selected_proxy_indexes = []

        if len(selected_proxy_indexes) == 0 or selected_proxy_indexes[0].parent() == QModelIndex():
            self.add_and_focus_note()
        else:
            self.add_and_focus_note(selected_proxy_indexes[0].parent())

    def add_child_to_selected_element(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) != 1:
            return False
        else:
            self.add_and_focus_note(selected_proxy_indexes[0])
            return True

    def _new_child_handler(self):
        added = self.add_child_to_selected_element()
        if not added:
            QMessageBox.warning(self, "Can't add note", "To be able to add a new child note select exactly one parent")
예제 #14
0
class TreeView(QWidget):
    def __init__(self, table, parent):
        QWidget.__init__(self, parent)
        self.window = parent
        self.tree = QTreeView(self)
        indent = self.tree.indentation()
        self.tree.setIndentation(indent / 2)

        self.model = DataModel(table)
        self.sorter = sorter = FilterModel(self)
        sorter.setSourceModel(self.model)
        self.tree.setModel(sorter)
        for col in range(3, 9):
            self.tree.setItemDelegateForColumn(col, PercentDelegate(self))
        self.tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.header().customContextMenuRequested.connect(
            self._on_header_menu)
        self.tree.setSortingEnabled(True)
        self.tree.setAutoExpandDelay(0)
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self.tree.expand(self.sorter.index(0, 0))
        #self.tree.expandAll()

        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self._on_tree_menu)

        searchbox = QHBoxLayout()
        self.search = QLineEdit(self)

        searchbox.addWidget(self.search)

        self.search_type = QComboBox(self)
        self.search_type.addItem("Contains", SEARCH_CONTAINS)
        self.search_type.addItem("Exact", SEARCH_EXACT)
        self.search_type.addItem("Reg.Exp", SEARCH_REGEXP)
        searchbox.addWidget(self.search_type)

        btn = QPushButton("&Search", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search)

        btn = QPushButton("&Next", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search_next)

        filterbox = QHBoxLayout()

        label = QLabel("Time Individual", self)
        filterbox.addWidget(label)
        self.individual_time = QSpinBox(self)
        self.individual_time.setMinimum(0)
        self.individual_time.setMaximum(100)
        self.individual_time.setSuffix(" %")
        filterbox.addWidget(self.individual_time)

        label = QLabel("Alloc Individual", self)
        filterbox.addWidget(label)
        self.individual_alloc = QSpinBox(self)
        self.individual_alloc.setMinimum(0)
        self.individual_alloc.setMaximum(100)
        self.individual_alloc.setSuffix(" %")
        filterbox.addWidget(self.individual_alloc)

        label = QLabel("Time Inherited", self)
        filterbox.addWidget(label)
        self.inherited_time = QSpinBox(self)
        self.inherited_time.setMinimum(0)
        self.inherited_time.setMaximum(100)
        self.inherited_time.setSuffix(" %")
        filterbox.addWidget(self.inherited_time)

        label = QLabel("Alloc Inherited", self)
        filterbox.addWidget(label)
        self.inherited_alloc = QSpinBox(self)
        self.inherited_alloc.setMinimum(0)
        self.inherited_alloc.setMaximum(100)
        self.inherited_alloc.setSuffix(" %")
        filterbox.addWidget(self.inherited_alloc)

        btn = QPushButton("&Filter", self)
        btn.clicked.connect(self._on_filter)
        filterbox.addWidget(btn)
        btn = QPushButton("&Reset", self)
        filterbox.addWidget(btn)
        btn.clicked.connect(self._on_reset_filter)

        vbox = QVBoxLayout()
        vbox.addLayout(searchbox)
        vbox.addLayout(filterbox)
        vbox.addWidget(self.tree)
        self.setLayout(vbox)

        self._search_idxs = None
        self._search_idx_no = 0

    def _expand_to(self, idx):
        idxs = [idx]
        parent = idx
        while parent and parent.isValid():
            parent = self.sorter.parent(parent)
            idxs.append(parent)
        #print(idxs)
        for idx in reversed(idxs[:-1]):
            data = self.sorter.data(idx, QtCore.Qt.DisplayRole)
            #print(data)
            self.tree.expand(idx)

    def _on_search(self):
        text = self.search.text()
        selected = self.tree.selectedIndexes()
        #         if selected:
        #             start = selected[0]
        #         else:
        start = self.sorter.index(0, NAME_COLUMN)
        search_type = self.search_type.currentData()
        if search_type == SEARCH_EXACT:
            method = QtCore.Qt.MatchFixedString
        elif search_type == SEARCH_CONTAINS:
            method = QtCore.Qt.MatchContains
        else:
            method = QtCore.Qt.MatchRegExp

        self._search_idxs = idxs = self.sorter.search(start, text, search_type)
        if idxs:
            self.window.statusBar().showMessage(
                "Found: {} occurence(s)".format(len(idxs)))
            self._search_idx_no = 0
            idx = idxs[0]
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("Not found")

    def _locate(self, idx):
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self._expand_to(idx)
        self.tree.setCurrentIndex(idx)
        #self.tree.selectionModel().select(idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Current | QItemSelectionModel.Rows)
        #self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)

    def _on_search_next(self):
        if self._search_idxs:
            n = len(self._search_idxs)
            self._search_idx_no = (self._search_idx_no + 1) % n
            idx = self._search_idxs[self._search_idx_no]
            self.window.statusBar().showMessage("Occurence {} of {}".format(
                self._search_idx_no, n))
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("No search results")

    def _on_filter(self):
        self.sorter.setFilter(self.search.text(), self.individual_time.value(),
                              self.individual_alloc.value(),
                              self.inherited_time.value(),
                              self.inherited_alloc.value())

    def _on_reset_filter(self):
        self.sorter.reset()

    def _on_header_menu(self, pos):
        menu = make_header_menu(self.tree)
        menu.exec_(self.mapToGlobal(pos))

    def _on_tree_menu(self, pos):
        index = self.tree.indexAt(pos)
        #print("index: {}".format(index))
        if index.isValid():
            record = self.sorter.data(index, QtCore.Qt.UserRole + 1)
            #print("okay?..")
            #print("context: {}".format(record))
            menu = self.window.make_item_menu(self.model, record)
            menu.exec_(self.tree.viewport().mapToGlobal(pos))
예제 #15
0
class RedditScraperWindow(QWidget):
    """The main window of the program."""

    ########### Setup ##################################
    def __init__(self):
        super().__init__()

        self.read_user_config()
        self.load_assets()
        self.initialize_ui()

    def load_assets(self):
        script_folder = os.path.dirname(
            os.path.dirname(os.path.abspath(__file__)))
        asset_folder = os.path.join(script_folder, "assets")
        if os.path.isdir(asset_folder):
            self.download_icon = QIcon(
                os.path.join(asset_folder, 'download_icon.png'))
            self.stop_icon = QIcon(os.path.join(asset_folder, 'stop_icon.png'))
            self.reddit_icon = QIcon(
                os.path.join(asset_folder, 'reddit_icon.png'))
        else:
            self.download_icon = QIcon(
                os.path.join("assets", 'download_icon.png'))
            self.stop_icon = QIcon(os.path.join("assets", 'stop_icon.png'))
            self.reddit_icon = QIcon(os.path.join("assets", 'reddit_icon.png'))

    def initialize_ui(self):
        """sets up the user interface, connects all the signals and shows the window. """

        self.init_components()
        self.read_settings_config()
        self.connect_signals()

        self.setGeometry(100, 100, 1000, 800)
        self.setWindowTitle('Reddit Image Scraper')
        self.setWindowIcon(self.reddit_icon)

        self.show()

    def init_components(self):
        """initializes all components and sets up the layout"""
        internalWidgetInput = QWidget()
        internalWidgetTree = QWidget()

        ############ define components ####################
        self.subredditInput = QComboBox()
        self.subredditInput.setEditable(True)
        self.subredditInput.addItems(self.get_downloaded_subreddits())

        self.numInput = QLineEdit()
        self.onlyInt = QIntValidator()
        self.numInput.setValidator(self.onlyInt)
        subredditLabel = QLabel('subreddit')
        numLabel = QLabel('number of images')
        self.dirLabel = QLabel('choose a directory')
        scale_label = QLabel("Scale images?")
        self.imgView = QLabel()

        self.outputText = QTextEdit('')
        self.outputText.setReadOnly(True)

        self.scale_cb = QCheckBox()

        self.sortingCb = QComboBox()
        self.sortingCb.addItems([
            "Hot", "Top all time", "Top this month", "Top past year", "New",
            "Controversial"
        ])
        sortingLabel = QLabel('sorting method')
        self.runButton = QPushButton('Download')
        self.runButton.setIcon(self.download_icon)
        self.chooseDirButton = QPushButton('Save dir')
        self.stopButton = QPushButton('Stop')
        self.stopButton.setIcon(self.stop_icon)

        self.fileModel = QFileSystemModel()
        self.tree = QTreeView()

        self.tree.setModel(self.fileModel)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)

        ############## Menu stuff ###################
        menu_bar = QMenuBar()
        file_menu = menu_bar.addMenu('File')
        help_menu = menu_bar.addMenu('Help')

        self.exit_action = QAction('Exit', self)

        file_menu.addAction(self.exit_action)
        self.help_action = QAction('Help', self)
        help_menu.addAction(self.help_action)
        menu_bar.setFixedHeight(30)

        ############# Setup the grid layout###############################
        grid = QGridLayout()
        # grid.addWidget(menu_bar, 1, 0, 1, 4)
        grid.setSpacing(4)
        grid.addWidget(subredditLabel, 1, 0)
        grid.addWidget(self.subredditInput, 1, 1)
        grid.addWidget(numLabel, 2, 0)
        grid.addWidget(self.numInput, 2, 1)
        grid.addWidget(sortingLabel, 3, 0)
        grid.addWidget(self.sortingCb, 3, 1)

        grid.addWidget(self.chooseDirButton, 4, 0)
        grid.addWidget(self.dirLabel, 4, 1)
        grid.addWidget(self.stopButton, 5, 0)
        grid.addWidget(self.runButton, 5, 1)
        grid.addWidget(self.outputText, 7, 0, 7, 2)
        # grid.addWidget(self.tree,1,2, 11,7)

        grid.addWidget(scale_label, 6, 0)
        grid.addWidget(self.scale_cb, 6, 1)

        hboxTree = QVBoxLayout()
        hboxTree.addWidget(self.tree)

        #the image viewer, setting how it behaves under resizing.
        self.imgView.setSizePolicy(
            QSizePolicy(QSizePolicy.MinimumExpanding,
                        QSizePolicy.MinimumExpanding))
        self.imgView.setMaximumHeight(MAX_IMAGE_HEIGHT)

        self.imgView.setAlignment(Qt.AlignmentFlag.AlignCenter)

        img_scroll_area = QScrollArea()
        img_scroll_area.setMinimumHeight(MAX_IMAGE_HEIGHT)
        img_scroll_area.setMinimumWidth(MAX_IMAGE_HEIGHT)
        img_scroll_area.setWidget(self.imgView)

        internalWidgetInput.setLayout(grid)
        # internalWidgetInput.setMinimumWidth(300)
        internalWidgetInput.setFixedWidth(300)
        internalWidgetInput.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding))

        internalWidgetTree.setLayout(hboxTree)
        internalWidgetTree.setFixedWidth(360)
        internalWidgetTree.setSizePolicy(
            QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding))

        #construct layout of main window.
        hbox = QHBoxLayout()
        hbox.setSpacing(0)
        hbox.setContentsMargins(0, 0, 0, 0)
        hbox.setMenuBar(menu_bar)
        hbox.addWidget(internalWidgetInput)
        hbox.addWidget(internalWidgetTree)
        hbox.addWidget(img_scroll_area)
        self.setLayout(hbox)

    def connect_signals(self):
        """connects all the signals to the right functions"""
        self.chooseDirButton.clicked.connect(self.show_dir_dialog)
        self.runButton.clicked.connect(self.run_download_threaded)
        self.tree.clicked.connect(self.on_treeView_clicked)
        self.stopButton.clicked.connect(self.stop_download)
        self.scale_cb.clicked.connect(self.refresh_image)
        self.exit_action.triggered.connect(exit)

        #self.edit_login_action.triggered.connect(self.edit_login_info)
        self.help_action.triggered.connect(self.show_help)

        self.tree.selectionModel().selectionChanged.connect(
            self.on_selection_change)

    def read_user_config(self):
        """reads in the users username and password from the config file, or if there is no config file,
        shows an input dialog.
        Also tests so that only valid login information gets saved to the config file. """
        config = configparser.ConfigParser()
        self.redditScraper = redditScraper()
        if os.path.exists('redditScraper.ini'):
            config.read('redditScraper.ini')

            if 'DIR' in config:
                self.folder = config['DIR']['root_folder']

        else:
            config['REDDIT'] = {
                'subreddit': "wallpapers",
                'num': 10,
                'sorting': "Hot",
                'downloaded_subreddits': ""
            }

            with open('redditScraper.ini', 'w') as configfile:
                config.write(configfile)

        self.config = config

    def read_settings_config(self):
        """reads the saved settings from the config file, if they're there."""
        if 'DIR' in self.config:
            self.folder = self.config['DIR']['root_folder']
            idx = self.fileModel.setRootPath(str(self.folder))
            self.tree.setRootIndex(idx)
            self.dirLabel.setText(self.folder)

        if 'REDDIT' in self.config:
            self.subredditInput.setCurrentText(
                self.config['REDDIT']['subreddit'])
            self.numInput.setText(self.config['REDDIT']['num'])
            self.sortingCb.setCurrentText(self.config['REDDIT']['sorting'])

    ################### Actions: ##################
    @pyqtSlot(QModelIndex)
    def on_treeView_clicked(self, index):
        """triggers when the user clicks on a file item shown in the treeView, and shows that file in the picture viewer."""
        index = self.fileModel.index(index.row(), 0, index.parent())
        self.show_image(index)

    def on_selection_change(self, selected: QItemSelection,
                            deselected: QItemSelection):
        """ Triggers when the selected item in the treeview changes, and updates the shown picture. """
        self.refresh_image()

    def refresh_image(self):
        selected_image_index = self.tree.selectedIndexes()[0]
        self.show_image(selected_image_index)

    def show_image(self, index: QModelIndex):
        filePath = self.fileModel.filePath(index)
        if os.path.isfile(filePath) and filePath.split(".")[-1] in [
                "jpg", "gif", "png", "jpeg"
        ]:
            pixmap = QPixmap(filePath)
            if self.scale_cb.isChecked():
                self.imgView.setFixedHeight(MAX_IMAGE_HEIGHT)
                scaled_img = pixmap.scaledToHeight(MAX_IMAGE_HEIGHT)
                self.imgView.setFixedWidth(scaled_img.width())
                self.imgView.setPixmap(scaled_img)
            else:
                self.imgView.setFixedHeight(pixmap.height())
                self.imgView.setFixedWidth(pixmap.width())
                self.imgView.setPixmap(pixmap)

    def show_dir_dialog(self):
        """lets the user select the root folder, and saves the choice to the config file."""

        self.folder = QFileDialog.getExistingDirectory(
            self, 'Choose base directory', '/home')
        self.dirLabel.setText(self.folder)

        self.config['DIR'] = {'root_folder': self.folder}
        with open('redditScraper.ini', 'w') as configfile:
            self.config.write(configfile)

        idx = self.fileModel.setRootPath(self.folder)
        self.tree.setRootIndex(idx)

        return self.folder

    @pyqtSlot(str)
    def update_output_text(self, message: str):
        """updates the output text area, to show progress on downloads."""
        self.outputText.setText(message + self.outputText.toPlainText())

    def save_subreddit(self, subreddit: str, num: int, sorting: str):
        """helper function to save the current settings to the config file."""
        downloaded_subreddits = self.get_downloaded_subreddits()
        if subreddit not in downloaded_subreddits:
            downloaded_subreddits.append(subreddit)
            self.subredditInput.addItem(subreddit)

        downloaded_subreddits = ','.join(downloaded_subreddits)
        self.config['REDDIT'] = {
            'subreddit': subreddit,
            'num': str(num),
            'sorting': sorting,
            'downloaded_subreddits': downloaded_subreddits
        }

        with open('redditScraper.ini', 'w') as configfile:
            self.config.write(configfile)

    def get_downloaded_subreddits(self) -> list:
        if 'downloaded_subreddits' in self.config['REDDIT'].keys():
            subreddits = self.config['REDDIT']['downloaded_subreddits'].split(
                ',')
            return subreddits
        return []

    def run_download_threaded(self):
        """downloads the pictures. Runs in a QThread, so that the program does not freeze.
        Also checks whether the specified subreddit exists. """
        subreddit = self.subredditInput.currentText()
        num = int(self.numInput.text())
        sorting = self.sortingCb.currentText()

        if self.redditScraper.sub_exists(subreddit):

            if not hasattr(self, "folder"):
                msgBox = QMessageBox()
                msgBox.setText('You need to set a download folder!')
                msgBox.setWindowTitle("Pick a download folder")
                msgBox.exec_()
                return

            self.save_subreddit(subreddit, num, sorting)
            self.get_thread = RedditDownloadThread(
                self.redditScraper, subreddit, num,
                num * LOOKUP_LIMIT_MULTIPLIER, sorting, self.folder)
            self.get_thread.changeText.connect(self.update_output_text)
            self.get_thread.start()
        else:
            msgBox = QMessageBox()
            msgBox.setText('That subreddit does not exist, please try again')
            msgBox.setWindowTitle("Invalid subreddit")
            msgBox.exec_()

        pass

    def stop_download(self):
        """Stops the download thread and prints a message to the output."""
        try:
            if self.get_thread.isRunning():
                self.get_thread.terminate()
                self.outputText.setText(self.outputText.toPlainText() +
                                        ' Aborted!\n')
        except Exception:
            pass

    ############### Menu actions: ###############

    def show_help(self):
        msgBox = QMessageBox()

        msgBox.setWindowIcon(self.reddit_icon)
        msgBox.setText(
            'This program downloads images posted to reddit.com, or more specifically, to subreddits'
            +
            '(i.e. sub-forums). To use it, one needs a valid reddit account, which one can sign up for '
            +
            'on reddit.com. One also needs to know some names of subreddits. \n Some suggestions for subreddits: '
            + 'wallpapers , earthporn , nature , and pics . \n \n ' +
            "This program will download up to the specified number of images. It can handle png, jpg and gif,"
            +
            "so links to gyf-files, videos or anything else will be ignored. Therefore the number of images actually "
            +
            "downloaded will usually be less than the specified number. So if you want a lot of images, just put"
            +
            " a large limit.\n The images will be placed in a subfolder of the chosen base folder, named after the subreddit. This folder will be created if it does not exist.\n \n "
            +
            "To view the images, click on them in the tree-view, and they will appear on the right"
        )
        msgBox.setWindowTitle("Help")
        msgBox.exec_()
예제 #16
0
class App(QWidget):
    ITEM, ITEM2 = range(2)

    def __init__(self):
        super(App, self).__init__()
        self.title = 'Example GUI'
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)

        # Edit Box
        self.textbox = QLineEdit(self)

        # Push button
        self.button = QPushButton('see list', self)
        self.button.setToolTip('Show List of items')
        self.button.clicked.connect(self.on_click)

        # Search and button Layout grouping
        self.horizontalGroupBox = QGroupBox()
        layout = QHBoxLayout()
        layout.addWidget(self.textbox)
        layout.addWidget(self.button)
        self.horizontalGroupBox.setLayout(layout)

        # Tree View
        self.dataGroupBox = QGroupBox("Android Directory")
        self.dataView = QTreeView()
        self.dataView.setRootIsDecorated(False)
        self.dataView.setAlternatingRowColors(True)
        # self.dataView.setDisabled(True)
        self.dataView.setSortingEnabled(True)
        self.dataView.clicked.connect(self.on_click)

        dataLayout = QHBoxLayout()
        dataLayout.addWidget(self.dataView)
        self.dataGroupBox.setLayout(dataLayout)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.horizontalGroupBox)
        mainLayout.addWidget(self.dataGroupBox)
        self.setLayout(mainLayout)

        # Example for Set the model
        process = subprocess.Popen(["adb shell ls"],
                                   shell=True,
                                   stdout=subprocess.PIPE)
        stdout = process.communicate()[0]
        dirList = [i for i in format(stdout.decode("utf-8")).split("\n")]

        model = self.createListModel(self)
        self.dataView.setModel(model)
        for i in dirList:
            self.addItem(model, str(i))

        self.show()

    # Model
    def createListModel(self, parent):
        model = QStandardItemModel(0, 1, parent)
        model.setHeaderData(self.ITEM, Qt.Horizontal, "DIRectory")
        return model

    # Add data
    def addItem(self, model, item):
        model.insertRow(0)
        model.setData(model.index(0, self.ITEM), item)

    @pyqtSlot()
    def on_click(self):
        path = [str(data.data()) for data in self.dataView.selectedIndexes()]
        print(self.dataView.selectedIndexes()[0].row())
        self.textbox.setText(path[0])
        self.textbox.setFocus()
예제 #17
0
class DataWindow(QMdiSubWindow):
    def __init__(self, app, OAuth_token, parent):
        super().__init__()

        self.app = app
        self.token = OAuth_token
        self.parent = parent

        self.__threads = []

        self.initUI()

    def initUI(self):

        # Format the window
        self.format_window()

        # Create a horizontal layout to hold the widgets
        hbox = QHBoxLayout()

        # Add the widgets
        hbox.addWidget(self.set_directory_btn())
        hbox.addWidget(self.create_file_browser())
        hbox.addWidget(self.add_to_selection_btn())

        # Create a central widget for the local data window
        window_widget = QWidget()
        # Add the vertical box layout
        window_widget.setLayout(hbox)
        # Set the projects window widget
        self.setWidget(window_widget)

    def format_window(self):
        """
        Form the local data window
        :return:
        """
        # Gets the QRect of the main window
        geom = self.parent.geometry()
        # Gets the Qrect of the sections window
        section_geom = self.parent.section_geom

        # Define geometries for the projects window
        x0 = section_geom.x() + section_geom.width()
        y0 = section_geom.y()
        w = geom.width() - x0
        h = ((geom.height() - y0) / 3)
        self.setGeometry(x0, y0, w, h)
        # Remove frame from projects window
        self.setWindowFlags(Qt.FramelessWindowHint)

    #####
    # Window Widgets
    #####

    def set_directory_btn(self):
        """
        Creates a QPushButton that can be used to set the current root directory
        :return: QPushButton
        """

        btn = QPushButton()
        btn.setIcon(
            QIcon(os.path.normpath(__file__ + '/../../img/Folder-48.png')))
        press_button(self.app, btn)  # Format button
        btn.setToolTip("Select local directory")
        btn.setToolTipDuration(1)

        btn.pressed.connect(self.on_set_directory_pressed)

        return btn

    def create_file_browser(self):
        """
        Creates a QTreeView with a QFileSystemModel that is used as a file browser
        :return: QTreeview
        """

        self.browser = QTreeView()

        # Set the model of the QTreeView
        self.model = QFileSystemModel()
        home_dir = os.path.expanduser("~")  # Define the initial root directory
        self.model.setRootPath(home_dir)
        self.browser.setModel(self.model)

        # Resize the first column
        self.browser.setColumnWidth(0, self.geometry().width() / 3)

        # Control how selection of items works
        #self.browser.setSelectionBehavior(QAbstractItemView.SelectItems)  # Allow for only single item selection
        self.browser.setSelectionMode(
            QAbstractItemView.ExtendedSelection
        )  # Alow for multiple rows to be selected

        return self.browser

    def add_to_selection_btn(self):
        """
        Creates a QPushButton that can be used to open the metadata window for the selected items in the file browser
        :return: QPushButton
        """
        btn = QPushButton()
        btn.setIcon(
            QIcon(
                os.path.normpath(__file__ +
                                 '/../../img/Insert Row Below-48.png')))
        press_button(self.app, btn)  # Format button
        btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        btn.setToolTip("Add selected items to articles list")
        btn.setToolTipDuration(1)

        btn.pressed.connect(self.on_open_selection_clicked)

        return btn

    #####
    # Widget Actions
    #####

    def on_set_directory_pressed(self):
        """
        Called when the set root directory button is pressed
        :return:
        """
        dir_name = self.user_set_dir()
        self.browser.setRootIndex(self.model.index(dir_name))

    def user_set_dir(self):
        """
        Creates a QFileDialog that prompts the user to choose a root directory
        :return: Sting. directory path
        """
        return str(QFileDialog.getExistingDirectory(self, "Select Directory"))

    def on_open_selection_clicked(self):
        """
        Called when the open selection button is clicked. Either open or closes the metadata window
        :return:
        """
        # Retrieve a set of file paths
        file_paths = self.get_selection_set()

        # Locally reference the Article List Widget
        article_tree = self.parent.data_articles_window.article_tree

        # Prevent the user from editting, searching, etc. while new articles are created.
        article_tree.disable_fields()

        # Create an article creation worker
        worker = ArticleCreationWorker(self.token, self.parent, file_paths)

        # Create the thread.
        load_articles_thread = QThread()
        load_articles_thread.setObjectName('local_articles_thread')
        self.__threads.append((load_articles_thread, worker))

        # Add the worker to the thread
        worker.moveToThread(load_articles_thread)

        # Connect signals from worker
        worker.sig_step.connect(article_tree.add_to_tree)
        worker.sig_step.connect(
            lambda article_id: article_tree.add_to_articles(article_id))
        worker.sig_done.connect(article_tree.enable_fields)
        worker.sig_done.connect(article_tree.update_search_field)
        worker.sig_done.connect(self.parent.data_articles_window.check_edit)
        load_articles_thread.started.connect(worker.work)

        # Begin worker thread
        load_articles_thread.start()

    def get_selection_set(self):
        """
        Creates a set of selected item file paths.
        :return:
        """
        # Get a list of selected items from the QTreeview
        items = self.browser.selectedIndexes()

        # Create an empty set to add file paths to
        file_paths = set()
        for item in items:
            # For items that are not directories
            if not self.model.isDir(item):
                file_paths.add(
                    self.model.filePath(item))  # Add the item file path
            else:
                # Combine the current set with a set of files contained within the directory. Does not recursively
                # open contained directories
                contained_files = self.get_child_files(
                    self.model.filePath(item))
                if contained_files is not None:
                    file_paths |= contained_files

        return file_paths

    @staticmethod
    def get_child_files(path):
        """
        given a path to a directory will return a set of file paths contained within. Does not recursively open internal
        directories
        :param path: string. path to directory
        :return: set. Containing file paths
        """
        dir = os.path.normpath(path)
        if os.path.isdir(dir):
            dir_contents = os.listdir(dir)

            file_set = set()
            for item in dir_contents:
                if not os.path.isdir(item):
                    file_set.add(os.path.join(dir, item))
            return file_set
        else:
            return None
예제 #18
0
class ParamEditor(QTabWidget):
    jsonData = {}
    isParseError = False
    error = pyqtSignal(str, str)
    format_data = []

    horizontal_tab = 0
    vertical_tab = 1

    def __init__(self, mode=0):
        super().__init__()
        self.setObjectName("ParamEditor")
        self.json_edit = JSONEditor()
        self.table_edit = QTreeView()
        self.model = QStandardItemModel()
        self.table_edit.setAlternatingRowColors(True)

        self.table_edit.setModel(self.model)

        self.addTab(self.table_edit, "UI")
        self.addTab(self.json_edit, "JSON")

        self.table_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table_edit.customContextMenuRequested.connect(self.open_menu)
        self.model.itemChanged.connect(self.data_change)
        self.currentChanged.connect(self.tab_selected)

        if mode == self.vertical_tab:
            self.setTabPosition(QTabWidget.West)

    def set_json_data(self, param):
        self.jsonData = param
        self.update()

    def update(self):
        self.load_param_2_table(self.jsonData)
        self.load_param_2_json_editor(self.jsonData)

    def load_param_2_table(self, param):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['key', 'value'])
        header = self.table_edit.header()
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
        self.create_param_ui(self.model.invisibleRootItem(), param, 0, "", [])
        self.table_edit.expandAll()

    def create_param_ui(self,
                        parent,
                        obj,
                        level,
                        parent_name="",
                        item_link=None):
        if item_link is None:
            item_link = []
        if isinstance(obj, list):
            for i in range(len(obj)):
                if isinstance(obj[i], dict) or isinstance(obj[i], list):
                    item = QStandardItem(str(i) + ": ")
                    item.setEditable(False)
                    data = {
                        "key": ":" + str(i),
                        "parent": parent_name,
                        'level': level,
                        'link': item_link
                    }
                    item.setData(data)
                    parent.appendRow([item])
                    link = item_link.copy()
                    link.append(":" + str(i))
                    self.create_param_ui(item, obj[i], level, parent_name,
                                         link)
                else:
                    item = QStandardItem(str(i) + ": ")
                    item.setEditable(False)
                    value = QStandardItem(str(obj[i]))
                    value.setEditable(True)
                    link = item_link.copy()
                    link.append(":" + str(i))
                    data = {
                        "key": ":" + str(i),
                        "parent": parent_name,
                        'level': level,
                        'link': link
                    }
                    item.setData(data)
                    value.setData(data)
                    parent.appendRow([item, value])
        elif isinstance(obj, dict):
            for key, value in obj.items():
                if isinstance(value, dict) or isinstance(value, list):
                    item = QStandardItem(key)
                    item.setEditable(False)
                    data = {
                        "key": key,
                        "parent": parent_name,
                        'level': level,
                        "link": item_link
                    }
                    item.setData(data)
                    parent.appendRow([item])
                    link = item_link.copy()
                    link.append(key)
                    self.create_param_ui(item, value, level + 1, key, link)
                else:
                    self.add_object(parent, obj[key], level, key, item_link)
        else:
            self.add_object(parent, obj, level, parent_name, item_link)

    def add_object(self, parent, obj, level, parent_name="", item_link=None):
        if item_link is None:
            item_link = []
        item = QStandardItem(parent_name)
        item.setEditable(False)
        value = QStandardItem(str(obj))
        value.setEditable(True)
        data = {
            "key": parent_name,
            "parent": "",
            'level': level,
            'link': item_link
        }
        item.setData(data)
        value.setData(data)
        parent.appendRow([item, value])

    def load_param_2_json_editor(self, param):
        self.json_edit.setDocument(color_json(param))

    def sync(self):
        if self.currentIndex() == 0:
            self.load_param_2_json_editor(self.jsonData)
        elif self.currentIndex() == 1:
            self.try_sync_table()

    def try_sync_table(self):
        self.isParseError = False
        str_param = self.json_edit.toPlainText()
        try:
            if str_param == "" or str_param.isspace():
                self.jsonData = {}
            else:
                self.jsonData = json.loads(str_param)
            self.load_param_2_json_editor(self.jsonData)
            self.isParseError = False
        except Exception as ex:
            print(ex)
            self.error.emit("JSON Param", str(ex))
            self.isParseError = True
        finally:
            if self.isParseError:
                self.load_param_2_json_editor(str_param)
            else:
                self.load_param_2_json_editor(self.jsonData)
            self.load_param_2_table(self.jsonData)
        return self.isParseError

    def data_change(self, item: QStandardItem):
        data = item.data()
        if data is not None and "link" in data:
            item_link = data['link']
            link = item_link.copy()
            link.append(data['key'])
            self.update_data(self.jsonData, link, item.text(), 0, len(link))

    def update_data(self, obj, link, new, index, end):
        i = link[index]
        if i.startswith(":"):
            i = int(i.replace(":", ""))
        if index == end - 1:
            obj[i] = new
        else:
            self.update_data(obj[i], link, new, index + 1, end)

    def tab_selected(self, arg=None):
        if arg is not None:
            if arg == 0:
                self.try_sync_table()
            else:
                if not self.isParseError:
                    self.load_param_2_json_editor(self.jsonData)

    def open_menu(self, position):
        indexes = self.table_edit.selectedIndexes()
        level = 0
        data_link = []
        link = []
        item = None
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            if data is not None:
                link = data['link']
                data_link = link.copy()
                data_link.append(data['key'])
                level = 1

        menu = QMenu()
        menu.setStyleSheet(open(get_stylesheet()).read())

        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete key', self)
        delete_action.setStatusTip('Delete')

        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&Add key', self)
        new_action.setStatusTip('Add key')

        menu.addAction(new_action)
        if level == 1:
            menu.addAction(delete_action)

        action = menu.exec_(self.table_edit.viewport().mapToGlobal(position))

        if action == delete_action:
            if data_link is not None and data_link != []:
                self.remove_data(self.jsonData, data_link, 0, len(data_link))
                self.update()
        elif action == new_action:
            if self.is_list(self.jsonData, link, 0, len(link)):
                data_new = self.get_data(self.jsonData, data_link, 0,
                                         len(data_link))
                self.duplicate_data(self.jsonData, link, data_new.copy(), 0,
                                    len(link))
                self.update()
            elif self.is_list(self.jsonData, data_link, 0, len(data_link)):
                data_new = self.get_data(self.jsonData, data_link, 0,
                                         len(data_link))
                self.add_data(self.jsonData, data_link, data_new.copy(), 0,
                              len(data_link))
                self.update()
            else:
                input_name = QInputDialog()
                input_name.setStyleSheet(open(get_stylesheet()).read())
                text, ok = input_name.getText(self, 'New field', 'Field key:')
                if ok:
                    if data_link:
                        self.add_data(self.jsonData, data_link, text, 0,
                                      len(data_link))
                    else:
                        self.jsonData[text] = ""
                    self.update()

    def remove_data(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                del obj[i]
            else:
                self.remove_data(obj[i], link, index + 1, end)

    def is_list(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                return isinstance(obj[i], list)
            else:
                return self.is_list(obj[i], link, index + 1, end)
        else:
            return False

    def get_data(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                return obj[i]
            else:
                return self.get_data(obj[i], link, index + 1, end)
        else:
            return ""

    def add_data(self, obj, link, new, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                if isinstance(obj[i], list):
                    if len(obj[i]) > 1:
                        obj[i].append(obj[i][0])
                    else:
                        obj[new] = ""
                elif isinstance(obj[i], dict) or str(obj[i]) == "":
                    obj[i] = {}
                    obj[i][new] = ""
                else:
                    obj[new] = ""
            else:
                self.add_data(obj[i], link, new, index + 1, end)

    def duplicate_data(self, obj, link, new, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                if isinstance(obj[i], list):
                    obj[i].append(new)
            else:
                self.duplicate_data(obj[i], link, new, index + 1, end)

    def text(self):
        return self.json_edit.toPlainText()

    def set_format_data(self, data):
        self.format_data = data
예제 #19
0
class TwoLines(QWidget):
    def __init__(self):
        super().__init__()
        self.top = QListWidget()
        if MOVIE_UNSEEN:
            self.current_dict = MOVIE_UNSEEN
            self.current_list = MOVIE_UNSEEN.keys()
        else:
            self.current_dict = MOVIE_SEEN
            self.current_list = MOVIE_SEEN.keys()

        self.tabs = QTabWidget()
        self.bottom = QLabel()
        self.tree = QTreeView()

        self.init_ui()

    def init_ui(self):
        hbox = QHBoxLayout(self)

        self.top.addItems(self.current_list)
        self.top.setCurrentRow(0)
        self.top.setContextMenuPolicy(Qt.CustomContextMenu)

        # TAB movie info
        self.data_to_show()
        # TAB ls dir
        self.ls_current_dir()
        # TABS
        self.set_tabs()

        # must be here because of QBasicTimer red msg
        # QBasicTimer can only be used with threads started with QThread
        def changed_item():
            if self.top.currentItem():
                self.data_to_show()
                self.ls_current_dir()

        self.top.currentItemChanged.connect(changed_item)
        self.top.customContextMenuRequested.connect(self.right_click)
        self.tree.doubleClicked.connect(self.clicked_movie)

        # to choose a browser
        # self.labelOnlineHelp.linkActivated.connect(self.link_handler)
        self.bottom.setOpenExternalLinks(True)

        splitter1 = QSplitter(Qt.Vertical)
        splitter1.addWidget(self.top)
        splitter1.addWidget(self.tabs)

        hbox.addWidget(splitter1)
        self.setLayout(hbox)

    def set_tabs(self):
        """
        movie info on one tab
        ls dir on the other tab
        """
        # movie info
        tab_synopsys = QWidget()

        # layout
        synopsys_vbox = QVBoxLayout()

        # ls dir
        tab_ls_dir = QWidget()

        lsdir_vbox = QVBoxLayout()

        # tab one
        synopsys_vbox.addWidget(self.bottom)
        tab_synopsys.setLayout(synopsys_vbox)
        self.tabs.addTab(tab_synopsys, "Movie Info")
        # tab two
        lsdir_vbox.addWidget(self.tree)
        tab_ls_dir.setLayout(lsdir_vbox)
        self.tabs.addTab(tab_ls_dir, "ls dir")

    def data_to_show(self):
        """
        call HtmlTags to build html with a poster and a synopsis
        and put the result on self.bottom
        """
        title = self.top.currentItem().text()
        url = self.current_dict[title][0]
        context = HtmlTags(url, title)
        self.bottom.setText(context.context)

    def ls_current_dir(self):
        path_to_dir = self.current_dict[self.top.currentItem().text()][-1]
        # ls content of the current dirQt.CustomContextMenu
        lsdir = QFileSystemModel()
        lsdir.setRootPath(path_to_dir)
        self.tree.setModel(lsdir)
        self.tree.setRootIndex(lsdir.index(path_to_dir))
        self.tree.setColumnWidth(0, 450)

    def right_click(self):
        RightClickMenu(self.current_dict, self.top)

    def clicked_movie(self):
        item = self.tree.selectedIndexes()[0]
        file_to_play = item.model().filePath(item)
        if file_to_play.endswith(('.avi', 'mp4', '.mkv')):
            call(['/usr/bin/mpv', file_to_play])

    def on_changed(self, text):
        self.lbl.setText(text)
        self.lbl.adjustSize()
예제 #20
0
class ScorePartsWidget(QSplitter):
    def __init__(self, parent):
        super(ScorePartsWidget, self).__init__(parent)
        
        self.typesLabel = QLabel()
        self.typesView = QTreeView(
            selectionMode=QTreeView.ExtendedSelection,
            selectionBehavior=QTreeView.SelectRows,
            animated=True,
            headerHidden=True)
        self.scoreLabel = QLabel()
        self.scoreView = widgets.treewidget.TreeWidget(
            selectionMode=QTreeView.ExtendedSelection,
            selectionBehavior=QTreeView.SelectRows,
            headerHidden=True,
            animated=True,
            dragDropMode=QTreeView.InternalMove)
        self.addButton = QPushButton(icon = icons.get("list-add"))
        self.removeButton = QPushButton(icon = icons.get("list-remove"))
        self.upButton = QToolButton(icon = icons.get("go-up"))
        self.downButton = QToolButton(icon = icons.get("go-down"))
        self.partSettings = QStackedWidget()
        
        w = QWidget()
        self.addWidget(w)
        layout = QVBoxLayout(spacing=0)
        w.setLayout(layout)
        
        layout.addWidget(self.typesLabel)
        layout.addWidget(self.typesView)
        layout.addWidget(self.addButton)
        
        w = QWidget()
        self.addWidget(w)
        layout = QVBoxLayout(spacing=0)
        w.setLayout(layout)
        
        layout.addWidget(self.scoreLabel)
        layout.addWidget(self.scoreView)
        
        box = QHBoxLayout(spacing=0)
        layout.addLayout(box)
        
        box.addWidget(self.removeButton)
        box.addWidget(self.upButton)
        box.addWidget(self.downButton)
        
        self.addWidget(self.partSettings)

        self.typesView.setModel(parts.model())
        app.translateUI(self)
        
        # signal connections
        self.addButton.clicked.connect(self.slotAddButtonClicked)
        self.removeButton.clicked.connect(self.slotRemoveButtonClicked)
        self.typesView.doubleClicked.connect(self.slotDoubleClicked)
        self.scoreView.currentItemChanged.connect(self.slotCurrentItemChanged)
        self.upButton.clicked.connect(self.scoreView.moveSelectedChildrenUp)
        self.downButton.clicked.connect(self.scoreView.moveSelectedChildrenDown)
        
    def translateUI(self):
        bold = "<b>{0}</b>".format
        self.typesLabel.setText(bold(_("Available parts:")))
        self.scoreLabel.setText(bold(_("Score:")))
        self.addButton.setText(_("&Add"))
        self.removeButton.setText(_("&Remove"))
        self.upButton.setToolTip(_("Move up"))
        self.downButton.setToolTip(_("Move down"))

    def slotDoubleClicked(self, index):
        self.addParts([index])
        
    def slotAddButtonClicked(self):
        self.addParts(self.typesView.selectedIndexes())

    def addParts(self, indexes):
        """Adds the parts for the given indexes."""
        # add to current if that is a container type
        currentItem = self.scoreView.currentItem()
        for index in indexes:
            category = index.internalPointer()
            if category:
                part = category.items[index.row()]
                box = QGroupBox(self.partSettings)
                self.partSettings.addWidget(box)
                # determine the parent: current or root
                if currentItem and issubclass(part, currentItem.part.accepts()):
                    parent = currentItem
                    parent.setExpanded(True)
                else:
                    parent = self.scoreView
                item = PartItem(parent, part, box)
    
    def slotCurrentItemChanged(self, item):
        if isinstance(item, PartItem):
            self.partSettings.setCurrentWidget(item.box)
    
    def slotRemoveButtonClicked(self):
        self.scoreView.removeSelectedItems()
       
    def clear(self):
        """Called when the user clicks the clear button on this page."""
        self.scoreView.clear()

    def rootPartItem(self):
        """Returns the invisibleRootItem(), representing the tree of parts in the score view."""
        return self.scoreView.invisibleRootItem()
예제 #21
0
class EditScreen(QWidget):
    def __init__(self, rester: Rester):
        super(EditScreen, self).__init__()
        self.last_item_type = ''

        self.rester = rester
        self.tree = QTreeView()
        layout = QVBoxLayout()
        layout.addWidget(self.tree)
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['Name'])
        self.tree.header().setDefaultSectionSize(200)
        self.tree.setModel(self.model)
        self.data = self.rester.list_request(list(APIEnum))
        self.import_data(self.data)
        self.tree.setSortingEnabled(False)
        self.tree.expandAll()
        self.tree.setSelectionMode(QAbstractItemView.SingleSelection)
        self.tree.selectionModel().selectionChanged.connect(self.item_selected)

        self.init_ui()

    def init_ui(self):
        hbox = QHBoxLayout()

        vbox = QVBoxLayout()
        left_layout = QFormLayout()
        tree_group_box = QGroupBox()
        self.search_field = QLineEdit()
        # self.search_field.editingFinished.connect(self.search_field_changed)
        self.search_field.textEdited.connect(self.search_field_changed)
        left_layout.addRow(QLabel('Search:'), self.search_field)
        tree_group_box.setLayout(left_layout)

        vbox.addWidget(tree_group_box)
        vbox.addWidget(self.tree)

        gb = QGroupBox()
        gb.setLayout(vbox)

        self.splitter = QSplitter()
        self.splitter.addWidget(gb)

        self.ew = EquipmentEdit()
        self.splitter.addWidget(self.ew)

        hbox.addWidget(self.splitter)

        self.setLayout(hbox)

    def import_data(self, data, root=None):

        self.model.setRowCount(0)
        if root is None:
            root = self.model.invisibleRootItem()
        for key, value_list in data.items():
            parent = TreeItem({'name': key}, '', parent=True)
            parent.setEditable(False)
            root.appendRow([parent])
            for value in value_list:
                parent.appendRow([TreeItem(value, key)])

    def search_field_changed(self):
        term = self.search_field.text()
        data = {}
        for key, value_list in self.data.items():
            li = []
            for value in value_list:
                for k in value.keys():
                    if term in k:
                        li.append(value)
            data[key] = li

        self.import_data(data)

    def item_selected(self):
        indexes = self.tree.selectedIndexes()
        selected = indexes[0]

        item = self.model.itemFromIndex(selected)

        if item.is_parent:
            return

        #if item.typ != self.last_item_type:
        self.ew.hide()
        self.ew.destroy()
        self.ew = get_edit_widget(item.typ, self.data)
        self.splitter.addWidget(self.ew)

        self.last_item_type = item.typ

        data_dict = item.get_data_dict()
        self.ew.load_data(data_dict)
예제 #22
0
class AssetUploader(QMainWindow):
    def __init__(self):
        super().__init__()

        # Setup Box interface
        self.keyPath = Path("./authfiles")
        self.keyName = "appauth.json"
        self.box = None
        if os.path.isdir(str(self.keyPath)):
            if os.path.exists(str(self.keyPath / self.keyName)):
                self.box = BoxInterface.BoxInterface(
                    str(self.keyPath / self.keyName))
        else:
            os.makedirs(str(self.keyPath))

        if self.box is None:
            auth = AuthWindowDialog(self)
            choice = auth.exec_()  # blocks while dialog open
            if choice == 0:  # cancelled
                sys.exit()
            else:
                if auth.importKey:
                    copy(auth.path, (self.keyPath / self.keyName))
                    self.box = BoxInterface.BoxInterface(self.keyPath /
                                                         self.keyName)
                else:
                    self.box = BoxInterface.BoxInterface(auth.path)

        # Icons
        self.folderIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_DirClosedIcon'))
        self.fileIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_FileIcon'))
        self.windowIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_ArrowUp'))

        self.title = ("Asset Uploader")

        self.width = 640
        self.height = 240

        self.initUI()

    def initUI(self):
        # Title/icon
        self.setWindowTitle(self.title)

        # Menu bar
        menu = self.menuBar()
        utilMenu = menu.addMenu('File')
        refreshButton = QAction('Refresh', self)
        utilMenu.addAction(refreshButton)
        refreshButton.triggered.connect(self.rebuildModel)

        # Status bar w/ logging
        statusBarHandler = StatusBarLogger(self)
        logging.getLogger().addHandler(statusBarHandler)
        logging.getLogger().setLevel(logging.WARNING)

        # Icons
        self.setWindowIcon(self.windowIcon)

        # Tree view
        self.treeView = QTreeView()
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.setHeaderHidden(True)

        self.model = QStandardItemModel()
        self.model.dataChanged.connect(self.updateIndex)
        self.rebuildModel()

        self.treeView.setModel(self.model)

        # Add all to main layout
        mainWidget = QWidget(self)
        mainLayout = QVBoxLayout(mainWidget)
        self.setCentralWidget(mainWidget)
        mainLayout.addWidget(self.treeView)

        # Communication
        self.treeView.customContextMenuRequested.connect(self.contextMenu)

        self.show()

    def buildFileTree(self, root):
        self.model.dataChanged.disconnect(self.updateIndex)
        self.__recursBuildFileTree(root)
        self.model.dataChanged.connect(self.updateIndex)

    def __recursBuildFileTree(self, root):
        for item in self.box.get_folder_contents(root.boxData):
            new = BoxItem.BoxItem(self.box, item)
            root.appendRow(new)
            if item['type'] == "folder":
                new.setIcon(self.folderIcon)
                self.__recursBuildFileTree(new)
            else:
                new.setIcon(self.fileIcon)
        self.treeView.setSortingEnabled(True)

    def rebuildModel(self):
        self.model.clear()
        root = BoxItem.BoxItem(self.box, self.box.get_folder('0'))
        root.setIcon(self.folderIcon)
        root.setIcon(self.folderIcon)
        self.model.appendRow(root)
        self.buildFileTree(root)

    def updateIndex(self, index):
        modified = index.model().itemFromIndex(index)
        parent = modified.parent()
        if parent is None:  # Must be the root that was modified
            parent = modified
        parent.removeRows(0, parent.rowCount())
        self.buildFileTree(parent)
        self.treeView

    def contextMenu(self, position):
        index = self.treeView.selectedIndexes()[0]
        selected = index.model().itemFromIndex(index)
        selected.showContextMenu(self.treeView, position)
예제 #23
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)
        
        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()
        
        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)
        
        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)
        
        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)
        
        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a
        
        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))
        
        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)
        
        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)
        
        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)
        
        # set shortcut action
        a = self.shortcutAction = act(self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)
        
        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)
        
        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)
        
        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)
        
        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())
        
        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)
        
        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())
        
        # complete on snippet variables
        self.searchEntry.setCompleter(QCompleter([
            ':icon', ':indent', ':menu', ':name', ':python', ':selection',
            ':set', ':symbol', ':template', ':template-run'], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)
        
    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()
        
    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(_(
            "Enter text to search in the snippets list.\n"
            "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(map("<p>{0}</p>\n".format, (
            _("Enter text to search in the snippets list, and "
              "press Enter to apply the currently selected snippet."),
            _("If the search text fully matches the value of the '{name}' variable "
              "of a snippet, that snippet is selected.").format(name="name"),
            _("If the search text starts with a colon ':', the rest of the "
              "search text filters snippets that define the given variable. "
              "After a space a value can also be entered, snippets will then "
              "match if the value of the given variable contains the text after "
              "the space."),
            _("E.g. entering {menu} will show all snippets that are displayed "
              "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))
    
    def sizeHint(self):
        return self.parent().mainwindow().size() / 4
        
    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))
    
    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide() # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()
    
    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)
        
    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)
        
    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)
        
    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(self, cb, (collection, name))
            
            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()
            
    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()), reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()
    
    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
    
    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename, filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)
        
    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [row for row in range(model.model().rowCount())
                       if not self.treeView.isRowHidden(row, QModelIndex())]
        selectedrows = [i.row() for i in self.treeView.selectedIndexes()
                                if i.column() == 0 and i.row() in allrows]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]
        
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        n = len(names)
        caption = app.caption(_("dialog title",
            "Export {num} Snippet", "Export {num} Snippets", n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None, filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(self, _("Error"), _(
                    "Can't write to destination:\n\n{url}\n\n{error}").format(
                    url=filename, error=e.strerror))
        
    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)
        
    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")
        
    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(i, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()
            
    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)
        
    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
예제 #24
0
class App(QWidget):

    FROM, SUBJECT, DATE = range(3)
    print("#####################")

    def __init__(self):
        super().__init__()

        global list

        self.title = 'Traitement de CV'
        self.left = 100
        self.top = 100
        self.width = 940
        self.height = 840

        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setStyle(QStyleFactory.create("Fusion"))

        self.setGeometry(self.left, self.top, self.width, self.height)
        print(PyQt5.QtWidgets.QStyleFactory.keys())
        self.dataGroupBox = QGroupBox("CVs")
        self.dataView = QTreeView()
        self.dataView.setStyle((QStyleFactory.create("Fusion")))
        self.dataView.setRootIsDecorated(False)
        self.dataView.setAlternatingRowColors(True)

        advancedLayout = QGridLayout()

        #okButtonq.clicked.connect(self.runFunction())

        self.textDisplay = QTextEdit()
        self.textDisplay.setStyle(QStyleFactory.create("Fusion"))

        #ConsulAppObject.textEdit = QtWidgets.QInputDialog
        font = PyQt5.QtGui.QFont()
        font.setFamily("Leelawadee UI")
        font.setPointSize(10)
        font.setBold(False)
        font.setItalic(True)
        font.setWeight(50)

        self.textDisplay.setStyleSheet(
            "alternate-background-color: rgb(50, 180,255);\n"
            "background-color: rgb(225, 225, 225);\n"
            "font: 9pt \"Leelawadee UI\";\n"
            "")

        self.textDisplay.setObjectName("textDisplay")
        self.dataView.setStyleSheet(
            "selection-background-color:rgb(50, 180,255);\n"
            "border: 1px outset rgb(0, 150,255);\n"
            "background-color: rgb(235, 235, 235);\n"
            "font: 10pt \"Leelawadee UI\";\n"
            "")
        self.dataView.setStyle(QStyleFactory.create("Fusion"))

        dataLayout = QHBoxLayout()
        dataLayout.addWidget(self.dataView)
        dataLayout.addWidget(self.textDisplay)
        self.dataGroupBox.setLayout(dataLayout)

        model = self.CreateModel(self)
        self.dataView.setModel(model)

        self.infoDisplay = QLabel()
        self.infoDisplay.setStyleSheet(
            "selection-background-color:rgb(50, 180,255); \n"
            "alternate-background-color: rgb(0, 0,0);\n"
            "border: 1px outset rgb(0, 150,255);\n"
            "background-color: rgb(235, 235, 235);\n"
            "font: 10pt \"Leelawadee UI\";\n"
            "")
        self.infoDisplay.setText("Likelihood of a match")
        self.infoDisplay.setAlignment(QtCore.Qt.AlignCenter)

        #mainLayout = QVBoxLayout()
        #mainLayout.addWidget(self.dataGroupBox)
        self.setLayout(advancedLayout)

        self.progressBar = PyQt5.QtWidgets.QProgressBar()

        self.progressBar.setStyleSheet("background-color: rgb(200, 200, 200);")
        self.progressBar.setValue(0)
        self.progressBar.setMinimum(0)
        self.progressBar.setMaximum(100)

        insertButton = QPushButton("Insert", self)
        insertButton.clicked.connect(lambda: self.InsertMethod(model))
        deleteButton = QPushButton("Delete")
        deleteButton.clicked.connect(lambda: self.RemoveEntry(model))

        applyButton = QPushButton("Apply")
        applyButton.setStyle(QStyleFactory.create("Fusion"))
        applyButton.setStyleSheet("alternate-background-color: rgb(0,0,0);\n"
                                  "background-color: rgb(50, 180,255);\n"
                                  "font: 10pt \"Leelawadee UI\";\n"
                                  "")
        applyButton.clicked.connect(lambda: self.ApplyButton())

        selectButton = QPushButton("Select Text")
        selectButton.setStyle(QStyleFactory.create("Fusion"))
        selectButton.setStyleSheet("alternate-background-color: rgb(0,0,0);\n"
                                   "font: 10pt \"Leelawadee UI\";\n"
                                   "")
        selectButton.clicked.connect(
            lambda: self.OnChangeCurrentProjectClicked(model))

        cancelButton = QPushButton("Close")
        cancelButton.setStyle(QStyleFactory.create("Fusion"))
        cancelButton.setStyleSheet("alternate-background-color: rgb(0,0,0);\n"
                                   "background-color: rgb(225,225,225);\n"
                                   "font: 8pt \"Leelawadee UI\";\n"
                                   "")
        cancelButton.clicked.connect(exit)
        hbox = QHBoxLayout()
        hbox.addStretch(1)

        advancedLayout.addWidget(applyButton, 2, 0, 1, 1)
        advancedLayout.addWidget(self.dataGroupBox, 0, 0, 1, 3)
        advancedLayout.addWidget(self.progressBar, 2, 1, 1, 2)
        advancedLayout.addWidget(self.infoDisplay, 3, 1, 1, 2)
        advancedLayout.addWidget(selectButton, 3, 0, 1, 1)
        advancedLayout.addWidget(insertButton, 4, 0, 1, 1)
        advancedLayout.addWidget(deleteButton, 5, 0, 1, 1)
        advancedLayout.addWidget(cancelButton, 6, 0, 1, 1)

        self.show()

    def animation(self, txtToDisplay):
        for c in itertools.cycle(['.', '..', '...', '\\']):
            print(threading.active_count())
            self.textEdit.setPlaceholderText(txtToDisplay + " " + c)
            if threading.active_count() == 3:
                return
            time.sleep(0.5)

    def ReadDat(self, model):
        file = QtCore.QFile("save.dat")
        file.open(QtCore.QIODevice.ReadOnly)
        goIn = QtCore.QDataStream(file)
        array = []
        self.ReadItem(model.invisibleRootItem(), array)
        file.close()
        return array

    def ReadItem(self, item, array=[]):
        for i in range(0, item.rowCount()):
            child = item.child(i, 0)
            print(child.text())
            array.append(child.text())
# --------------------------------------------------------------------------------------------------------------------

    def OnChangeCurrentProjectClicked(self, model):
        # create text file
        global file, f, text
        # open data stream
        item = model.invisibleRootItem()
        text = self.ChangeCurrentProject(item)
        print(txts)
        print(txts + "\\" + text)
        f = docx.Document(file)
        firstDraft = [p.text for p in f.paragraphs]
        self.textDisplay.setPlainText(" ".join(firstDraft))

    def ChangeCurrentProject(self, item):
        file = open("CurrentProjectInf.bin", "wb")
        if item.rowCount() != None:
            a = self.dataView.selectedIndexes()
            if a != "[]":
                b = a[0].row()
                child = item.child(b, 0)
                c = child.text()
                return c

        file.close()
# --------------------------------------------------------------------------------------------------------------------

    def RemoveEntry(self, model):

        a = self.dataView.selectedIndexes()
        print(a)
        for x in range(0, len(a)):
            print(a[x])
        b = a[x].row()
        item = model.invisibleRootItem()
        model.removeRow(b)

# --------------------------------------------------------------------------------------------------------------------

    def ApplyButton(self):
        #self.save_item()
        global f, file, txts
        if file == "":
            return
        result = MainOperations.Main.Naive(self, file)
        self.progressBar.setValue(result)

    def save_item(self, item, out):
        if item.rowCount() != None:
            for i in range(0, item.rowCount()):
                child = item.child(i, 0)
                child.write(out)
                print(child.text())
                if child != None:
                    print("EEE")
                    self.save_item(child, out)
                else:
                    return

    def copy_rename(self, old_file_name, new_file_name):
        src_dir = os.curdir
        dst_dir = os.path.join(os.curdir, "subfolder")
        src_file = os.path.join(src_dir, old_file_name)
        shutil.copy(src_file, dst_dir)

        dst_file = os.path.join(dst_dir, old_file_name)
        new_dst_file_name = os.path.join(dst_dir, new_file_name)
        os.rename(dst_file, new_dst_file_name)

# --------------------------------------------------------------------------------------------------------------------

    def InsertMethod(self, model):
        global file
        file = self.openFileNameDialog()
        self.addModel(model, file, 'insert', 'insert')

    def openFileNameDialog(self):

        options = PyQt5.QtWidgets.QFileDialog.Options()

        file, _ = PyQt5.QtWidgets.QFileDialog.getOpenFileName(
            self,
            "QFileDialog.getOpenFileName()",
            "",
            "CV(*.docx);",
            options=options)
        options = PyQt5.QtWidgets.QFileDialog.Options()
        file, _ = PyQt5.QtWidgets.QFileDialog.getOpenFileName(
            self,
            "QFileDialog.getOpenFileName()",
            "",
            "All Files (*);;Python Files (*.py)",
            options=options)
        return file

    def addModel(self, model, From, subject, date):
        if From == "":
            return
        model.insertRow(0)
        model.setData(model.index(0, self.FROM), From)
        model.setData(model.index(0, self.SUBJECT), subject)
        model.setData(model.index(0, self.DATE), date)

# --------------------------------------------------------------------------------------------------------------------

# --------------------------------------------------------------------------------------------------------------------

    def CreateModel(self, parent):
        model = QStandardItemModel(0, 1, parent)
        model.setHeaderData(self.FROM, Qt.Horizontal, "Potential Texts Names")
        model.setHeaderData(self.SUBJECT, Qt.Horizontal, "Path")
        model.setHeaderData(self.DATE, Qt.Horizontal, "Contains")
        return model
예제 #25
0
class MyMainWindow(QMainWindow):

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.config_window()
        self.create_widgets()
        self.config_widgets()
        self.create_menubar()
        self.bind_widgets()
        self.show_widgets()

    def config_window(self):
        self.setWindowTitle('DirectoPy')
        self.setMinimumHeight(600)
        self.setMinimumWidth(1000)

    def create_widgets(self):
        self.central_widget = QWidget()
        self.main_layout = QGridLayout()
        self.moveup_button = QPushButton('Collapse all', self)
        self.goto_lineedit = QLineEdit('', self)
        self.goto_button = QPushButton('Go', self)
        self.folder_view = QTreeView(self)
        self.file_view = QTreeView(self)
        self.folder_model = QFileSystemModel(self)
        self.file_model = QFileSystemModel(self)

    def config_widgets(self):
        self.main_layout.addWidget(self.moveup_button, 0, 0)
        self.main_layout.addWidget(self.goto_lineedit, 0, 1, 1, 2)
        self.main_layout.addWidget(self.goto_button, 0, 3)
        self.main_layout.addWidget(self.folder_view, 1, 0, 1, 2)
        self.main_layout.addWidget(self.file_view, 1, 2, 1, 2)

        self.central_widget.setLayout(self.main_layout)

        # Кнопка "вверх"
        self.moveup_button.setMaximumWidth(100)

        # Кнопка "перейти"
        self.goto_button.setMaximumWidth(70)
        self.setCentralWidget(self.central_widget)
        # панели
        self.folder_model.setRootPath(None)
        self.folder_model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
        self.folder_view.setModel(self.folder_model)
        self.folder_view.setRootIndex(self.folder_model.index(None))
        self.folder_view.clicked[QModelIndex].connect(self.clicked_onfolder)
        self.folder_view.hideColumn(1)
        self.folder_view.hideColumn(2)
        self.folder_view.hideColumn(3)

        self.file_model.setFilter(QDir.Files)
        self.file_view.setModel(self.file_model)
        self.file_model.setReadOnly(False)
        self.file_view.setColumnWidth(0, 200)
        self.file_view.setSelectionMode(QAbstractItemView.ExtendedSelection)

    # открытие папки при нажати на неё в окне папок
    def clicked_onfolder(self, index):
        selection_model = self.folder_view.selectionModel()
        index = selection_model.currentIndex()
        dir_path = self.folder_model.filePath(index)
        self.file_model.setRootPath(dir_path)
        self.file_view.setRootIndex(self.file_model.index(dir_path))

    # ф-я открытия нового файла
    def open_file(self):
        index = self.file_view.selectedIndexes()
        if not index:
            return
        else:
            index = index[0]
        file_path = self.file_model.filePath(index).replace('/', '\\')
        print(file_path)
        self.file_view.update()

    # ф-я создания нового файла
    def new_file(self):
        global file_name
        index = self.folder_view.selectedIndexes()
        if len(index) > 0:
            path = self.folder_model.filePath(index[0])
            for i in range(1, 9999999999999999):
                if not os.path.isfile(os.path.join(path, "newfile{}.txt".format(i))):
                    file_name = os.path.join(path, "newfile{}.txt".format(i))
                    break
            file_name = os.path.abspath(file_name)
            open(file_name, 'w').close()
        else:
            print("Please, select folder")

    # ф-я удаления файла
    def delete_file(self):
        indexes = self.file_view.selectedIndexes()
        for i in indexes:
            self.file_model.remove(i)

    # ф-я переименования файла
    def rename_file(self):
        index = self.file_view.selectedIndexes()
        if not index:
            return
        else:
            index = index[0]
        self.file_view.edit(index)

    # ф-я копирования файла
    def copy_file(self):
        print("COPY")
        ask = QFileDialog.getExistingDirectory(self, "Open Directory", "C:\\",
                                               QFileDialog.ShowDirsOnly |
                                               QFileDialog.DontResolveSymlinks)
        new_path = ask.replace('\\', '/')
        indexes = self.file_view.selectedIndexes()[::4]
        for i in indexes:
            new_filename = new_path + '/' + self.file_model.fileName(i)
            copy2(self.file_model.filePath(i), new_filename)

    # ф-я возвращения к корню пути
    def colapse(self):
        self.folder_view.collapseAll()

    # ф-я перемещения в заданную директорию
    def go_to(self):
        dir_path = self.goto_lineedit.text().replace('\\', '/')
        print(dir_path)
        self.file_model.setRootPath(dir_path)
        self.file_view.setRootIndex(self.file_model.index(dir_path))

        #self.file_model.setRootPath()

    # ф-я перемещения файла
    def move_file(self):
        print("MOVE")
        ask = QFileDialog.getExistingDirectory(self, "Open Directory", "C:\\",
                                               QFileDialog.ShowDirsOnly |
                                               QFileDialog.DontResolveSymlinks)
        if ask == '':
            return
        new_path = ask.replace('\\', '/')
        indexes = self.file_view.selectedIndexes()[::4]
        for i in indexes:
            new_filename = new_path + '/' + self.file_model.fileName(i)
            move(self.file_model.filePath(i), new_filename)

    # ф-я создания новой папки
    def new_folder(self):
        global file_name
        index = self.folder_view.selectedIndexes()
        if len(index) > 0:
            path = self.folder_model.filePath(index[0])
            for i in range(1, 9999999999999999):
                if not os.path.isdir(os.path.join(path, "newfolder{}".format(i))):
                    file_name = os.path.join(path, "newfolder{}".format(i))
                    break
            file_name = os.path.abspath(file_name)
            os.mkdir(file_name)
        else:
            print("Please, select folder")

    # ф-я удаления папки
    def delete_folder(self):
        indexes = self.folder_view.selectedIndexes()
        for i in indexes:
            self.folder_model.remove(i)

    # ф-я переименования папки
    def rename_folder(self):
        index = self.folder_view.selectedIndexes()
        if not index:
            return
        else:
            index = index[0]
        self.folder_view.edit(index)

    # ф-я закрытия окна файлового менеджера
    def exit_application(self):
       print("EXIT")
       self.close()

    # задавание функции каждой кнопке
    def bind_widgets(self):
        self.open_file_action.triggered.connect(self.open_file)
        self.new_file_action.triggered.connect(self.new_file)
        self.delete_file_action.triggered.connect(self.delete_file)
        self.rename_file_action.triggered.connect(self.rename_file)
        self.copy_file_action.triggered.connect(self.copy_file)
        self.move_file_action.triggered.connect(self.move_file)
        self.exit_action.triggered.connect(self.exit_application)
        self.new_folder_action.triggered.connect(self.new_folder)
        self.delete_folder_action.triggered.connect(self.delete_folder)
        self.rename_folder_action.triggered.connect(self.rename_folder)


        self.goto_button.clicked.connect(partial(self.go_to))
        self.moveup_button.clicked.connect(partial(self.colapse))

    # создание меню
    def create_menubar(self):

        self.exit_action = QAction('Exit', self)
        self.exit_action.setShortcut('Ctrl+Q')

        self.new_file_action = QAction('New file', self)
        self.new_file_action.setShortcut('F4')

        self.open_file_action = QAction('Open file', self)
        self.open_file_action.setShortcut('F3')

        self.rename_file_action = QAction('Rename file', self)
        self.rename_file_action.setShortcut('F2')

        self.delete_file_action = QAction('Remove file', self)
        self.delete_file_action.setShortcut(QKeySequence.Delete)

        self.copy_file_action = QAction('Copy folder...', self)
        self.copy_file_action.setShortcut(QKeySequence.Copy)

        self.move_file_action = QAction('Move folder...', self)
        self.move_file_action.setShortcut(QKeySequence.Cut)

        self.new_folder_action = QAction('New folder', self)
        self.new_folder_action.setShortcut('Ctrl+Shift+N')

        self.delete_folder_action = QAction('Delete folder', self)
        self.delete_folder_action.setShortcut('Ctrl+Shift+Del')

        self.rename_folder_action = QAction('Rename folder', self)
        self.rename_folder_action.setShortcut('Ctrl+Shift+R')

        self.menubar = self.menuBar()
        self.file_menu = self.menubar.addMenu('File')
        self.file_menu.addAction(self.new_file_action)
        self.file_menu.addAction(self.open_file_action)
        self.file_menu.addAction(self.rename_file_action)
        self.file_menu.addAction(self.delete_file_action)
        self.file_menu.addAction(self.copy_file_action)
        self.file_menu.addAction(self.move_file_action)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.exit_action)

        self.folder_menu = self.menubar.addMenu('Folder')
        self.folder_menu.addAction(self.new_folder_action)
        self.folder_menu.addAction(self.delete_folder_action)
        self.folder_menu.addAction(self.rename_folder_action)

    def show_widgets(self):
        self.setLayout(self.main_layout)
예제 #26
0
파일: score.py 프로젝트: stweil/frescobaldi
class ScorePartsWidget(QSplitter):
    def __init__(self, parent):
        super(ScorePartsWidget, self).__init__(parent)

        self.typesLabel = QLabel()
        self.typesView = QTreeView(selectionMode=QTreeView.ExtendedSelection,
                                   selectionBehavior=QTreeView.SelectRows,
                                   animated=True,
                                   headerHidden=True)
        self.scoreLabel = QLabel()
        self.scoreView = widgets.treewidget.TreeWidget(
            selectionMode=QTreeView.ExtendedSelection,
            selectionBehavior=QTreeView.SelectRows,
            headerHidden=True,
            animated=True,
            dragDropMode=QTreeView.InternalMove)
        self.addButton = QPushButton(icon=icons.get("list-add"))
        self.removeButton = QPushButton(icon=icons.get("list-remove"))
        self.upButton = QToolButton(icon=icons.get("go-up"))
        self.downButton = QToolButton(icon=icons.get("go-down"))
        self.partSettings = QStackedWidget()

        w = QWidget()
        self.addWidget(w)
        layout = QVBoxLayout(spacing=0)
        w.setLayout(layout)

        layout.addWidget(self.typesLabel)
        layout.addWidget(self.typesView)
        layout.addWidget(self.addButton)

        w = QWidget()
        self.addWidget(w)
        layout = QVBoxLayout(spacing=0)
        w.setLayout(layout)

        layout.addWidget(self.scoreLabel)
        layout.addWidget(self.scoreView)

        box = QHBoxLayout(spacing=0)
        layout.addLayout(box)

        box.addWidget(self.removeButton)
        box.addWidget(self.upButton)
        box.addWidget(self.downButton)

        self.addWidget(self.partSettings)

        self.typesView.setModel(parts.model())
        app.translateUI(self)

        # signal connections
        self.addButton.clicked.connect(self.slotAddButtonClicked)
        self.removeButton.clicked.connect(self.slotRemoveButtonClicked)
        self.typesView.doubleClicked.connect(self.slotDoubleClicked)
        self.scoreView.currentItemChanged.connect(self.slotCurrentItemChanged)
        self.upButton.clicked.connect(self.scoreView.moveSelectedChildrenUp)
        self.downButton.clicked.connect(
            self.scoreView.moveSelectedChildrenDown)

    def translateUI(self):
        bold = "<b>{0}</b>".format
        self.typesLabel.setText(bold(_("Available parts:")))
        self.scoreLabel.setText(bold(_("Score:")))
        self.addButton.setText(_("&Add"))
        self.removeButton.setText(_("&Remove"))
        self.upButton.setToolTip(_("Move up"))
        self.downButton.setToolTip(_("Move down"))

    def slotDoubleClicked(self, index):
        self.addParts([index])

    def slotAddButtonClicked(self):
        self.addParts(self.typesView.selectedIndexes())

    def addParts(self, indexes):
        """Adds the parts for the given indexes."""
        # add to current if that is a container type
        currentItem = self.scoreView.currentItem()
        for index in indexes:
            category = index.internalPointer()
            if category:
                part = category.items[index.row()]
                box = QGroupBox(self.partSettings)
                self.partSettings.addWidget(box)
                # determine the parent: current or root
                if currentItem and issubclass(part,
                                              currentItem.part.accepts()):
                    parent = currentItem
                    parent.setExpanded(True)
                else:
                    parent = self.scoreView
                item = PartItem(parent, part, box)

    def slotCurrentItemChanged(self, item):
        if isinstance(item, PartItem):
            self.partSettings.setCurrentWidget(item.box)

    def slotRemoveButtonClicked(self):
        self.scoreView.removeSelectedItems()

    def clear(self):
        """Called when the user clicks the clear button on this page."""
        self.scoreView.clear()

    def rootPartItem(self):
        """Returns the invisibleRootItem(), representing the tree of parts in the score view."""
        return self.scoreView.invisibleRootItem()
예제 #27
0
class FileTreeView(QWidget):
    on_menu_select = pyqtSignal(str, str)
    on_dir_change = pyqtSignal()

    def __init__(self, parent: Application):
        super().__init__()
        self.stopped = threading.Event()
        self.tree = QTreeView()
        self.handle = self.Handler(self)
        self.get_data_file()
        self.model = QtGui.QStandardItemModel()
        self.item_construct = {}

        v_box = QVBoxLayout()
        v_box.addWidget(self.finder())
        v_box.addWidget(self.tree_view())
        self.setLayout(v_box)
        self.watch_dog()
        parent.app_close.connect(self.exit_push)

        self.tree.setAlternatingRowColors(True)

    def exit_push(self):
        self.stopped.set()

    def finder(self):
        w_find = QLineEdit()
        w_find.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        w_find.textChanged.connect(self.sort_list)
        w_find.setPlaceholderText("Search file..")

        return w_find

    def sort_list(self, text):
        list_sort = []
        if text == "":
            self.show_tree(self.list_file)
        else:
            for data in self.list_file:
                if text.lower() in data.name().lower():
                    list_sort.append(data)
            self.show_tree(list_sort)

    def watch_dog(self):
        watch = ProcessRunnable(target=watch_winform,
                                args=(get_data_folder(), self.handle,
                                      self.stopped))
        watch.start()

    class Handler(watchdog.events.PatternMatchingEventHandler):
        def __init__(self, parent):
            super().__init__()
            self.parent = parent

        def on_created(self, event):
            print("Watchdog received created event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('create', event.src_path,
                                        event.is_directory))

        def on_modified(self, event):
            print("Watchdog received modified event",
                  event.src_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('modify', event.src_path,
                                        event.is_directory))

        def on_moved(self, event):
            print("Watchdog received move event",
                  event.src_path,
                  event.dest_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('move', event.src_path,
                                        event.is_directory, event.dest_path))

        def on_deleted(self, event):
            print("Watchdog received delete event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('delete', event.src_path,
                                        event.is_directory))

    async def file_change(self, tpe, old, is_directory, new=""):
        if tpe == "move":
            self.import_single(self.model.invisibleRootItem(), new)
            self.remove_single(old)
        elif tpe == "delete":
            self.remove_single(old)
        elif tpe == "create":
            self.import_single(self.model.invisibleRootItem(), old)

        if is_directory:
            self.on_dir_change.emit()

    def get_data_file(self):
        self.list_file = []
        self.create_list(get_data_folder())

    def create_list(self, dir):
        lst = os.listdir(path=dir)
        for f in lst:
            path = os.path.join(dir, f)
            file = MyFile()
            if os.path.isdir(path):
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(True)
                self.list_file.append(file)
                self.create_list(path)
            else:
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(False)
                self.list_file.append(file)

    def tree_view(self):
        self.tree.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.open_menu)
        self.tree.doubleClicked.connect(self.open_event)
        # self.model.itemChanged.connect(self.data_change)
        self.tree.setModel(self.model)
        self.show_tree(self.list_file)
        return self.tree

    def show_tree(self, list_file):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['List API'])
        self.tree.header().setDefaultSectionSize(180)
        parent = self.model.invisibleRootItem()
        self.item_construct = {}
        self.import_data(parent, list_file)
        self.tree.expandAll()

    def import_data_by_path(self, parent, path: list, index):
        if index < len(path):
            full = os.sep.join(path[:index + 1])
            if full in self.item_construct:
                item = self.item_construct[full]
            else:
                item = QStandardItem(path[index])
                if os.path.isfile(os.path.join(get_data_folder(), full)):
                    item.setToolTip(
                        self.read_description(
                            os.path.join(get_data_folder(), full)))
                item.setEditable(False)
                if not os.path.isdir(os.path.join(get_data_folder(), full)):
                    item.setIcon(QIcon(get_icon_link("text_snippet.svg")))
                else:
                    item.setIcon(QIcon(get_icon_link("folder_yellow.svg")))

                item.setData(full)
                parent.appendRow(item)
                self.item_construct[full] = item

            self.import_data_by_path(item, path, index + 1)

    def read_description(self, path):
        try:
            data = json.loads(open(path, encoding='utf-8').read())
            json_data = APIData()
            json_data.construct(data)
            x = json_data.parseSave().description()
            if x.isspace() or x == "":
                return ".."
            else:
                return x
        except Exception as ex:
            print(ex)
            return ".."

    def import_data(self, parent, list_data):
        for i in list_data:
            self.import_single(parent, i)

    def import_single(self, parent, file_path):
        path = self.path_extract(file_path)
        self.import_data_by_path(parent, path, 0)

    def remove_single(self, file_path):
        path = self.path_extract(file_path)
        full = os.sep.join(path[:len(path)])
        if full in self.item_construct:
            item = self.item_construct[full]
            (item.parent()
             or self.model.invisibleRootItem()).removeRow(item.row())
            del self.item_construct[full]

    def path_extract(self, file_path):
        if isinstance(file_path, MyFile):
            path = os.path.join(file_path.parent(), file_path.name())
        else:
            path = file_path

        path = path.replace(get_data_folder(), "")
        if path.startswith(os.sep):
            path = path.replace(os.sep, "", 1)
        path = path.split(os.sep)
        return path

    def open_menu(self, position):
        indexes = self.tree.selectedIndexes()
        level = 0
        data = ""
        item = None
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2

        menu = QMenu()
        menu.setStyleSheet(open(get_stylesheet()).read())

        rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename',
                                self)
        rename_action.setStatusTip('Rename')

        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&New Folder', self)
        new_action.setStatusTip('New Folder')

        refresh_action = QAction(QIcon(get_icon_link('refresh.svg')),
                                 '&Refresh', self)
        refresh_action.setStatusTip('Refresh')

        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete', self)
        delete_action.setStatusTip('Delete')

        open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open',
                              self)
        open_action.setStatusTip('Open file')

        expand_action = QAction(QIcon(), '&Expand', self)
        expand_action.setStatusTip('Expand')

        collapse_action = QAction(QIcon(), '&Collapse', self)
        collapse_action.setStatusTip('Collapse')

        duplicate_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                                   '&Duplicate', self)
        duplicate_action.setStatusTip('Duplicate')

        copy_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                              '&Copy', self)
        copy_action.setStatusTip('Copy')

        move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')),
                              '&Move', self)
        move_action.setStatusTip('Move')

        if level == 1:
            menu.addAction(rename_action)
            menu.addAction(new_action)
            menu.addSeparator()
            menu.addAction(refresh_action)
            menu.addAction(expand_action)
            menu.addAction(collapse_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        elif level == 2:
            menu.addAction(open_action)
            menu.addAction(new_action)
            menu.addAction(refresh_action)
            menu.addSeparator()
            menu.addAction(rename_action)
            menu.addAction(duplicate_action)
            menu.addAction(copy_action)
            menu.addAction(move_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        else:
            menu.addAction(new_action)
            menu.addAction(refresh_action)

        action = menu.exec_(self.tree.viewport().mapToGlobal(position))

        if action == open_action:
            if data != "":
                self.on_menu_select.emit("open", data)
        elif action == refresh_action:
            self.get_data_file()
            self.show_tree(self.list_file)
        elif action == expand_action:
            if item is not None:
                self.tree.expand(item.index())
        elif action == collapse_action:
            if item is not None:
                self.tree.collapse(item.index())
        elif action == delete_action:
            if data != "":
                msg = QMessageBox()
                msg.setStyleSheet(open(get_stylesheet()).read())
                msg.setIcon(QMessageBox.Warning)
                msg.setBaseSize(QSize(500, 300))
                msg.setText("Delete file.")
                msg.setInformativeText("Are you sure to detele " +
                                       os.path.basename(data) + "?")
                msg.setWindowTitle("Delete Warning!!!")
                msg.addButton('Delete', QMessageBox.YesRole)
                msg.addButton('Move to Trash', QMessageBox.YesRole)
                msg.addButton('Cancel', QMessageBox.NoRole)

                rs = msg.exec_()
                if rs == 0:
                    if os.path.isdir(data):
                        shutil.rmtree(data)
                    else:
                        os.remove(data)
                elif rs == 1:
                    send2trash(data)
        elif action == new_action:
            if data == "":
                data = get_data_folder()
            # input_name = QInputDialog()
            # input_name.setStyleSheet(open(get_stylesheet()).read())
            # text, ok = input_name.getText(self, 'New Folder', 'Folder name:')
            inp = QComboDialog('New Folder', 'Folder name:', QComboDialog.Text)
            ok = inp.exec_()
            if ok and inp.select:
                if os.path.isdir(data):
                    try:
                        os.mkdir(os.path.join(data, inp.select))
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
                else:
                    new = os.path.join(os.path.dirname(data), inp.select)
                    try:
                        os.mkdir(new)
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == rename_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Rename file', 'New name:')
                inp = QComboDialog('Rename file', 'New name:',
                                   QComboDialog.Text)
                ok = inp.exec_()
                if ok and inp.select:
                    if os.path.isdir(data):
                        new = os.path.join(os.path.dirname(data), inp.select)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename folder error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
                    else:
                        filename, file_extension = os.path.splitext(data)
                        new = os.path.join(os.path.dirname(data),
                                           inp.select + file_extension)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename file error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
        elif action == move_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                #
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                inp = QComboDialog("Move", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        os.rename(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Move file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == duplicate_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Duplicate file', 'New name:')
                inp = QComboDialog('Duplicate file', 'New name:',
                                   QComboDialog.Text)
                filename, file_extension = os.path.splitext(data)
                inp.set_init_text(filename)
                ok = inp.exec_()
                if ok and inp.select:
                    new = os.path.join(os.path.dirname(data),
                                       inp.select + file_extension)
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Duplicate file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == copy_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                inp = QComboDialog("Copy", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Copy file error", str(ex))
                        alert.exec_()
                        print(ex)

    def open_event(self, index):
        item = self.model.itemFromIndex(index)
        if item is not None:
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2
            if data != "":
                if level == 2:
                    self.on_menu_select.emit("open", data)
                elif level == 1:
                    if not self.tree.isExpanded(item.index()):
                        self.tree.collapse(item.index())
                    else:
                        self.tree.expand(item.index())
예제 #28
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()
예제 #29
0
class FileTreeView(QWidget):
    on_menu_select = pyqtSignal(str, dict)

    def __init__(self):
        super().__init__()
        self.list_file = []
        self.get_data_file()

        v_box = QVBoxLayout()
        v_box.addLayout(self.finder())
        v_box.addWidget(self.tree_view())
        self.setLayout(v_box)

    def finder(self):
        w_find = QLineEdit()
        w_find.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))

        w_find_button = QPushButton()
        w_find_button.setText("Find")
        w_find_button.setFixedWidth(50)

        h_box = QHBoxLayout()
        h_box.addWidget(w_find)
        h_box.addWidget(w_find_button, 1)
        return h_box

    def get_data_file(self):
        self.list_file = self.create_list(get_data_folder())

    def create_list(self, dir):
        lst = os.listdir(path=dir)
        list_file = []
        for f in lst:
            path = dir + "\\" + f
            file = {}
            if os.path.isdir(path):
                file["dir"] = dir
                file["name"] = f
                file["isDir"] = True
                file["child"] = self.create_list(path)
            else:
                file["dir"] = dir
                file["name"] = f
                file["isDir"] = False

            list_file.append(file)
        return list_file

    def tree_view(self):
        self.tree = QTreeView()
        self.tree.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.openMenu)

        self.model = QtGui.QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['List API'])
        self.tree.header().setDefaultSectionSize(180)
        self.tree.setModel(self.model)
        self.import_data(self.model.invisibleRootItem(), self.list_file)
        self.tree.expandAll()

        return self.tree

    def import_data(self, parent_item, list):
        for i in list:
            if i["isDir"]:
                item = QStandardItem(i["name"])
                item.setEditable(False)
                item.setIcon(QIcon(get_icon_link("folder_yellow.svg")))
                item.setData(i)
                parent_item.appendRow(item)
                self.import_data(item, i["child"])
            else:
                item = QStandardItem(i["name"])
                item.setEditable(False)
                item.setIcon(QIcon(get_icon_link("text_snippet.svg")))
                item.setData(i)
                parent_item.appendRow(item)

    def openMenu(self, position):
        indexes = self.tree.selectedIndexes()
        level = 0
        data = {}
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            if data['isDir']:
                level = 1
            else:
                level = 2

        menu = QMenu()
        menu.setStyleSheet(open('stylesheet/default.qss').read())

        # Create preference action
        rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename',
                                self)
        rename_action.setStatusTip('Rename')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&New Folder', self)
        new_action.setStatusTip('New Folder')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        refresh_action = QAction(QIcon(get_icon_link('refresh.svg')),
                                 '&Refresh', self)
        refresh_action.setStatusTip('Refresh')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete', self)
        delete_action.setStatusTip('Delete')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open',
                              self)
        open_action.setStatusTip('Open file')
        # open_action.triggered.connect(self.open_file)

        # Create preference action
        expand_action = QAction(QIcon(), '&Expand', self)
        expand_action.setStatusTip('Expand')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        collapse_action = QAction(QIcon(), '&Collapse', self)
        collapse_action.setStatusTip('Collapse')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        copy_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                              '&Copy', self)
        copy_action.setStatusTip('Copy')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')),
                              '&Move', self)
        move_action.setStatusTip('Move')
        # exitAction.triggered.connect(self.exitCall)

        if level == 1:
            menu.addAction(rename_action)
            menu.addAction(new_action)
            menu.addSeparator()
            menu.addAction(refresh_action)
            menu.addAction(expand_action)
            menu.addAction(collapse_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        elif level == 2:
            menu.addAction(open_action)
            menu.addAction(refresh_action)
            menu.addSeparator()
            menu.addAction(rename_action)
            menu.addAction(copy_action)
            menu.addAction(move_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        else:
            menu.addAction(new_action)
            menu.addAction(refresh_action)

        action = menu.exec_(self.tree.viewport().mapToGlobal(position))
        if action == open_action:
            self.on_menu_select.emit("open", data)
예제 #30
0
파일: __init__.py 프로젝트: Zubax/kucher
class RegisterViewWidget(WidgetBase):
    def __init__(self, parent: QWidget):
        super(RegisterViewWidget, self).__init__(parent)

        self._registers = []
        self._running_task: asyncio.Task = None

        self._visibility_selector = QComboBox(self)
        self._visibility_selector.addItem("Show all registers", lambda _: True)
        self._visibility_selector.addItem("Only configuration parameters",
                                          lambda r: r.mutable and r.persistent)

        # noinspection PyUnresolvedReferences
        self._visibility_selector.currentIndexChanged.connect(
            lambda _: self._on_visibility_changed())

        self._reset_selected_button = make_button(
            self,
            "Reset selected",
            icon_name="clear-symbol",
            tool_tip=f"Reset the currently selected registers to their default "
            f"values. The restored values will be committed "
            f"immediately. This function is available only if a "
            f"default value is defined. [{RESET_SELECTED_SHORTCUT}]",
            on_clicked=self._do_reset_selected,
        )

        self._reset_all_button = make_button(
            self,
            "Reset all",
            icon_name="skull-crossbones",
            tool_tip=f"Reset the all registers to their default "
            f"values. The restored values will be committed "
            f"immediately.",
            on_clicked=self._do_reset_all,
        )

        self._read_selected_button = make_button(
            self,
            "Read selected",
            icon_name="process",
            tool_tip=f"Read the currently selected registers only "
            f"[{READ_SELECTED_SHORTCUT}]",
            on_clicked=self._do_read_selected,
        )

        self._read_all_button = make_button(
            self,
            "Read all",
            icon_name="process-plus",
            tool_tip="Read all registers from the device",
            on_clicked=self._do_read_all,
        )

        self._export_button = make_button(
            self,
            "Export",
            icon_name="export",
            tool_tip="Export configuration parameters",
            on_clicked=self._do_export,
        )

        self._import_button = make_button(
            self,
            "Import",
            icon_name="import",
            tool_tip="Import configuration parameters",
            on_clicked=self._do_import,
        )

        self._expand_all_button = make_button(
            self,
            "",
            icon_name="expand-arrow",
            tool_tip="Expand all namespaces",
            on_clicked=lambda: self._tree.expandAll(),
        )

        self._collapse_all_button = make_button(
            self,
            "",
            icon_name="collapse-arrow",
            tool_tip="Collapse all namespaces",
            on_clicked=lambda: self._tree.collapseAll(),
        )

        self._status_display = QLabel(self)
        self._status_display.setWordWrap(True)

        self._reset_selected_button.setEnabled(False)
        self._reset_all_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(False)
        self._export_button.setEnabled(False)
        self._import_button.setEnabled(False)

        self._tree = QTreeView(self)
        self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setAnimated(True)
        self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._tree.setAlternatingRowColors(True)
        self._tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        # Not sure about this one. This hardcoded value may look bad on some platforms.
        self._tree.setIndentation(20)

        def add_action(
            callback: typing.Callable[[], None],
            icon_name: str,
            name: str,
            shortcut: typing.Optional[str] = None,
        ):
            action = QAction(get_icon(icon_name), name, self)
            # noinspection PyUnresolvedReferences
            action.triggered.connect(callback)
            if shortcut:
                action.setShortcut(shortcut)
                action.setAutoRepeat(False)
                try:
                    action.setShortcutVisibleInContextMenu(True)
                except AttributeError:
                    pass  # This feature is not available in PyQt before 5.10

            self._tree.addAction(action)

        add_action(self._do_read_all, "process-plus", "Read all registers")
        add_action(
            self._do_read_selected,
            "process",
            "Read selected registers",
            READ_SELECTED_SHORTCUT,
        )
        add_action(
            self._do_reset_selected,
            "clear-symbol",
            "Reset selected to default",
            RESET_SELECTED_SHORTCUT,
        )

        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.VALUE),
            EditorDelegate(self._tree, self._display_status),
        )

        # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom
        # decoration position in order to be able to use center alignment. Left or right positions do not work here.
        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.FLAGS),
            StyleOptionModifyingDelegate(
                self._tree,
                decoration_position=QStyleOptionViewItem.Top,  # Important
                decoration_alignment=Qt.AlignCenter,
            ),
        )

        header: QHeaderView = self._tree.header()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setStretchLastSection(
            False)  # Horizontal scroll bar doesn't work if this is enabled

        buttons_layout = QGridLayout()
        buttons_layout.addWidget(self._read_selected_button, 0, 0)
        buttons_layout.addWidget(self._reset_selected_button, 0, 2)
        buttons_layout.addWidget(self._read_all_button, 1, 0)
        buttons_layout.addWidget(self._reset_all_button, 1, 2)
        buttons_layout.addWidget(self._import_button, 2, 0)
        buttons_layout.addWidget(self._export_button, 2, 2)

        for col in range(3):
            buttons_layout.setColumnStretch(col, 1)

        layout = lay_out_vertically(
            (self._tree, 1),
            buttons_layout,
            lay_out_horizontally(
                self._visibility_selector,
                (None, 1),
                self._expand_all_button,
                self._collapse_all_button,
            ),
            self._status_display,
        )

        self.setLayout(layout)

    def reset(self):
        self.setup([])

    def setup(self, registers: typing.Iterable[Register]):
        self._registers = list(registers)
        self._on_visibility_changed()

    def _replace_model(
            self, register_visibility_predicate: typing.Callable[[Register],
                                                                 bool]):
        # Cancel all operations that might be pending on the old model
        self._cancel_task()

        old_model = self._tree.model()

        # Configure the new model
        filtered_registers = list(
            filter(register_visibility_predicate, self._registers))
        # It is important to set the Tree widget as the parent in order to let the widget take ownership
        new_model = Model(self._tree, filtered_registers)
        _logger.info("New model %r", new_model)
        self._tree.setModel(new_model)

        # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured
        self._tree.selectionModel().selectionChanged.connect(
            lambda *_: self._on_selection_changed())

        # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it.
        #       We could call deleteLater() on it, but it seems dangerous, because if that something ever decided
        #       to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on
        #       our hands. The horror!
        if old_model is not None:
            import gc

            model_referrers = gc.get_referrers(old_model)
            if len(model_referrers) > 1:
                _logger.warning(
                    "Extra references to the old model %r: %r",
                    old_model,
                    model_referrers,
                )

        # Update the widget - all root items are expanded by default
        for row in itertools.count():
            index = self._tree.model().index(row, 0)
            if not index.isValid():
                break

            self._tree.expand(index)

        self._reset_selected_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(len(filtered_registers) > 0)
        self._reset_all_button.setEnabled(len(filtered_registers) > 0)
        self._export_button.setEnabled(len(filtered_registers) > 0)
        self._import_button.setEnabled(len(filtered_registers) > 0)

        self._display_status(f"{len(filtered_registers)} registers loaded")

    def _on_visibility_changed(self):
        self._replace_model(self._visibility_selector.currentData())

    def _on_selection_changed(self):
        selected = self._get_selected_registers()

        self._reset_selected_button.setEnabled(
            any(map(lambda r: r.has_default_value, selected)))
        self._read_selected_button.setEnabled(len(selected) > 0)

    def _do_read_selected(self):
        selected = self._get_selected_registers()
        if selected:
            self._read_specific(selected)
        else:
            self._display_status("No registers are selected, nothing to read")

    def _do_reset_selected(self):
        rv = {}
        for r in self._get_selected_registers():
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_reset_all(self):
        rv = {}
        for r in self._registers:
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_read_all(self):
        self._read_specific(self._tree.model().registers)

    def _do_import(self):
        import_registers(parent=self, registers=self._registers)

    def _do_export(self):
        export_registers(parent=self, registers=self._registers)

    def _read_specific(self, registers: typing.List[Register]):
        total_registers_read = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_read
            total_registers_read = total_registers
            self._display_status(
                f"Reading {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info("Reading registers: %r",
                             [r.name for r in registers])
                mod: Model = self._tree.model()
                await mod.read(registers=registers,
                               progress_callback=progress_callback)
            except asyncio.CancelledError:
                self._display_status(f"Read has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register read failed")
                show_error("Read failed", "Could not read registers", repr(ex),
                           self)
                self._display_status(f"Could not read registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_read} registers have been read")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _write_specific(self, register_value_mapping: typing.Dict[Register,
                                                                  typing.Any]):
        total_registers_assigned = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_assigned
            total_registers_assigned = total_registers
            self._display_status(
                f"Writing {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info(
                    "Writing registers: %r",
                    [r.name for r in register_value_mapping.keys()],
                )
                mod: Model = self._tree.model()
                await mod.write(
                    register_value_mapping=register_value_mapping,
                    progress_callback=progress_callback,
                )
            except asyncio.CancelledError:
                self._display_status(f"Write has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register write failed")
                show_error("Write failed", "Could not read registers",
                           repr(ex), self)
                self._display_status(f"Could not write registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_assigned} registers have been written")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _get_selected_registers(self) -> typing.List[Register]:
        selected_indexes: typing.List[
            QModelIndex] = self._tree.selectedIndexes()
        selected_registers = set()
        for si in selected_indexes:
            r = Model.get_register_from_index(si)
            if r is not None:
                selected_registers.add(r)
        # Beware that sets are not sorted, this may lead to weird user experience when watching the registers
        # read in a funny order.
        return list(sorted(selected_registers, key=lambda x: x.name))

    def _cancel_task(self):
        # noinspection PyBroadException
        try:
            self._running_task.cancel()
        except Exception:
            pass
        else:
            _logger.info("A running task had to be cancelled: %r",
                         self._running_task)
        finally:
            self._running_task = None

    def _display_status(self, text=None):
        self._status_display.setText(text)
예제 #31
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()
예제 #32
0
class Navigation(QWidget):
    """
    Navigation class definition.
    
    Provide a combobox to switch on each opened directories and display it into
    a tree view
    
    Provide 2 useful function (to use in alter module):
      - add_action(name, shortcut, callback)
         - callback take 2 arguments : file_info and parent
      - add_separator()
    
    """

    SETTINGS_DIRECTORIES = 'navigation_dirs'
    SETTINGS_CURRENT_DIR = 'navigation_current_dir'

    onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated")
    onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated")

    def __init__(self, parent=None):
        super(Navigation, self).__init__(parent)
        self.setObjectName("Navigation")

        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.menu_button = QPushButton('Select directory', self)
        self.menu_button.setFlat(True)
        #        self.menu_button.clicked.connect(self.on_menu_button_clicked)
        self.menu = QMenu(self)
        self.menu_button.setMenu(self.menu)
        self.menu_directories = QMenu(self)
        self.menu_directories.setTitle('Directories')
        self.menu_add_action('Open directory', self.open_directory, None,
                             QKeySequence.Open)
        self.menu_add_separator()
        self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh)
        # @TODO invoke_all
        self.menu_add_separator()
        self.menu.addMenu(self.menu_directories)

        self.tree = QTreeView(self)
        self.model = FileSystemModel(self)
        self.tree.setModel(self.model)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)
        self.tree.setHeaderHidden(True)
        # only to expand directory or activated with one click
        self.tree.clicked.connect(self.on_item_clicked)
        # else, for file use activated signal
        self.tree.activated.connect(self.on_item_activated)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.on_context_menu)

        self.widgets = collections.OrderedDict()
        self.widgets['menu_button'] = self.menu_button
        self.widgets['tree'] = self.tree

        # @ToDo: Alter.invoke_all('add_widget', self.widgets)

        for name, widget in self.widgets.items():
            if name == 'menu_button':
                self.layout.addWidget(widget, 0, Qt.AlignLeft)
            else:
                self.layout.addWidget(widget)

        self.context_menu = QMenu(self)
        self.add_action('New file', QKeySequence.New,
                        FileSystemHelper.new_file)
        self.add_separator()
        self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy)
        self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut)
        self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste)
        self.add_separator()
        self.add_action('Delete', QKeySequence.Delete, FileSystemHelper.delete)

        # @ToDo Alter.invoke_all('navigation_add_action', self)

        #restore previous session and data
        dirs = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_DIRECTORIES, None, True)
        for directory_path in dirs:
            name = os.path.basename(directory_path)
            self.menu_add_directory(name, directory_path)
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()

        self.menu_button.setFocusPolicy(Qt.NoFocus)
        self.menu_button.setFocusProxy(self.tree)

    def reset(self, file_info):
        self.model.beginResetModel()
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()

    def on_menu_button_clicked(self):
        pos = self.mapToGlobal(self.menu_button.pos())
        menu_width = self.menu.sizeHint().width()
        pos.setY(pos.y() + self.menu_button.height())
        #        pos.setX(pos.x() + self.menu_button.width() - menu_width)
        if len(self.menu.actions()) > 0:
            self.menu.exec(pos)

    def menu_add_action(self,
                        name,
                        callback,
                        data=None,
                        shortcut=None,
                        icon=None):
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        if shortcut:
            action.setShortcut(shortcut)
            action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        if data:
            action.setData(data)
        action.triggered.connect(callback)
        self.addAction(action)
        self.menu.addAction(action)

    def menu_add_directory(self, name, data):
        action = QAction(name, self)
        action.setData(data)
        action.triggered.connect(self.on_menu_action_triggered)
        self.menu_directories.addAction(action)
        return action

    def menu_add_separator(self):
        self.menu.addSeparator()

    def add_action(self, name, shortcut, callback, icon=None):
        """
        Ajoute une action au context menu et au widget navigation lui même.
        Créer une fonction à la volé pour fournir des arguments aux fonctions
        associé aux actions.
        """
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        action.setShortcut(shortcut)
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.__wrapper(callback))
        self.addAction(action)
        self.context_menu.addAction(action)

    def add_separator(self):
        """Simple abstraction of self.context_menu.addSeparator()"""
        self.context_menu.addSeparator()

    def __wrapper(self, callback):
        def __new_function():
            """
            __new_function représente la forme de tous les callbacks connecté
            à une action pour pouvoir utiliser les raccourcis en même temps que
            le menu contextuel.
            """
            action = self.sender()
            file_info = action.data()
            if not file_info:
                indexes = self.tree.selectedIndexes()
                if indexes:
                    model_index = indexes[0]
                    file_info = self.model.fileInfo(model_index)
                    callback(file_info, self)
                elif action.shortcut() == QKeySequence.New:
                    file_info = self.model.fileInfo(self.tree.rootIndex())
                    callback(file_info, self)
            else:
                callback(file_info, self)
                action.setData(None)

        return __new_function

    def question(self, text, informative_text=None):
        message_box = QMessageBox(self)
        message_box.setText(text)
        if informative_text:
            message_box.setInformativeText(informative_text)
        message_box.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        message_box.setDefaultButton(QMessageBox.No)
        return message_box.exec()

    def on_context_menu(self, point):
        model_index = self.tree.indexAt(point)
        file_info = self.model.fileInfo(model_index)
        # pour chaque action on met a jour les data (file_info)
        # puis on altère les actions (ex enabled)
        for action in self.context_menu.actions():
            if not action.isSeparator():
                action.setData(file_info)
                action.setEnabled(model_index.isValid())
                if action.shortcut() == QKeySequence.New:
                    action.setEnabled(True)
                    if not model_index.isValid():
                        file_info = self.model.fileInfo(self.tree.rootIndex())
                        action.setData(file_info)
                if action.shortcut() == QKeySequence.Paste:
                    enable = FileSystemHelper.ready() and model_index.isValid()
                    action.setEnabled(enable)
                if action.shortcut() == QKeySequence.Delete:
                    # remove directory only if is an empty directory
                    if model_index.isValid() and file_info.isDir():
                        path = file_info.absoluteFilePath()
                        # QDir(path).count() always contains '.' and '..'
                        action.setEnabled(QDir(path).count() == 2)
                # @ToDo
                #Alter.invoke_all(
                #    'navigation_on_menu_action',
                #    model_index, file_info, action, self)
        if len(self.context_menu.actions()) > 0:
            self.context_menu.exec(self.tree.mapToGlobal(point))
        # reset action data, sinon y a des problèmes dans _new_function
        for action in self.context_menu.actions():
            action.setData(None)

    def on_item_activated(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
        else:
            self.onFileItemActivated.emit(qFileInfo)

    def on_item_clicked(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
            self.tree.setExpanded(index, not self.tree.isExpanded(index))
        else:
            self.onFileItemActivated.emit(qFileInfo)

    def open_directory(self):
        path = QFileDialog.getExistingDirectory(self, "Open Directory", ".")
        if path:
            name = os.path.basename(path)
            action = self.menu_add_directory(name, path)
            self.save_directories_path()
            action.trigger()

    def on_menu_action_triggered(self):
        action = self.sender()
        path = action.data()
        if path:
            self.model.setRootPath(path)
            self.tree.setRootIndex(self.model.index(path))
            self.menu_button.setText(os.path.basename(path))
            self.save_current_dir(path)

    def save_directories_path(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_DIRECTORIES,
            [action.data() for action in self.menu_directories.actions()])

    def save_current_dir(self, path):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_CURRENT_DIR, path)
예제 #33
0
class bands_pairing2(QWidget):
    def __init__(self, dataview):
        super().__init__()
        """a tree widget to create a list of datasets and select bands from each dataset"""

        self.dataview = dataview
        self.firstband = QTreeView()
        self.secondband = QTreeView()
        self.firstband.setModel(self.dataview.model)
        self.secondband.setModel(self.dataview.model)
        self.pairs = treewidget()
        self.pairs.setColumnCount(2)
        self.pairs.setHeaderLabels(['first band', 'second band'])
        self.pairbutton = QPushButton('Pair')

        self.pairbutton.clicked.connect(self.add_pair)
        mainlayout = QVBoxLayout()
        sublayout = QHBoxLayout()
        sublayout.addWidget(self.firstband)
        sublayout.addWidget(self.secondband)
        firstsecondgroup = QGroupBox()
        firstsecondgroup.setLayout(sublayout)
        mainlayout.addWidget(firstsecondgroup)
        mainlayout.addWidget(self.pairbutton)
        mainlayout.addWidget(self.pairs)
        self.maingroup = QGroupBox()
        self.maingroup.setLayout(mainlayout)

    def additems(self):
        """when the widget is activated, a list of selected bands will be added"""
        dict = self.dataview.collect_user_input()
        for i in dict['bandsdict'].keys():
            name = os.path.basename(i)
            for j in dict['bandsdict'][name].keys():
                band = QListWidgetItem(j)
                band2 = QListWidgetItem(j)
                self.firstband.addItem(band)
                self.secondband.addItem(band2)

    def add_pair(self):
        """when pair button is pressed the paire will be add to the list"""
        # get selected index
        firstitem = self.firstband.selectedIndexes()
        # item text (example:band1)
        firstitemtext = self.firstband.model().itemData(firstitem[0])[0]
        # parent text (example: rastername.tif)
        firstparent = self.firstband.model().itemFromIndex(firstitem[0]).parent().data(0)
        #text to appear on the pairing widget
        firstpairtext = os.path.basename(firstparent) + ' - ' + firstitemtext

        # get selected index
        seconditem = self.secondband.selectedIndexes()
        # item text (example:band1)
        seconditemtext = self.secondband.model().itemData(seconditem[0])[0]
        # parent text (example: rastername.tif)
        secondparent = self.firstband.model().itemFromIndex(firstitem[0]).parent().data(0)
        # text to appear on the pairing widget
        secondpairtext = os.path.basename(secondparent) + ' - ' + seconditemtext

        print(firstitemtext)
        print(os.path.basename(firstparent))

        pairitem = QTreeWidgetItem()
        pairitem.setText(0, firstpairtext)
        pairitem.setText(1, secondpairtext)
        self.pairs.addTopLevelItem(pairitem)
        #self.get_pairs()

    def get_pairs(self):
        """returns  1. a dictionary of selected bands created with the datatreeview widget, 2. a list of featurebands,
         3. a list of texts from the items in the widgets (dataset and band number) """
        bandsdict = self.dataview.collectinput()[0]
        print (bandsdict)
        datasets = self.dataview.collectinput()[1]
        selectedbands = self.dataview.collectinput()[2]
        print(selectedbands)
        featurebands = []
        feturebandstext = []
        for i in range(self.pairs.topLevelItemCount()):
            item = self.pairs.topLevelItem(i)
            item1text = item.text(0)
            item2text = item.text(1)
            # get the list of the items from the widget
            feturebandstext += [[item1text, item2text]]
            band1 = bandsdict[item1text]
            band2 = bandsdict[item2text]
            # get the pair of bands by indexes
            pair = [selectedbands.index(band1), selectedbands.index(band2)]
            featurebands += [pair]
        print(selectedbands)
        print(featurebands)
        return [selectedbands, featurebands,feturebandstext]
예제 #34
0
class MainWindow(QWidget):
    Id, Password = range(2)
    CONFIG_FILE = 'config'

    def __init__(self):
        super().__init__()
        with open(self.CONFIG_FILE, 'a'):
            pass
        self.init()

    def init(self):
        # ------ initUI
        self.resize(555, 245)
        self.setFixedSize(555, 245)
        self.center()
        self.setWindowTitle('Portal Connector')
        self.setWindowIcon(QIcon('gao.ico'))
        self.backgroundRole()
        palette1 = QPalette()
        palette1.setColor(self.backgroundRole(), QColor(250, 250,
                                                        250))  # 设置背景颜色

        self.setPalette(palette1)

        # ------setLeftWidget

        self.dataGroupBox = QGroupBox("Saved", self)
        self.dataGroupBox.setGeometry(10, 10, 60, 20)
        self.dataGroupBox.setStyleSheet(MyGroupBox)

        self.model = QStandardItemModel(0, 2, self)
        self.model.setHeaderData(self.Id, Qt.Horizontal, "Id")
        self.model.setHeaderData(self.Password, Qt.Horizontal, "Pw")

        self.dataView = QTreeView(self)
        self.dataView.setGeometry(10, 32, 255, 150)
        self.dataView.setRootIsDecorated(False)
        self.dataView.setAlternatingRowColors(True)
        self.dataView.setModel(self.model)
        self.dataView.setStyleSheet(MyTreeView)

        save_btn = QPushButton('Save', self)
        save_btn.setGeometry(15, 195, 100, 35)
        save_btn.setStyleSheet(MyPushButton)

        delete_btn = QPushButton('Delete', self)
        delete_btn.setGeometry(135, 195, 100, 35)
        delete_btn.setStyleSheet(MyPushButton)

        # ------ setRightWidget

        username = QLabel('Id:', self)
        username.setGeometry(300, 45, 50, 30)
        username.setStyleSheet(MyLabel)

        self.username_edit = QLineEdit(self)
        self.username_edit.setGeometry(350, 40, 190, 35)
        self.username_edit.setStyleSheet(MyLineEdit)

        password = QLabel('Pw:', self)
        password.setGeometry(300, 100, 50, 30)
        password.setStyleSheet(MyLabel)

        self.password_edit = QLineEdit(self)
        self.password_edit.setGeometry(350, 95, 190, 35)
        self.password_edit.setStyleSheet(MyLineEdit)

        status_label = QLabel('Result:', self)
        status_label.setGeometry(295, 150, 70, 30)
        status_label.setStyleSheet(UnderLabel)

        self.status = QLabel('Disconnect', self)
        self.status.setGeometry(360, 150, 190, 30)
        self.status.setStyleSheet(UnderLabel)

        connect_btn = QPushButton('Connect', self)
        connect_btn.setGeometry(320, 195, 100, 35)
        connect_btn.setStyleSheet(MyPushButton)

        test_btn = QPushButton('Test', self)
        test_btn.setGeometry(440, 195, 100, 35)
        test_btn.setStyleSheet(MyPushButton)

        # ------setTabOrder

        self.setTabOrder(self.username_edit, self.password_edit)
        self.setTabOrder(self.password_edit, connect_btn)
        self.setTabOrder(connect_btn, test_btn)

        # ------setEvent

        self.dataView.mouseDoubleClickEvent = self.set_text
        self.dataView.mousePressEvent = self.set_focus
        delete_btn.clicked.connect(self.removeItem)
        connect_btn.clicked.connect(self.connect_clicked)
        save_btn.clicked.connect(self.save_infomation)
        test_btn.clicked.connect(self.test_network)

        self.readItem(self.CONFIG_FILE)
        self.connect_clicked()
        self.show()

    def connect_clicked(self):
        result = connect_portal(self.username_edit.text(),
                                self.password_edit.text())
        self.status.setText(result)

    def save_infomation(self):
        if self.username_edit.text() and self.password_edit.text():
            try:
                selected = self.dataView.selectedIndexes()[0].row()
                self.modifyItem(selected)
            except IndexError:
                self.addItem(self.username_edit.text(),
                             self.password_edit.text())

    def test_network(self):
        result = test_public()
        self.status.setText(result)

    def set_text(self, event=None):
        try:
            self.username_edit.setText(
                self.dataView.selectedIndexes()[0].data())
            self.password_edit.setText(
                self.dataView.selectedIndexes()[1].data())
        except IndexError:
            pass

    def set_focus(self, event):
        index = self.dataView.indexAt(event.pos())
        if not index.isValid():
            self.dataView.clearSelection()
        else:
            self.dataView.setCurrentIndex(index)

    def readItem(self, filename):
        with open(filename, 'r') as f:
            for line in f.readlines():
                self.addItem(*(line.split()))

        self.dataView.setCurrentIndex(self.dataView.indexAt(QPoint(1, 1)))
        self.set_text()

    def addItem(self, username, password):
        self.model.insertRow(0)
        self.model.setData(self.model.index(0, self.Id), username)
        self.model.setData(self.model.index(0, self.Password), password)
        self.save_to_file()

    def modifyItem(self, row):
        self.model.setData(self.model.index(row, self.Id),
                           self.username_edit.text())
        self.model.setData(self.model.index(row, self.Password),
                           self.password_edit.text())
        self.save_to_file()

    def removeItem(self):
        try:
            self.model.removeRow(self.dataView.selectedIndexes()[0].row())
            self.save_to_file()
        except IndexError:
            pass

    def save_to_file(self):
        with open(self.CONFIG_FILE, 'w') as f:
            for x in range(self.model.rowCount()):
                for y in range(self.model.columnCount()):
                    f.write(self.model.data(self.model.index(x, y)) + " ")
                f.write("\n")

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
예제 #35
0
class mainForm(QMainWindow):
    get_template_filename = pyqtSignal(str)
    exit_program = pyqtSignal()
    camera_changed = pyqtSignal(int)

    def __init__(self):
        super().__init__()
        self.stream = None
        self.image_difference_thread = None
        self.template_set = False
        self._countour_max_tresh = 10
        self._countour_min_tresh = 1
        self._transparency_max = 10
        self._transparency_min = 0
        self._countour_gamma_max = 10
        self._countour_gamma_min = 1
        self._color_max = 255
        self._color_min = 0
        self.screen_resolution = None
        self.grid_layout = None
        self.output_picture = None
        self.initUI()
        self.init_image_difference()
        if self.webcam_switcher.count() > 0:
            self.stream = streamCapture(self.webcam_switcher.itemData(self.webcam_switcher.currentIndex()))
            self.stream.getframe.connect(self.mat2qimage)
            self.webcam_switcher.currentIndexChanged.connect(self.camera_switcher_index_changed)
            self.camera_changed.connect(self.stream.reopenStream)
            self.stream.start()
            self.exit_program.connect(self.stream.exit)

    ### отрисовка интерфейса
    def initUI(self):
        self.screen_resolution = QApplication.desktop().screenGeometry()
        # self.resize(self.screen_resolution.size())
        # self.move(self.screen_resolution.left(), self.screen_resolution.top())
        self.grid_layout = QGridLayout()

        self.central_widget = QWidget()
        self.central_widget.setLayout(self.grid_layout)
        self.central_widget.setMaximumSize(self.screen_resolution.width() // 4 * 3,
                                           self.screen_resolution.height() // 4 * 3)
        self.setCentralWidget(self.central_widget)

        self.camera_label = QLabel("Camera:")
        self.grid_layout.addWidget(self.camera_label, 0, 0, 1, 1, Qt.AlignmentFlag.AlignHCenter)

        self.webcam_switcher = QComboBox()
        self.detect_webcam_devices(self.webcam_switcher)
        self.grid_layout.addWidget(self.webcam_switcher, 0, 1, 1, 3)

        self.output_picture = QLabel()
        self.grid_layout.addWidget(self.output_picture, 1, 0, 1, 4)

        ### creating right dock
        self.right_dock_layout = QVBoxLayout()

        self.right_dock_widget = QDockWidget()
        self.right_dock_widget.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetMovable | QDockWidget.DockWidgetFeature.DockWidgetFloatable)
        self.right_dock_widget.setMinimumSize(self.screen_resolution.width() // 4, self.screen_resolution.height())
        right_dock = QWidget(self.right_dock_widget)
        right_dock.setMinimumSize(self.screen_resolution.width() // 4, self.screen_resolution.height())
        right_dock.setLayout(self.right_dock_layout)

        template_label = QLabel("Templates")
        template_label.setMinimumSize(50, 25)
        self.right_dock_layout.addWidget(template_label)

        self.filter_template_edit = QLineEdit()
        self.filter_template_edit.setPlaceholderText("Filter (Ctr + Alt + f)")
        template_label.setMinimumSize(90, 25)
        self.filter_template_edit.setStyleSheet(
            "background-image: url(../image_difference/icons/searchIcon.png); background-repeat: no-repeat; background-position: right;")

        self.right_dock_layout.addWidget(self.filter_template_edit)

        self.file_system_model = QFileSystemModel()
        self.file_system_model.setFilter(QDir.Filter.AllDirs | QDir.Filter.NoDotAndDotDot | QDir.Filter.AllEntries)
        self.file_system_model.setRootPath(QDir.currentPath())

        self.directory_tree_view = QTreeView()
        self.directory_tree_view.setModel(self.file_system_model)
        self.directory_tree_view.setMinimumSize(200, 100)
        self.directory_tree_view.hideColumn(1)
        self.directory_tree_view.hideColumn(2)
        self.directory_tree_view.hideColumn(3)
        # self.directory_tree_view.sortByColumn(0)
        self.directory_tree_view.setSortingEnabled(True)
        self.directory_tree_view.doubleClicked.connect(self.load_template)
        self.directory_tree_view.setRootIndex(self.file_system_model.index("../image_difference/"))

        self.right_dock_layout.addWidget(self.directory_tree_view)

        self.load_template_button = QPushButton("Select Template")
        self.load_template_button.setMaximumSize(self.screen_resolution.width() // 4 - 30, 30)
        self.load_template_button.clicked.connect(self.load_template)

        self.right_dock_layout.addWidget(self.load_template_button)

        self.create_template_button = QPushButton("Create Template")
        self.create_template_button.setMaximumSize(self.screen_resolution.width() // 4 - 30, 30)
        self.create_template_button.clicked.connect(self.create_template)

        self.right_dock_layout.addWidget(self.create_template_button)

        self.template_image_widget = QWidget()
        self.template_image_widget.setMinimumSize(self.screen_resolution.width() // 4 - 20,
                                                  self.screen_resolution.width() // 4 - 10)

        self.template_image_back = QLabel(self.template_image_widget)
        self.template_image_back.resize(self.screen_resolution.width() // 4 - 20,
                                        self.screen_resolution.width() // 4 - 10)
        pix = QPixmap(self.template_image_back.size())
        pix.fill(Qt.lightGray)
        rect = QRectF(0.0, 0.0, self.template_image_back.size().width(), self.template_image_back.size().height())
        painter = QPainter()
        painter.begin(pix)
        painter.setRenderHints(QPainter.Antialiasing, True)
        path = QPainterPath()
        path.addRoundedRect(rect, 5.0, 5.0)
        painter.drawPath(path)
        painter.end()
        self.template_image_back.setPixmap(pix)

        self.template_image = QLabel(self.template_image_widget)
        self.template_image.move(5, 5)
        self.template_image.resize(self.screen_resolution.width() // 4 - 30, self.screen_resolution.width() // 4 - 30)

        self.template_image_text = QLabel(self.template_image_widget, text="Current Template")
        self.template_image_text.setStyleSheet("font-weight: bold")
        self.template_image_text.move(self.screen_resolution.width() // 8 - 65, 20)

        self.right_dock_layout.addWidget(self.template_image_widget)

        self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.right_dock_widget)

        ### creating bottom dock
        self.bottom_dock_layout = QGridLayout()
        self.bottom_dock_layout.setSpacing(10)

        self.bottom_dock_widget = QDockWidget()
        self.bottom_dock_widget.setMinimumSize(self.screen_resolution.width() // 4 * 3 - 10,
                                               self.screen_resolution.height() // 4 - 10)
        bottom_dock = QWidget(self.bottom_dock_widget)
        bottom_dock.setMinimumSize(self.screen_resolution.width() // 4 * 3 - 20,
                                   self.screen_resolution.height() // 4 - 20)
        bottom_dock.move(10, 10)
        bottom_dock.setLayout(self.bottom_dock_layout)

        settings_label = QLabel("Settings:")
        self.bottom_dock_layout.addWidget(settings_label, 0, 0, 1, 2,
                                          Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)

        countour_tresh_label = QLabel("Countour Tresh:")
        self.bottom_dock_layout.addWidget(countour_tresh_label, 1, 0, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.countour_tresh_slider = QSlider(Qt.Orientation.Horizontal)
        self.countour_tresh_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.countour_tresh_slider.setRange(self._countour_min_tresh, self._countour_max_tresh)
        self.countour_tresh_slider.setValue(2)
        self.bottom_dock_layout.addWidget(self.countour_tresh_slider, 1, 1, 1, 1, Qt.AlignmentFlag.AlignTop)

        transparency_weight_label = QLabel("Transparency:")
        self.bottom_dock_layout.addWidget(transparency_weight_label, 2, 0, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.transparency_weight_slider = QSlider(Qt.Orientation.Horizontal)
        self.transparency_weight_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.transparency_weight_slider.setValue(6)
        self.transparency_weight_slider.setRange(self._transparency_min, self._transparency_max)
        self.bottom_dock_layout.addWidget(self.transparency_weight_slider, 2, 1, 1, 1, Qt.AlignmentFlag.AlignTop)

        countour_gamma_label = QLabel("Countour Gamma:")
        self.bottom_dock_layout.addWidget(countour_gamma_label, 3, 0, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.countour_gamma_slider = QSlider(Qt.Orientation.Horizontal)
        self.countour_gamma_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.countour_gamma_slider.setValue(8)
        self.countour_gamma_slider.setRange(self._countour_gamma_min, self._countour_gamma_max)
        self.bottom_dock_layout.addWidget(self.countour_gamma_slider, 3, 1, 1, 1, Qt.AlignmentFlag.AlignTop)

        ### right side of settings
        countour_color_label = QLabel("Countour Color:")
        self.bottom_dock_layout.addWidget(countour_color_label, 0, 2, 1, 2,
                                          Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)

        r_color_label = QLabel("R:")
        self.bottom_dock_layout.addWidget(r_color_label, 1, 2, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.r_color_slider = QSlider(Qt.Orientation.Horizontal)
        self.r_color_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.r_color_slider.setRange(self._color_min, self._color_max)
        self.r_color_slider.setValue(255)
        self.bottom_dock_layout.addWidget(self.r_color_slider, 1, 3, 1, 1, Qt.AlignmentFlag.AlignTop)

        g_color_label = QLabel("G:")
        self.bottom_dock_layout.addWidget(g_color_label, 2, 2, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.g_color_slider = QSlider(Qt.Orientation.Horizontal)
        self.g_color_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.g_color_slider.setRange(self._color_min, self._color_max)
        self.bottom_dock_layout.addWidget(self.g_color_slider, 2, 3, 1, 1, Qt.AlignmentFlag.AlignTop)

        b_color_label = QLabel("B:")
        self.bottom_dock_layout.addWidget(b_color_label, 3, 2, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.b_color_slider = QSlider(Qt.Orientation.Horizontal)
        self.b_color_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
        self.b_color_slider.setRange(self._color_min, self._color_max)
        self.bottom_dock_layout.addWidget(self.b_color_slider, 3, 3, 1, 1, Qt.AlignmentFlag.AlignTop)

        self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.bottom_dock_widget)

        self.setCorner(Qt.Corner.BottomRightCorner, Qt.DockWidgetArea.RightDockWidgetArea)

    def init_image_difference(self):
        self.image_difference_thread = imageDifference()
        self.get_template_filename.connect(self.image_difference_thread.set_template_image)
        self.countour_tresh_slider.valueChanged.connect(self.image_difference_thread.set_countour_tresh_value)
        self.transparency_weight_slider.valueChanged.connect(self.image_difference_thread.set_transparency_weight_value)
        self.countour_gamma_slider.valueChanged.connect(self.image_difference_thread.set_countour_gamma_value)
        self.r_color_slider.valueChanged.connect(self.image_difference_thread.set_countour_color_r)
        self.g_color_slider.valueChanged.connect(self.image_difference_thread.set_countour_color_g)
        self.b_color_slider.valueChanged.connect(self.image_difference_thread.set_countour_color_b)
        self.image_difference_thread.output_image_defference.connect(self.mat2qimage)
        self.image_difference_thread.set_template_picture.connect(self.set_template_picture)
        self.image_difference_thread.start()

    def detect_webcam_devices(self, combo_box):
        _video_capture = cv2.VideoCapture()
        _dev_id = 0
        while (_dev_id < 3):
            if _video_capture.open(_dev_id):
                combo_box.addItem("Device #" + str(_dev_id + 1), _dev_id)
                _dev_id += 1
            else:
                _dev_id += 1
        _video_capture.release()

    def load_template(self):
        index = self.directory_tree_view.selectedIndexes()[0]
        if not QFileInfo(self.file_system_model.filePath(index)).isDir():
            # print("load template, path:", self.file_system_model.filePath(index))
            self.get_template_filename.emit(self.file_system_model.filePath(index))

    def create_template(self):
        if self.stream is not None:
            template_to_save = self.stream.get_current_frame()
            cv2.imwrite("../image_difference/examples/template.jpg", template_to_save)
            print("create template")

    pyqtSlot(np.ndarray)

    def mat2qimage(self, image):
        rgbImage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        rgbImage = cv2.resize(rgbImage, (self.output_picture.size().width(),
                                         self.output_picture.size().height()))
        # rgbImage = imutils.resize(rgbImage, height=self.output_picture.height())
        h, w, ch = rgbImage.shape
        bytesPerLine = ch * w
        result_image = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
        self.output_picture.setPixmap(QPixmap.fromImage(result_image))

    pyqtSlot(np.ndarray)

    def set_template_picture(self, image):
        rgbImage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # rgbImage = cv2.resize(rgbImage, (self.template_image.size().width(),
        #                                  self.template_image.size().height()))
        rgbImage = imutils.resize(rgbImage, self.template_image.size().width())
        h, w, ch = rgbImage.shape
        bytesPerLine = ch * w
        result_image = QImage(rgbImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
        self.template_image.setPixmap(QPixmap.fromImage(result_image))
        if not self.template_set:
            self.template_set = True
            self.stream.getframe.disconnect(self.mat2qimage)
            self.stream.getframe.connect(self.image_difference_thread.get_image)
            self.image_difference_thread.output_image_defference.connect(self.mat2qimage)

    def camera_switcher_index_changed(self, index):
        self.camera_changed.emit(self.webcam_switcher.itemData(index))
        print("current index:", index)
        print("item data:", self.webcam_switcher.itemData(index))

    def closeEvent(self, event):
        self.exit_program.emit()
        event.accept()
class AnalyzeTab(QWidget):

    analyzeDone = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject',
                             'PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject',
                             'PyQt_PyObject')

    def __init__(self):
        super().__init__()

        # Layouts
        self.mainLayout = QHBoxLayout()
        self.lVbox = QVBoxLayout()
        self.lHbox = QHBoxLayout()
        self.lHbox_top = QHBoxLayout()
        self.rVbox = QVBoxLayout()
        self.rHbox = QHBoxLayout()
        self.rVbox2 = QVBoxLayout()
        self.stack = QStackedWidget()
        self.stack_Vbox = QVBoxLayout()
        self.stack_Hbox1 = QHBoxLayout()
        self.stack_Hbox2 = QHBoxLayout()
        self.hSplit = QSplitter(Qt.Horizontal)
        self.hSplit.setFrameShape(QFrame.StyledPanel)
        self.vSplit = QSplitter(Qt.Vertical)
        self.vSplit.setFrameShape(QFrame.StyledPanel)
        self.mainLayout.addLayout(self.lVbox, 1)
        self.mainLayout.addLayout(self.rVbox, 3)

        # Setup file browser
        self.fileModel = QFileSystemModel()
        self.fileModel.setNameFilters(['*.wav'])
        self.fileModel.setRootPath(QDir.currentPath())
        self.fileTree = QTreeView()
        self.fileTree.setModel(self.fileModel)
        self.fileTree.setRootIndex(self.fileModel.index(r'./'))
        self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection)
        self.fileTree.setColumnHidden(2, True)
        self.fileTree.setColumnHidden(1, True)
        self.rootDirEdit = QLineEdit(os.path.dirname(__file__))
        self.rootDirEdit.returnPressed.connect(self.on_edit_root)
        self.browseBtn = QPushButton('Browse')
        self.browseBtn.clicked.connect(self.on_browse)
        self.lHbox_top.addWidget(self.rootDirEdit, 3)
        self.lHbox_top.addWidget(self.browseBtn, 1)

        # Setup Canvas
        self.canvas = PlotCanvas(self)

        self.analyzeDone.connect(self.canvas.plot)
        self._analyze = lambda _: self.analyze(self.fileTree.selectedIndexes())
        self.analyzeBtn = QPushButton('Analyze')
        self.analyzeBtn.clicked.connect(self._analyze)

        ## BATCH ANALYSIS CONTROLS ##

        self.batchAnalyzeChk = QCheckBox('Batch Analysis')
        self.dataTable = QTableWidget()
        self.batchCtrlBox = QGroupBox("Batch Analysis")
        self.batchCtrlBox.setLayout(self.stack_Vbox)

        # Analysis Mode
        self.modeGroup = QButtonGroup()
        self.modeBox = QGroupBox('Analysis Mode')
        self.modeBox.setLayout(self.stack_Hbox1)
        self.stack_Vbox.addWidget(self.modeBox)
        self.wavAnalysisChk = QCheckBox('Wav analysis')
        self.wavAnalysisChk.setChecked(True)
        self.calibrationLocationBox = QComboBox()
        self.calibrationLocationBox.addItems([str(n) for n in range(1, 11)])
        self.calibrationCurveChk = QCheckBox('Calibration Curve')
        self.calibrationCurveChk.toggled.connect(
            lambda state: self.calibrationLocationBox.setEnabled(state))
        self.calibrationCurveChk.setChecked(False)
        self.stack_Hbox1.addWidget(self.wavAnalysisChk, 3)
        self.stack_Hbox1.addWidget(self.calibrationCurveChk, 3)
        self.stack_Hbox1.addWidget(QLabel('Location: '), 1)
        self.stack_Hbox1.addWidget(self.calibrationLocationBox, 1)
        self.stack_Vbox.addLayout(self.stack_Hbox1)
        self.modeGroup.addButton(self.wavAnalysisChk)
        self.modeGroup.addButton(self.calibrationCurveChk)
        self.modeGroup.setExclusive(True)

        # Outputs
        self.outputCtrlBox = QGroupBox('Outputs')
        self.outputCtrlBox.setLayout(self.stack_Hbox2)
        self.stack_Vbox.addWidget(self.outputCtrlBox)
        self.toCSVchk = QCheckBox('.csv')
        self.toJSONchk = QCheckBox('.json')
        self.toCSVchk.stateChanged.connect(lambda _: self.update_settings(
            'output', 'toCSV', self.toCSVchk.isChecked()))
        self.toJSONchk.stateChanged.connect(lambda _: self.update_settings(
            'output', 'toJSON', self.toJSONchk.isChecked()))

        self.stack_Hbox2.addWidget(self.toCSVchk)
        self.stack_Hbox2.addWidget(self.toJSONchk)
        self.stack_Vbox.addLayout(self.stack_Hbox2)

        self.stack.addWidget(self.dataTable)
        self.stack.addWidget(self.batchCtrlBox)
        self.stack.setCurrentWidget(self.dataTable)
        self.stack.show()
        self.batchAnalyzeChk.stateChanged.connect(self.toggle_stack)
        self.batchAnalyzeChk.setChecked(False)
        self.stack_Vbox.addStretch()

        ## PROCESSING CONTROLS ##
        self.processControls = QGroupBox('Signal Processing')
        self.tOffsetSlider = QSlider(Qt.Horizontal, )
        self.tOffsetSlider.setMinimum(1)
        self.tOffsetSlider.setMaximum(100)
        self.tOffsetSlider.setValue(100)
        self.tOffsetSlider.setTickPosition(QSlider.TicksBelow)
        self.tOffsetSlider.setTickInterval(10)
        self.tOffsetSlider.valueChanged.connect(
            lambda val: self.update_settings('processing', 'tChop', val))
        self.tOffsetLayout = QHBoxLayout()
        self.tOffsetSlider_Box = QGroupBox(
            f'Chop Signal - {self.tOffsetSlider.value()}%')
        self.tOffsetSlider.valueChanged.connect(
            lambda val: self.tOffsetSlider_Box.setTitle(f'Chop Signal - {val}%'
                                                        ))
        self.tOffsetSlider_Box.setLayout(self.tOffsetLayout)
        self.tOffsetLayout.addWidget(self.tOffsetSlider)

        self.nFFTSlider = QSlider(Qt.Horizontal, )
        self.nFFTSlider.setMinimum(1)
        self.nFFTSlider.setMaximum(16)
        self.nFFTSlider.setValue(1)
        self.nFFTSlider.setTickPosition(QSlider.TicksBelow)
        self.nFFTSlider.setTickInterval(2)
        self.nFFTSlider.valueChanged.connect(
            lambda val: self.update_settings('processing', 'detail', val))
        self.nFFTLayout = QHBoxLayout()
        self.nFFTSlider.valueChanged.connect(
            lambda val: self.nFFTSlider_Box.setTitle(f'FFT Size - {val*65536}'
                                                     ))
        self.nFFTSlider_Box = QGroupBox(
            f'FFT Size - {self.nFFTSlider.value()*65536}')
        self.nFFTSlider_Box.setLayout(self.nFFTLayout)
        self.nFFTLayout.addWidget(self.nFFTSlider)

        self.rVbox2.addWidget(self.tOffsetSlider_Box)
        self.rVbox2.addWidget(self.nFFTSlider_Box)
        self.processControls.setLayout(self.rVbox2)

        self.lVbox.addLayout(self.lHbox_top, 1)
        self.lVbox.addWidget(self.fileTree, 7)
        self.lVbox.addLayout(self.lHbox, 1)
        self.lHbox.addWidget(self.analyzeBtn, 2)
        self.lHbox.addWidget(self.batchAnalyzeChk, 1)
        self.vSplit.addWidget(self.canvas)
        self.vSplit.addWidget(self.hSplit)
        self.rVbox.addWidget(self.vSplit)
        self.hSplit.addWidget(self.stack)
        self.hSplit.addWidget(self.processControls)

        self.settings = {
            'processing': {
                'tChop': self.tOffsetSlider.value(),
                'detail': self.nFFTSlider.value()
            },
            'output': {
                'toCSV': self.toCSVchk.isChecked(),
                'toJSON': self.toJSONchk.isChecked()
            }
        }
        self.setLayout(self.mainLayout)

    def on_browse(self):
        # Browse to file tree root directory
        options = QFileDialog.Options()
        path = QFileDialog.getExistingDirectory(
            self, caption="Choose root directory", options=options)
        self.rootDirEdit.setText(path)
        self.fileTree.setRootIndex(self.fileModel.index(path))

    def on_edit_root(self):
        # Update the file tree root directory
        self.fileTree.setRootIndex(
            self.fileModel.index(self.rootDirEdit.text()))

    def update_settings(self, category, setting, value):
        # Update settings and reprocess FFT if in single analysis mode
        self.settings[category][setting] = value

        if category == 'processing' and self.fileTree.selectedIndexes():
            self.analyze(self.fileTree.selectedIndexes())

    def toggle_stack(self, state):
        if state == 2:
            self.stack.setCurrentWidget(self.batchCtrlBox)
            self.fileTree.setSelectionMode(QAbstractItemView.MultiSelection)
        else:
            self.stack.setCurrentWidget(self.dataTable)
            self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection)

    def analyze(self, filePaths):
        if self.batchAnalyzeChk.isChecked():
            if self.wavAnalysisChk.isChecked():
                self.batch_analyze_wav(
                    [self.fileModel.filePath(path) for path in filePaths[::4]])
            if self.calibrationCurveChk.isChecked():
                self.generate_calibration_curve(
                    [self.fileModel.filePath(path) for path in filePaths[::4]])

        else:
            if os.path.isdir(self.fileModel.filePath(
                    filePaths[0])) or len(filePaths) > 4:
                QMessageBox.information(
                    self, 'Error',
                    'Please select only 1 file for single analysis.')
                return
            self.single_analyze_wav(self.fileModel.filePath(filePaths[0]))

    def single_analyze_wav(self, filePath):
        """
        Do an FFT and find peaks on a single wav file

        :param filePath: file path to .wav file
        """

        tChopped, vChopped, fVals,\
        powerFFT, peakFreqs, peakAmps = Utils.AnalyzeFFT(filePath, tChop=self.settings['processing']['tChop'],
                                                                   detail=self.settings['processing']['detail'])

        self.analyzeDone.emit(tChopped, vChopped, fVals, powerFFT, peakFreqs,
                              peakAmps, filePath)
        self.update_table(peakFreqs, peakAmps)

    def batch_analyze_wav(self, filePaths):
        """
        Perform a batch analysis of many .wav files. Outputs FFTs and peaks in .csv or .json format

        :param filePaths: A list of folders containing the .wav files to be analyzed
        """

        toCSV = self.settings['output']['toCSV']
        toJSON = self.settings['output']['toJSON']

        start = time.time()

        fileTotal = 0
        for path in filePaths:
            if os.path.isdir(path):
                blockName = os.path.basename(path)
                print(f'Block: {blockName}')

                files = [
                    os.path.join(path, file) for file in os.listdir(path)
                    if '.wav' in file
                ]
                fileTotal += len(files)

                if toCSV:
                    if not os.path.exists(os.path.join(path,
                                                       'fft_results_csv')):
                        os.makedirs(os.path.join(path, 'fft_results_csv'))
                    resultFilePath = os.path.join(path, 'fft_results_csv')

                    print('Processing FFTs...')
                    with multiprocessing.Pool(processes=4) as pool:
                        results = pool.starmap(
                            Utils.AnalyzeFFT,
                            zip(files, itertools.repeat(True),
                                itertools.repeat(True)))
                    results = [
                        result for result in results if result is not None
                    ]

                    peaks = [result[0] for result in results]
                    ffts = [result[1] for result in results]

                    print('Writing to .csv...')
                    resultFileName = os.path.join(resultFilePath,
                                                  f'{blockName}_Peaks.csv')
                    peakFrames = pd.concat(peaks)
                    peakFrames.to_csv(resultFileName, index=False, header=True)
                    with concurrent.futures.ThreadPoolExecutor(
                            max_workers=16) as executor:
                        executor.map(self.multi_csv_write, ffts)

                if toJSON:
                    if not os.path.exists(
                            os.path.join(path, 'fft_results_json')):
                        os.makedirs(os.path.join(path, 'fft_results_json'))
                    print(os.path.join(path, 'fft_results_json'))

                    print('Processing FFTs...')
                    with multiprocessing.Pool(processes=4) as pool:
                        results = pool.starmap(
                            Utils.AnalyzeFFT,
                            zip(files, itertools.repeat(True),
                                itertools.repeat(False),
                                itertools.repeat(True)))
                        results = [
                            result for result in results if result is not None
                        ]

                    print('Writing to .json...')
                    with concurrent.futures.ThreadPoolExecutor(
                            max_workers=16) as executor:
                        executor.map(self.multi_json_write, results)

        end = time.time()
        print(
            f'**Done!** {len(filePaths)} blocks with {fileTotal} files took {round(end-start, 1)}s'
        )

    def generate_calibration_curve(self, filePaths):
        """
        Attempt to fit an exponential function to a set of data points (x: Peak Frequency, y: Compressive strength)
        provided in JSON format.

        ex:{
              "shape": "2-Hole",
              "testData": {
                "location": "1",
                "strength": 3.092453552,
                "peaks": [
                  {
                    "frequency": 1134.5561082797967,
                    "magnitude": 0.349102384777402
                  }]
              },
              "waveData": [...],
              "freqData": [...]
        }

        Plot the curve, data points and give the function if successful.

        ** NOTE ** This function is still experimental and a bit buggy. Sometimes the scipy.optimize curve_fit won't
        converge with the initial guess given for the coeffecients. You're probably better off writing your own code.

        :param filePaths: A list of folders containing .jsons
        """
        # Strike Location
        location = self.calibrationLocationBox.currentText()

        # Function to fit to the data
        exp_f = lambda x, a, b, c: a * np.exp(b * x) + c

        # Threaded method for opening all the .jsons and fitting
        calibCurve = ThreadedCalibrationCurve(filePaths, location, exp_f)
        progressDialog = QProgressDialog(
            f'Gettings samples for location: {location}', None, 0,
            len(filePaths), self)
        progressDialog.setModal(True)
        calibCurve.blocksSearched.connect(progressDialog.setValue)
        try:
            peakFreqs, strengths, popt, pcov, fitX = calibCurve.run()
        except Exception as e:
            QMessageBox.information(self, 'Error', e)
            return

        # Calculate R Squared
        residuals = strengths - exp_f(peakFreqs, *popt)
        ss_res = np.sum(residuals**2)
        ss_tot = np.sum((strengths - np.mean(strengths))**2)
        r_squared = 1 - (ss_res / ss_tot)

        # Plot Results
        fig = Figure()
        plt.scatter(peakFreqs, strengths)
        plt.plot(fitX, exp_f(fitX, *popt), '-k')
        ax = plt.gca()
        plt.text(
            0.05,
            0.9,
            f'y = {round(popt[0],3)}*exp({round(popt[1], 5)}x) + {round(popt[2], 3)}\n',
            ha='left',
            va='center',
            transform=ax.transAxes)
        plt.text(0.05,
                 0.85,
                 f'R^2 = {round(r_squared,3)}',
                 ha='left',
                 va='center',
                 transform=ax.transAxes)

        plt.title(f'Calibration Curve, Location: {location}')
        plt.xlabel('Frequency (Hz)')
        plt.ylabel('Compressive Strength (MPa)')

        plt.show()

    def multi_csv_write(self, frameTuple):
        frame = frameTuple[1]
        wavPath = frameTuple[0]

        resultFileDir = os.path.join(os.path.dirname(wavPath),
                                     'fft_results_csv')
        resultFileName = os.path.basename(wavPath) + '_fft.csv'
        resultFilePath = os.path.join(resultFileDir, resultFileName)

        frame.to_csv(resultFilePath, index=False, header=True)

    def multi_json_write(self, results):
        data = results[0]
        wavPath = results[1]

        jsonFileDir = os.path.join(os.path.dirname(wavPath),
                                   'fft_results_json')
        resultFileName = os.path.basename(wavPath) + '_fft.json'
        resultFilePath = os.path.join(jsonFileDir, resultFileName)

        # blockName = os.path.basename(os.path.dirname(wavPath))
        # blockDir = os.path.join(jsonFileDir, blockName)
        # if not os.path.exists(blockDir):
        #     os.makedirs(blockDir)
        # print(resultFilePath)
        with open(resultFilePath, 'w') as f:
            json.dump(data, f, indent=2)

    def update_table(self, peakFreqs, peakAmps):
        """

        :param peakFreqs:
        :param peakAmps:
        :return:
        """
        self.dataTable.setRowCount(2)
        self.dataTable.setColumnCount(len(peakFreqs) + 1)

        self.dataTable.setItem(0, 0, QTableWidgetItem("Frequencies: "))
        self.dataTable.setItem(1, 0, QTableWidgetItem("Powers: "))

        for col, freq in enumerate(peakFreqs, start=1):
            self.dataTable.setItem(0, col, QTableWidgetItem(str(round(freq))))
        for col, power in enumerate(peakAmps, start=1):
            item = QTableWidgetItem(str(round(power, 3)))
            if power > 0.7:
                item.setBackground(QColor(239, 81, 28))
            elif power >= 0.4:
                item.setBackground(QColor(232, 225, 34))
            elif power < 0.4:
                item.setBackground(QColor(113, 232, 34))
            self.dataTable.setItem(1, col, item)