예제 #1
0
class LibraryCatalogDialog(QDialog):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["libraryCatalog"])
        self.setMinimumSize(700, 500)
        self.setupVariables()
        self.setupUI()

    def setupVariables(self):
        self.isUpdating = False
        self.catalogEntryId = None
        self.localCatalog = CatalogUtil.loadLocalCatalog()
        self.remoteCatalog = gitHubRepoCacheData
        self.localCatalogData = self.getLocalCatalogItems()
        self.remoteCatalogData = self.getRemoteCatalogItems()
        self.location = "local"
        self.textButtonStyle = "QPushButton {background-color: #333972; color: white;} QPushButton:hover {background-color: #333972;} QPushButton:pressed { background-color: #515790;}"

    def setupUI(self):
        mainLayout = QVBoxLayout()

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(QLabel(config.thisTranslation["menu5_search"]))
        self.filterEntry = QLineEdit()
        self.filterEntry.setClearButtonEnabled(True)
        self.filterEntry.textChanged.connect(self.resetItems)
        filterLayout.addWidget(self.filterEntry)
        mainLayout.addLayout(filterLayout)

        self.searchTypeBox = QGroupBox("")
        locationLayout = QHBoxLayout()
        self.localRadioButton = QRadioButton("Local")
        self.localRadioButton.setChecked(True)
        self.localRadioButton.toggled.connect(
            lambda: self.setLocation("local"))
        locationLayout.addWidget(self.localRadioButton)
        self.remoteRadioButton = QRadioButton("Remote")
        self.remoteRadioButton.toggled.connect(
            lambda: self.setLocation("remote"))
        locationLayout.addWidget(self.remoteRadioButton)
        self.searchTypeBox.setLayout(locationLayout)
        mainLayout.addWidget(self.searchTypeBox)

        typesLayout = QHBoxLayout()
        button = QPushButton("All")
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(lambda: self.selectAllTypes(True))
        typesLayout.addWidget(button)
        button = QPushButton("None")
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(lambda: self.selectAllTypes(False))
        typesLayout.addWidget(button)
        self.bookCheckbox = QCheckBox("BOOK")
        self.bookCheckbox.setChecked(True)
        self.bookCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.bookCheckbox)
        self.pdfCheckbox = QCheckBox("PDF")
        self.pdfCheckbox.setChecked(True)
        self.pdfCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.pdfCheckbox)
        self.docxCheckbox = QCheckBox("DOCX")
        self.docxCheckbox.setChecked(True)
        self.docxCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.docxCheckbox)
        self.devotionalCheckbox = QCheckBox("DEVOTIONAL")
        self.devotionalCheckbox.setChecked(True)
        self.devotionalCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.devotionalCheckbox)
        self.commCheckbox = QCheckBox("COMM")
        self.commCheckbox.setChecked(True)
        self.commCheckbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.commCheckbox)
        self.mp3Checkbox = QCheckBox("MP3")
        self.mp3Checkbox.setChecked(True)
        self.mp3Checkbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.mp3Checkbox)
        self.mp4Checkbox = QCheckBox("MP4")
        self.mp4Checkbox.setChecked(True)
        self.mp4Checkbox.stateChanged.connect(self.resetItems)
        typesLayout.addWidget(self.mp4Checkbox)
        mainLayout.addLayout(typesLayout)

        self.dataView = QTableView()
        self.dataView.clicked.connect(self.itemClicked)
        self.dataView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.dataView.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.dataView)
        self.dataView.setModel(self.dataViewModel)
        self.resetItems()
        mainLayout.addWidget(self.dataView)

        buttonLayout = QHBoxLayout()
        self.openButton = QPushButton(config.thisTranslation["open"])
        self.openButton.setEnabled(True)
        self.openButton.setStyleSheet(self.textButtonStyle)
        self.openButton.clicked.connect(self.open)
        buttonLayout.addWidget(self.openButton)
        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setEnabled(False)
        self.downloadButton.clicked.connect(self.download)
        buttonLayout.addWidget(self.downloadButton)
        button = QPushButton(config.thisTranslation["close"])
        button.setStyleSheet(self.textButtonStyle)
        button.clicked.connect(self.close)
        buttonLayout.addWidget(button)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

    def setLocation(self, location):
        self.location = location
        self.resetItems()
        if location == "local":
            self.openButton.setEnabled(True)
            self.openButton.setStyleSheet(self.textButtonStyle)
            self.downloadButton.setEnabled(False)
        else:
            self.openButton.setEnabled(False)
            self.openButton.setStyleSheet("")
            self.downloadButton.setEnabled(True)

    def selectAllTypes(self, value):
        self.pdfCheckbox.setChecked(value)
        self.mp3Checkbox.setChecked(value)
        self.mp4Checkbox.setChecked(value)
        self.bookCheckbox.setChecked(value)
        self.docxCheckbox.setChecked(value)
        self.devotionalCheckbox.setChecked(value)
        self.commCheckbox.setChecked(value)

    def getLocalCatalogItems(self):
        return self.getCatalogItems(self.localCatalog)

    def getRemoteCatalogItems(self):
        return self.getCatalogItems(self.remoteCatalog)

    def getCatalogItems(self, catalog):
        data = {}
        pdfCount = 0
        mp3Count = 0
        mp4Count = 0
        bookCount = 0
        docxCount = 0
        commCount = 0
        lexCount = 0
        devotionalCount = 0
        for filename, type, directory, file, description, repo, installDirectory, sha in catalog:
            id = "UNKNOWN"
            if type == "PDF":
                pdfCount += 1
                id = "{0}-{1}".format(type, pdfCount)
            elif type == "MP3":
                mp3Count += 1
                id = "{0}-{1}".format(type, mp3Count)
            elif type == "MP4":
                mp4Count += 1
                id = "{0}-{1}".format(type, mp4Count)
            elif type == "BOOK":
                bookCount += 1
                id = "{0}-{1}".format(type, bookCount)
            elif type == "DOCX":
                docxCount += 1
                id = "{0}-{1}".format(type, docxCount)
            elif type == "COMM":
                commCount += 1
                id = "{0}-{1}".format(type, commCount)
            elif type == "LEX":
                lexCount += 1
                id = "{0}-{1}".format(type, lexCount)
            elif type == "DEVOTIONAL":
                devotionalCount += 1
                id = "{0}-{1}".format(type, devotionalCount)
            data[id] = [
                id, filename, type, directory, file, description, repo,
                installDirectory, sha
            ]
        return data

    def resetItems(self):
        self.isUpdating = True
        self.dataViewModel.clear()
        filterEntry = self.filterEntry.text().lower()
        rowCount = 0
        colCount = 0
        catalogData = self.localCatalogData
        if self.location == "remote":
            catalogData = self.remoteCatalogData
        for id, value in catalogData.items():
            id2, filename, type, directory, file, description, repo, installDirectory, sha = value
            if (filterEntry == "" or filterEntry in filename.lower()
                    or filterEntry in description.lower()):
                if (not self.pdfCheckbox.isChecked() and type == "PDF") or \
                        (not self.mp3Checkbox.isChecked() and type == "MP3") or \
                        (not self.mp4Checkbox.isChecked() and type == "MP4") or \
                        (not self.bookCheckbox.isChecked() and type == "BOOK") or \
                        (not self.docxCheckbox.isChecked() and type == "DOCX") or \
                        (not self.devotionalCheckbox.isChecked() and type == "DEVOTIONAL") or \
                        (not self.commCheckbox.isChecked() and type == "COMM"):
                    continue
                enable = True
                if self.location == "remote":
                    installDirectory = os.path.join(config.marvelData,
                                                    installDirectory)
                    if FileUtil.regexFileExists(
                            "{0}.*".format(GithubUtil.getShortname(filename)),
                            installDirectory):
                        enable = False
                item = QStandardItem(id)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                item = QStandardItem(file)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                item = QStandardItem(directory)
                item.setEnabled(enable)
                self.dataViewModel.setItem(rowCount, colCount, item)
                colCount += 1
                # item = QStandardItem(description)
                # self.dataViewModel.setItem(rowCount, colCount, item)
                # colCount += 1
                # add row count
                rowCount += 1
                colCount = 0
        self.dataViewModel.setHorizontalHeaderLabels([
            "#",
            config.thisTranslation["file"],
            config.thisTranslation["directory"],
            # config.thisTranslation["description"]
        ])
        self.dataView.resizeColumnsToContents()
        self.isUpdating = False

    def itemClicked(self, index):
        selectedRow = index.row()
        self.catalogEntryId = self.dataViewModel.item(selectedRow, 0).text()
        if self.location == "remote":
            item = self.remoteCatalogData[self.catalogEntryId]
            id, filename, type, directory, file, description, repo, installDirectory, sha = item
            installDirectory = os.path.join(config.marvelData,
                                            installDirectory)
            if FileUtil.regexFileExists(
                    "{0}.*".format(GithubUtil.getShortname(filename)),
                    installDirectory):
                self.downloadButton.setEnabled(False)
                self.downloadButton.setStyleSheet("")
            else:
                self.downloadButton.setEnabled(True)
                self.downloadButton.setStyleSheet(self.textButtonStyle)

    def displayMessage(self, message="", title="UniqueBible"):
        QMessageBox.information(self, title, message)

    def saveRemoteCatalogToCache(self):
        data = CatalogUtil.loadRemoteCatalog()
        with open("util/GitHubRepoCache.py", "w", encoding="utf-8") as fileObj:
            fileObj.write("gitHubRepoCacheData = {0}\n".format(
                pprint.pformat(data)))

    def fixDirectory(self, directory, type):
        if type == "PDF":
            directory = directory.replace(config.marvelData, "")
            directory = directory.replace("/pdf", "")
        if len(directory) > 0 and not directory.endswith("/"):
            directory += "/"
        if len(directory) > 0 and directory.startswith("/"):
            directory = directory[1:]
        return directory

    def open(self):
        item = self.localCatalogData[self.catalogEntryId]
        id, filename, type, directory, file, description, repo, installDirectory, sha = item
        directory = self.fixDirectory(directory, type)
        command = ""
        if type == "PDF":
            command = "PDF:::{0}{1}".format(directory, file)
        elif type == "MP3":
            command = "VLC:::{0}{1}".format(directory, file)
        elif type == "MP4":
            command = "VLC:::{0}{1}".format(directory, file)
        elif type == "BOOK":
            if file.endswith(".book"):
                file = file.replace(".book", "")
            config.booksFolder = directory
            command = "BOOK:::{0}".format(file)
        elif type == "COMM":
            file = file.replace(".commentary", "")
            file = file[1:]
            config.commentariesFolder = directory
            command = "COMMENTARY:::{0}:::{1} {2}".format(
                file, BibleBooks.eng[str(config.mainB)][0], config.mainC)
        elif type == "DOCX":
            command = "DOCX:::{0}".format(file)
        elif type == "DEVOTIONAL":
            file = file.replace(".devotional", "")
            command = "DEVOTIONAL:::{0}".format(file)
        self.parent.runTextCommand(command)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.downloadButton.setStyleSheet("")
        item = self.remoteCatalogData[self.catalogEntryId]
        id, filename, type, directory, file, description, repo, installDirectory, sha = item
        github = GithubUtil(repo)
        installDirectory = os.path.join(config.marvelData, installDirectory)
        file = os.path.join(installDirectory, filename + ".zip")
        github.downloadFile(file, sha)
        with zipfile.ZipFile(file, 'r') as zipped:
            zipped.extractall(installDirectory)
        os.remove(file)
        self.displayMessage(filename + " " +
                            config.thisTranslation["message_installed"])
        self.localCatalog = CatalogUtil.reloadLocalCatalog()
        self.localCatalogData = self.getLocalCatalogItems()
        self.resetItems()
예제 #2
0
class SpectrumSelection(QDialog):
    """
    A widget providing a simple dialog for selecting spectra to be loaded.

    The widget itself knows nothing about spectral data objects, but instead
    just presents a list of strings which represent the names of the available
    spectra, and returns a list of strings which represent the names of the
    spectra that were actually selected.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        loadUi(os.path.abspath(
            os.path.join(os.path.dirname(__file__),
                         "ui", "spectrum_selection.ui")), self)

        self.setWindowTitle('Spectrum Selection')

        self._model = QStandardItemModel(self.spectrumList)
        self.spectrumList.setModel(self._model)
        self._selected = False

        self.buttonBox.accepted.connect(self._confirm_selection)

        self.selectAllButton.clicked.connect(self._select_all)
        self.deselectAllButton.clicked.connect(self._deselect_all)

    def populate(self, names):
        """
        Add a list of names to be displayed as list items in the dialog

        Parameters
        ----------
        names : `list`
            The list of names to be populated in the dialog
        """
        for s in names:
            item = QStandardItem(s)
            item.setCheckable(True)
            item.setCheckState(Qt.Checked)
            self._model.appendRow(item)

    def get_selected(self):
        """
        Get the list of names that were actually checked when the dialog closes

        This method will return an empty list in the following cases:
            * No items were selected when the dialog closes
            * The "Cancel" button was hit by the user instead of "Open"
            * This method is called before the dialog closes

        Returns
        -------
        names : `list`
            The list of names that were selected when the dialog closes
        """
        if not self._selected:
            return []

        selected = []
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            if item.checkState() == Qt.Checked:
                selected.append(item.text())
        return selected

    def _confirm_selection(self):
        self._selected = True

    def _select_all(self):
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            item.setCheckState(Qt.Checked)

    def _deselect_all(self):
        for i in range(self._model.rowCount()):
            item = self._model.item(i)
            item.setCheckState(Qt.Unchecked)
예제 #3
0
class Switcher(QDialog):
    """
    A multi purpose switcher.

    Example
    -------
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherSeparator: [---------------------------------------]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
    """

    # Dismissed switcher
    sig_rejected = Signal()
    # Search/Filter text changes
    sig_text_changed = Signal(TEXT_TYPES[-1])
    # Current item changed
    sig_item_changed = Signal(object)
    # List item selected, mode and cleaned search text
    sig_item_selected = Signal(object, TEXT_TYPES[-1], TEXT_TYPES[-1], )
    sig_mode_selected = Signal(TEXT_TYPES[-1])

    _MAX_NUM_ITEMS = 15
    _MIN_WIDTH = 580
    _MIN_HEIGHT = 200
    _MAX_HEIGHT = 390
    _ITEM_WIDTH = _MIN_WIDTH - 20

    def __init__(self, parent, help_text=None, item_styles=ITEM_STYLES,
                 item_separator_styles=ITEM_SEPARATOR_STYLES):
        """Multi purpose switcher."""
        super(Switcher, self).__init__(parent)
        self._modes = {}
        self._mode_on = ''
        self._item_styles = item_styles
        self._item_separator_styles = item_separator_styles

        # Widgets
        self.edit = QLineEdit(self)
        self.list = QListView(self)
        self.model = QStandardItemModel(self.list)
        self.proxy = SwitcherProxyModel(self.list)
        self.filter = KeyPressFilter()

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
#        self.setMinimumHeight(self._MIN_HEIGHT)
        self.setMaximumHeight(self._MAX_HEIGHT)
        self.edit.installEventFilter(self.filter)
        self.edit.setPlaceholderText(help_text if help_text else '')
        self.list.setMinimumWidth(self._MIN_WIDTH)
        self.list.setItemDelegate(SwitcherDelegate(self))
        self.list.setFocusPolicy(Qt.NoFocus)
        self.list.setSelectionBehavior(self.list.SelectItems)
        self.list.setSelectionMode(self.list.SingleSelection)
        self.list.setVerticalScrollMode(QAbstractItemView.ScrollPerItem)
        self.proxy.setSourceModel(self.model)
        self.list.setModel(self.proxy)

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.filter.sig_enter_key_pressed.connect(self.enter)
        self.edit.textChanged.connect(self.setup)
        self.edit.textChanged.connect(self.sig_text_changed)
        self.edit.returnPressed.connect(self.enter)
        self.list.clicked.connect(self.enter)
        self.list.clicked.connect(self.edit.setFocus)
        self.list.selectionModel().currentChanged.connect(
            self.current_item_changed)
        self.edit.setFocus()

    # --- Helper methods
    def _add_item(self, item, last_item=True):
        """Perform common actions when adding items."""
        item.set_width(self._ITEM_WIDTH)
        self.model.appendRow(item)
        if last_item:
            # Only set the current row to the first item when the added item is
            # the last one in order to prevent performance issues when
            # adding multiple items
            self.set_current_row(0)
            self.set_height()
        self.setup_sections()

    # --- API
    def clear(self):
        """Remove all items from the list and clear the search text."""
        self.set_placeholder_text('')
        self.model.beginResetModel()
        self.model.clear()
        self.model.endResetModel()
        self.setMinimumHeight(self._MIN_HEIGHT)

    def set_placeholder_text(self, text):
        """Set the text appearing on the empty line edit."""
        self.edit.setPlaceholderText(text)

    def add_mode(self, token, description):
        """Add mode by token key and description."""
        if len(token) == 1:
            self._modes[token] = description
        else:
            raise Exception('Token must be of length 1!')

    def get_mode(self):
        """Get the current mode the switcher is in."""
        return self._mode_on

    def remove_mode(self, token):
        """Remove mode by token key."""
        if token in self._modes:
            self._modes.pop(token)

    def clear_modes(self):
        """Delete all modes spreviously defined."""
        del self._modes
        self._modes = {}

    def add_item(self, icon=None, title=None, description=None, shortcut=None,
                 section=None, data=None, tool_tip=None, action_item=False,
                 last_item=True):
        """Add switcher list item."""
        item = SwitcherItem(
            parent=self.list,
            icon=icon,
            title=title,
            description=description,
            data=data,
            shortcut=shortcut,
            section=section,
            action_item=action_item,
            tool_tip=tool_tip,
            styles=self._item_styles
        )
        self._add_item(item, last_item=last_item)

    def add_separator(self):
        """Add separator item."""
        item = SwitcherSeparatorItem(parent=self.list,
                                     styles=self._item_separator_styles)
        self._add_item(item)

    def setup(self):
        """Set-up list widget content based on the filtering."""
        # Check exited mode
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        # Check exited mode
        if self.search_text() == '':
            self._mode_on = ''
            self.clear()
            self.proxy.set_filter_by_score(False)
            self.sig_mode_selected.emit(self._mode_on)
            return

        # Check entered mode
        for key in self._modes:
            if self.search_text().startswith(key) and not mode:
                self._mode_on = key
                self.sig_mode_selected.emit(key)
                return

        # Filter by text
        titles = []
        for row in range(self.model.rowCount()):
            item = self.model.item(row)
            if isinstance(item, SwitcherItem):
                title = item.get_title()
            else:
                title = ''
            titles.append(title)

        search_text = clean_string(search_text)
        scores = get_search_scores(to_text_string(search_text),
                                   titles, template=u"<b>{0}</b>")

        for idx, (title, rich_title, score_value) in enumerate(scores):
            item = self.model.item(idx)
            if not self._is_separator(item) and not item.is_action_item():
                rich_title = rich_title.replace(" ", "&nbsp;")
                item.set_rich_title(rich_title)
            item.set_score(score_value)
        self.proxy.set_filter_by_score(True)

        self.setup_sections()
        if self.count():
            self.set_current_row(0)
        else:
            self.set_current_row(-1)
        self.set_height()

    def setup_sections(self):
        """Set-up which sections appear on the item list."""
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        if search_text:
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    item.set_section_visible(False)
        else:
            sections = []
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    sections.append(item.get_section())
                    item.set_section_visible(bool(search_text))
                else:
                    sections.append('')

                if row != 0:
                    visible = sections[row] != sections[row - 1]
                    if not self._is_separator(item):
                        item.set_section_visible(visible)
                else:
                    item.set_section_visible(True)

        self.proxy.sortBy('_score')
        self.sig_item_changed.emit(self.current_item())

    def set_height(self):
        """Set height taking into account the number of items."""
        if self.count() >= self._MAX_NUM_ITEMS:
            switcher_height = self._MAX_HEIGHT
        elif self.count() != 0 and self.current_item():
            current_item = self.current_item()
            item_height = current_item.get_height()
            list_height = item_height * (self.count() + 3)
            edit_height = self.edit.height()
            spacing_height = self.layout().spacing() * 4
            switcher_height = list_height + edit_height + spacing_height
            switcher_height = max(switcher_height, self._MIN_HEIGHT)
        else:
            switcher_height = self._MIN_HEIGHT
        self.setFixedHeight(int(switcher_height))

    def set_position(self, top):
        """Set the position of the dialog."""
        parent = self.parent()
        if parent is not None:
            geo = parent.geometry()
            width = self.list.width()  # This has been set in setup
            left = parent.geometry().width()/2 - width/2

            while parent:
                geo = parent.geometry()
                top += geo.top()
                left += geo.left()
                parent = parent.parent()

            self.move(round(left), top)

    @Slot(QModelIndex, QModelIndex)
    def current_item_changed(self, current, previous):
        """Handle item selection."""
        self.sig_item_changed.emit(self.current_item())

    # --- Qt overrides
    # ------------------------------------------------------------------------
    @Slot()
    @Slot(QListWidgetItem)
    def enter(self, itemClicked=None):
        """Override Qt method."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        if item:
            mode = self._mode_on
            self.sig_item_selected.emit(item, mode,
                                        self.search_text()[len(mode):])

    def accept(self):
        """Override Qt method."""
        super(Switcher, self).accept()

    def reject(self):
        """Override Qt method."""
        self.set_search_text('')
        self.sig_rejected.emit()
        super(Switcher, self).reject()

    def resizeEvent(self, event):
        """Override Qt method."""
        super(Switcher, self).resizeEvent(event)

    # --- Helper methods: Lineedit widget
    def search_text(self):
        """Get the normalized (lowecase) content of the search text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, string):
        """Set the content of the search text."""
        self.edit.setText(string)

    # --- Helper methods: List widget
    def _is_separator(self, item):
        """Check if item is an separator item (SwitcherSeparatorItem)."""
        return isinstance(item, SwitcherSeparatorItem)

    def _select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def count(self):
        """Get the item count in the list widget."""
        return self.proxy.rowCount()

    def current_row(self):
        """Return the current selected row in the list widget."""
        return self.list.currentIndex().row()

    def current_item(self):
        """Return the current selected item in the list widget."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        return item

    def set_current_row(self, row):
        """Set the current selected row in the list widget."""
        proxy_index = self.proxy.index(row, 0)
        selection_model = self.list.selectionModel()

        # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum
        selection_model.setCurrentIndex(
            proxy_index, selection_model.ClearAndSelect)

        # Ensure that the selected item is visible
        self.list.scrollTo(proxy_index, QAbstractItemView.EnsureVisible)

    def previous_row(self):
        """Select previous row in list widget."""
        steps = 1
        prev_row = self.current_row() - steps

        if prev_row == -1:
            self.set_current_row(self.count() - 1)
        else:
            if prev_row >= 0:
                # Need to map the filtered list to the actual model items
                list_index = self.proxy.index(prev_row, 0)
                model_index = self.proxy.mapToSource(list_index)
                item = self.model.item(model_index.row(), 0)
                if self._is_separator(item):
                    steps += 1
            self._select_row(-steps)

    def next_row(self):
        """Select next row in list widget."""
        steps = 1
        next_row = self.current_row() + steps

        # Need to map the filtered list to the actual model items
        list_index = self.proxy.index(next_row, 0)
        model_index = self.proxy.mapToSource(list_index)
        item = self.model.item(model_index.row(), 0)

        if next_row >= self.count():
            self.set_current_row(0)
        else:
            if item:
                if self._is_separator(item):
                    steps += 1
            self._select_row(steps)
예제 #4
0
class LiveFilterDialog(QDialog):

    JS_HIDE = """
            count = 0;
            searchResultCount = document.getElementById("searchResultCount");
            divs = document.querySelectorAll("div");
            for (var i = 0, len = divs.length; i < len; i++) {{
                div = divs[i];
                div.hidden = {0};
                count++;
            }};
            if (searchResultCount) {{
                searchResultCount.innerHTML = count;
            }}
            """

    JS_SHOW = """
            wordSets = [{0}];
            count = 0;
            searchResultCount = document.getElementById("searchResultCount");
            divs = document.querySelectorAll("div");
            for (var i=0, len=divs.length; i < len; i++) {{
                div = divs[i];
                var found = true;
                for (var j=0, len2=wordSets.length; j < len2; j++) {{
                    wordSet = wordSets[j];
                    var regex;
                    if (wordSet.startsWith("'")) {{
                        wordSet = wordSet.replace("'", "");
                        wordSet = wordSet.replace("'", "");
                        regex = new RegExp(wordSet);
                    }} else {{
                        regex = new RegExp(wordSet, "i");
                    }}
                    found &= regex.test(div.innerHTML);
                }}
                if (found) {{
                    div.hidden = false;
                    count++;
                }}
            }};
            if (searchResultCount) {{
                searchResultCount.innerHTML = count;
            }}
            """

    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["liveFilter"])
        self.setMinimumSize(400, 400)
        self.selectedFilter = None
        self.selectedPattern = None
        self.settingBibles = False
        self.db = LiveFilterSqlite()
        self.filters = None
        self.saveReadFormattedBibles = config.readFormattedBibles
        if config.readFormattedBibles:
            self.parent.disableBiblesInParagraphs()
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["liveFilter"])
        mainLayout.addWidget(title)

        self.filtersTable = QTableView()
        self.filtersTable.setEnabled(True)
        self.filtersTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.filtersTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.filtersTable)
        self.filtersTable.setModel(self.dataViewModel)
        self.dataViewModel.itemChanged.connect(self.filterSelectionChanged)
        self.selectionModel = self.filtersTable.selectionModel()
        self.selectionModel.selectionChanged.connect(self.handleSelection)
        mainLayout.addWidget(self.filtersTable)
        self.reloadFilters()

        buttonsLayout = QHBoxLayout()
        addButton = QPushButton(config.thisTranslation["add"])
        addButton.clicked.connect(self.addNewFilter)
        buttonsLayout.addWidget(addButton)
        removeButton = QPushButton(config.thisTranslation["remove"])
        removeButton.clicked.connect(self.removeFilter)
        buttonsLayout.addWidget(removeButton)
        editButton = QPushButton(config.thisTranslation["edit"])
        editButton.clicked.connect(self.editFilter)
        buttonsLayout.addWidget(editButton)
        importButton = QPushButton(config.thisTranslation["import"])
        importButton.clicked.connect(self.importFile)
        buttonsLayout.addWidget(importButton)
        buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        buttons = QDialogButtonBox.Ok
        self.buttonBox = QDialogButtonBox(buttons)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.accepted.connect(self.close)
        self.buttonBox.rejected.connect(self.reject)
        mainLayout.addWidget(self.buttonBox)

        self.setLayout(mainLayout)

    def close(self):
        pass

    def reloadFilters(self):
        self.filters = self.db.getAll()
        self.dataViewModel.clear()
        rowCount = 0
        for bible, description in self.filters:
            item = QStandardItem(bible)
            item.setToolTip(bible)
            item.setCheckable(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(description)
            self.dataViewModel.setItem(rowCount, 1, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["filter2"],
            config.thisTranslation["pattern"]
        ])
        self.filtersTable.resizeColumnsToContents()

    def handleSelection(self, selected, deselected):
        for item in selected:
            row = item.indexes()[0].row()
            filter = self.dataViewModel.item(row, 0)
            self.selectedFilter = filter.text()
            pattern = self.dataViewModel.item(row, 1)
            self.selectedPattern = pattern.text()

    def filterSelectionChanged(self, item):
        try:
            numChecked = 0
            for index in range(self.dataViewModel.rowCount()):
                item = self.dataViewModel.item(index)
                if item.checkState() == Qt.Checked:
                    numChecked += 1
            if numChecked == 0:
                config.mainWindow.studyPage.runJavaScript(
                    self.JS_HIDE.format("false"))
            else:
                sets = []
                config.mainWindow.studyPage.runJavaScript(
                    self.JS_HIDE.format("true"))
                for index in range(self.dataViewModel.rowCount()):
                    item = self.dataViewModel.item(index)
                    if item.checkState() == Qt.Checked:
                        sets.append('"{0}"'.format(self.filters[index][1]))
                wordSets = ",".join(sets)
                js = self.JS_SHOW.format(wordSets)
                config.mainWindow.studyPage.runJavaScript(js)
        except Exception as e:
            print(str(e))

    def addNewFilter(self):
        fields = [(config.thisTranslation["filter2"], ""),
                  (config.thisTranslation["pattern"], "")]
        dialog = MultiLineInputDialog("New Filter", fields)
        if dialog.exec():
            data = dialog.getInputs()
            self.db.insert(data[0], data[1])
            self.reloadFilters()

    def removeFilter(self):
        reply = QMessageBox.question(
            self, "Delete",
            'Delete {0} {1}'.format(self.selectedFilter,
                                    config.thisTranslation["filter2"]),
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.db.delete(self.selectedFilter)
            self.reloadFilters()

    def editFilter(self):
        fields = [(config.thisTranslation["filter2"], self.selectedFilter),
                  (config.thisTranslation["pattern"], self.selectedPattern)]
        dialog = MultiLineInputDialog("Edit Filter", fields)
        if dialog.exec():
            data = dialog.getInputs()
            self.db.delete(self.selectedFilter)
            self.db.insert(data[0], data[1])
            self.reloadFilters()

    def importFile(self):
        options = QFileDialog.Options()
        filename, filtr = QFileDialog.getOpenFileName(
            self, config.thisTranslation["import"],
            config.thisTranslation["liveFilter"], "File (*.*)", "", options)
        if filename:
            try:
                with open(filename, errors='ignore') as f:
                    for line in f:
                        data = line.split(":::")
                        filter = data[0].strip()
                        pattern = data[1].strip()
                        if self.db.checkFilterExists(filter):
                            self.db.delete(filter)
                        self.db.insert(filter, pattern)
            except Exception as e:
                print(e)
            self.reloadFilters()
예제 #5
0
class DownloadBibleMp3Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()

        self.bibles = {
            "BBE (British accent)":
            ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"),
            "KJV (American accent)":
            ("KJV", "otseng/UniqueBible_MP3_KJV", "default"),
            "KJV (American soft music)":
            ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"),
            "NHEB (Indian accent)":
            ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"),
            "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB",
                                      "default"),
            "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"),
            "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"),
            "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"),
            "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"),
        }
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"])
        self.setMinimumSize(150, 450)
        self.selectedRendition = None
        self.selectedText = None
        self.selectedRepo = None
        self.selectedDirectory = None
        self.settingBibles = False
        self.thread = None
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)

    def selectItem(self, item):
        self.selectRendition(item.text())

    def selectRendition(self, rendition):
        from util.GithubUtil import GithubUtil

        self.selectedRendition = rendition
        self.downloadTable.setEnabled(True)
        self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[
            self.selectedRendition]
        self.github = GithubUtil(self.selectedRepo)
        self.repoData = self.github.getRepoData()
        self.settingBibles = True
        self.dataViewModel.clear()
        rowCount = 0
        for file in self.repoData.keys():
            if len(str(file)) > 3:
                engFullBookName = file[3:]
            else:
                engFullBookName = BibleBooks().eng[str(int(file))][1]
            item = QStandardItem(file[:3].strip())
            folder = os.path.join("audio", "bibles", self.selectedText,
                                  self.selectedDirectory, file)
            folderWithName = os.path.join("audio", "bibles", self.selectedText,
                                          self.selectedDirectory,
                                          file + " " + engFullBookName)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item.setCheckable(False)
                item.setCheckState(Qt.Unchecked)
                item.setEnabled(False)
            else:
                item.setCheckable(True)
                item.setCheckState(Qt.Checked)
                item.setEnabled(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(engFullBookName)
            self.dataViewModel.setItem(rowCount, 1, item)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item = QStandardItem("Installed")
                self.dataViewModel.setItem(rowCount, 2, item)
            else:
                item = QStandardItem("")
                self.dataViewModel.setItem(rowCount, 2, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["menu_book"],
            config.thisTranslation["name"], ""
        ])
        self.downloadTable.setColumnWidth(0, 90)
        self.downloadTable.setColumnWidth(1, 125)
        self.downloadTable.setColumnWidth(2, 125)
        # self.downloadTable.resizeColumnsToContents()
        self.settingBibles = False

    def selectAll(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            if item.isEnabled():
                item.setCheckState(Qt.Checked)

    def selectNone(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            item.setCheckState(Qt.Unchecked)

    def selectOT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum <= 39:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def selectNT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum >= 40:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.setStatus(config.thisTranslation["message_installing"])
        self.closeButton.setEnabled(False)
        folder = os.path.join("audio", "bibles")
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText)
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText,
                              self.selectedDirectory)
        if not os.path.exists(folder):
            os.mkdir(folder)
        self.thread = QThread()
        self.worker = DownloadFromGitHub(self.github, self.repoData,
                                         self.dataViewModel, self.selectedText,
                                         self.selectedDirectory)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.finishedDownloading)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def finishedDownloading(self, count):
        self.selectRendition(self.selectedRendition)
        self.setStatus("")
        self.downloadButton.setEnabled(True)
        self.closeButton.setEnabled(True)
        if count > 0:
            self.parent.displayMessage(
                config.thisTranslation["message_installed"])

    def setStatus(self, message):
        self.status.setText(message)
        QApplication.processEvents()

    def closeDialog(self):
        if self.thread:
            if self.thread.isRunning():
                self.thread.quit()
        self.close()
예제 #6
0
class FrequencySelectionFromFile(QGroupBox):
    """
    select frequencies/periods from the selected edi files
    """

    def __init__(self, parent):
        QGroupBox.__init__(self, parent)
        self._mt_obj_dict = {}
        self.model_stations = QStandardItemModel()

        # setup ui
        self.ui = Ui_GroupBox_select_from_files()
        self.ui.setupUi(self)
        self.ui.listView_stations.setModel(self.model_stations)

        # connect signals
        self.ui.listView_stations.selectionModel().selectionChanged.connect(self._update_selection)

    data_changed = Signal()

    def set_data(self, mt_objs):
        self._mt_obj_dict.clear()
        for mt_obj in mt_objs:
            self._mt_obj_dict[mt_obj.station] = mt_obj

        self._update_stations()

        self.data_changed.emit()

    def _update_stations(self):
        self.model_stations.clear()
        for mt_obj in list(self._mt_obj_dict.values()):
            new_item = QStandardItem()
            new_item.setData(mt_obj.station, QtCore.Qt.DisplayRole)
            new_item.setData(mt_obj.fn, QtCore.Qt.ToolTipRole)
            self.model_stations.appendRow(new_item)
        self.model_stations.sort(0)

    def _update_selection(self):
        self.ui.tableWidget_selected.clearContents()
        unique_frequencies = set()
        # combine frequencies from all selected stations
        for index in self.ui.listView_stations.selectedIndexes():
            item = self.model_stations.item(index.row())
            station = item.data(QtCore.Qt.DisplayRole)
            mt_obj = self._mt_obj_dict[station]
            # get frequencies
            freq = [freq for freq in list(mt_obj.Z.freq)]
            unique_frequencies.update(freq)
        # order !
        unique_frequencies = sorted(list(unique_frequencies))
        unique_periods = list(1. / np.array(unique_frequencies))

        # update widget
        self.ui.tableWidget_selected.setRowCount(len(unique_frequencies))
        for index, freq_period in enumerate(zip(unique_frequencies, unique_periods)):
            for i in [0, 1]:
                newItem = QTableWidgetItem(str(freq_period[i]))
                newItem.setData(QtCore.Qt.UserRole, freq_period[i])
                newItem.setFlags(QtCore.Qt.ItemIsEnabled)
                self.ui.tableWidget_selected.setItem(index, i, newItem)

    def get_selected_frequencies(self):
        return self.get_data(0)

    def get_selected_periods(self):
        return self.get_data(1)

    def get_data(self, column_index):
        data = [
            self.ui.tableWidget_selected.item(index, column_index).data(QtCore.Qt.UserRole)
            for index in range(self.ui.tableWidget_selected.rowCount())
        ]
        return data
예제 #7
0
class FrequencySelection(QGroupBox):
    """
    frequency selection
    """

    def __init__(self, parent, show_period=True, show_frequency=True, allow_range_select=True,
                 select_multiple=True):
        QGroupBox.__init__(self, parent)
        self._mt_objs = None
        self._unique_periods = None
        self._unique_frequencies = None
        self._periods = None
        self._frequencies = None
        self._allow_range = allow_range_select
        self._select_multiple = select_multiple
        self.ui = Ui_GroupBox_frequency_select()
        self.ui.setupUi(self)

        self.ui.label_place_holder.hide()
        self.model_selected = QStandardItemModel()
        self.ui.listView_selected.setModel(self.model_selected)
        self.frequency_delegate = FrequencySelection.FrequencyDelegate(self.ui.listView_selected)
        self.ui.listView_selected.setItemDelegate(self.frequency_delegate)

        self.histogram = FrequencySelection.Histogram(self, allow_range_select=self._allow_range)
        self.histogram.set_unit(self._units[0])
        self.histogram.set_tol(self.ui.doubleSpinBox_tolerance.value())
        self.histogram.frequency_selected.connect(self._frequency_selected)
        self.histogram.frequency_range_selected.connect(self._frequency_selected)
        self.ui.widget_histgram.layout().addWidget(self.histogram)

        self.ui.radioButton_period.setChecked(show_period)
        self.ui.radioButton_frequency.setChecked(show_frequency)
        self.ui.doubleSpinBox_tolerance.setHidden(not self._allow_range)
        self.ui.checkBox_existing_only.setChecked(not self._allow_range)
        self.ui.checkBox_existing_only.setHidden(not self._allow_range)
        self.ui.label_tolerance.setHidden(not self._allow_range)
        self.ui.radioButton_period.setHidden(not (show_period and show_frequency))
        self.ui.radioButton_frequency.setHidden(not (show_period and show_frequency))
        if self.ui.radioButton_frequency.isHidden():
            self.setTitle(self._type[1])
        elif self.ui.radioButton_period.isHidden():
            self.setTitle(self._type[0])

        self.ui.radioButton_frequency.toggled.connect(self._frequency_toggled)
        self.ui.checkBox_existing_only.toggled.connect(self.histogram.select_existing)
        self.ui.checkBox_existing_only.toggled.connect(self.model_selected.clear)
        self.ui.checkBox_show_existing.toggled.connect(self.histogram.show_existing)
        self.ui.checkBox_x_log_scale.toggled.connect(self.histogram.set_x_log_scale)
        self.ui.checkBox_y_log_scale.toggled.connect(self.histogram.set_y_log_scale)
        self.ui.pushButton_clear.clicked.connect(self._clear_all)
        self.ui.pushButton_delete.clicked.connect(self._delete_selected)
        self.ui.doubleSpinBox_tolerance.valueChanged.connect(self.histogram.set_tol)

    def set_data(self, mt_objs):
        self._mt_objs = mt_objs
        self._unique_frequencies = None
        self._unique_periods = None
        self._update_frequency()

    def get_frequencies(self):
        frequencies = [self.model_selected.item(index).data(QtCore.Qt.DisplayRole)
                       for index in range(self.model_selected.rowCount())]
        if self._allow_range:
            frequencies = [(freq[0], freq[1]) if isinstance(freq, tuple) else freq
                           for freq in frequencies]
        else:
            frequencies = [freq[3] if isinstance(freq, tuple) else freq
                           for freq in frequencies
                           if (isinstance(freq, tuple) and len(freq) == 5)
                           or isinstance(freq, float)]
        # print frequencies
        if self._select_multiple:
            return frequencies
        else:
            return frequencies[0] if frequencies else self._unique_frequencies[0]  # if nothing selected, return minimal frequency

    _units = ['Hz', 's']

    _type = ['Frequency', 'Period']

    def _clear_all(self):
        self.model_selected.clear()
        self.histogram.clear_all_drawing()

    def _delete_selected(self):
        for item in [self.model_selected.item(index.row())
                     for index in self.ui.listView_selected.selectedIndexes()]:
            x = item.data(QtCore.Qt.DisplayRole)
            self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
            self.histogram.remove_marker(x)

    def _frequency_selected(self, x):
        if not self._select_multiple:
            self.histogram.clear_all_drawing()
            self.model_selected.clear()
        for item in [self.model_selected.item(index) for index in range(self.model_selected.rowCount())]:
            value = item.data(QtCore.Qt.DisplayRole)
            if value == x:
                return
            elif isinstance(value, tuple) and isinstance(x, float) and value[0] <= x <= value[1]:
                return  # x already in interval
            elif isinstance(x, tuple) and isinstance(value, float) and x[0] <= value <= x[1]:
                # existing value in new interval
                self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
                self.histogram.remove_marker(value)
            elif isinstance(x, tuple) and isinstance(value, tuple):
                if min(x[1], value[1]) - max(x[0], value[0]) >= 0:
                    # there is intersection between intervals, so marge them
                    mi = min(x[0], value[0])
                    ma = max(x[1], value[1])
                    uniques = self._unique_frequencies \
                        if self.ui.radioButton_frequency.isChecked() \
                        else self._unique_periods
                    num = len(
                        [freq for freq in uniques if mi <= freq <= ma])  # num of existing freqs in the new interval
                    x = (mi, ma, num)
                    # remove old interval
                    self.model_selected.removeRow(self.model_selected.indexFromItem(item).row())
                    self.histogram.remove_marker(value)
            else:
                prec = self.frequency_delegate.prec
                while np.all(np.isclose(value, x, pow(.1, prec))):
                    prec += 1
                self.frequency_delegate.prec = prec
        new_item = FrequencySelection.FrequencyItem()
        new_item.setData(x, QtCore.Qt.DisplayRole)
        # update graphic
        if isinstance(x, float):
            self.histogram.add_marker(x)
            # new_item.setData(x, QtCore.Qt.UserRole)
        elif isinstance(x, tuple):
            self.histogram.add_marker(x)
            # new_item.setData(x[0], QtCore.Qt.UserRole)
        # update model
        self.model_selected.appendRow(new_item)
        self.model_selected.sort(0)

    def show_period(self):
        self.ui.radioButton_period.setChecked(True)

    def show_frequency(self):
        self.ui.radioButton_frequency.setChecked(True)

    def _frequency_toggled(self, is_checked):
        self.histogram.set_unit(self._units[0] if is_checked else self._units[1])
        self._update_frequency()

    def _update_frequency(self):
        self.model_selected.clear()
        if self._mt_objs is not None:
            if self._unique_frequencies is None:
                self._frequencies = [freq for mt_obj in self._mt_objs for freq in list(mt_obj.Z.freq)]
                all_unique = set(self._frequencies)
                self._unique_frequencies = sorted(list(all_unique))
            if self.ui.radioButton_period.isChecked() and self._unique_periods is None:
                self._periods = 1. / np.array(self._frequencies)
                all_unique = set(self._periods)
                self._unique_periods = sorted(list(all_unique))
            self.histogram.set_data(
                self._periods if self.ui.radioButton_period.isChecked()
                else self._frequencies,
                self._unique_periods if self.ui.radioButton_period.isChecked()
                else self._unique_frequencies
            )
            self.frequency_delegate.freqs = self._unique_periods \
                if self.ui.radioButton_period.isChecked() \
                else self._unique_frequencies
            self.histogram.update_figure()

    class FrequencyItem(QStandardItem):
        def __lt__(self, other):
            value = self.data(QtCore.Qt.DisplayRole)
            other_value = other.data(QtCore.Qt.DisplayRole)
            if isinstance(value, tuple):
                value = value[0]
            if isinstance(other_value, tuple):
                other_value = other_value[0]
            return value < other_value

    class FrequencyDelegate(QStyledItemDelegate):
        _prec = 5  # decimal places

        def get_prec(self):
            return self._prec

        def set_prec(self, prec):
            self._prec = prec

        prec = property(get_prec, set_prec)

        def displayText(self, value, locale):
            if isinstance(value, float):
                return '{:.{prec}f}'.format(value, prec=self._prec)
            elif isinstance(value, tuple) and len(value) == 3:  # (min, max, num)
                return '{}{}, {}{} ({num} selected)'.format(
                    '(' if value[0] == -np.inf else '[',
                    '{:.{prec}f}'.format(value[0], prec=self._prec),
                    '{:.{prec}f}'.format(value[1], prec=self._prec),
                    ')' if value[1] == np.inf else ']',
                    num=value[2]
                )
            elif len(value) == 5:  # (min, max, num, freq, tol)
                return '{:.{prec}f} ±{tol}% ({num} selected)'.format(
                    value[3], prec=self._prec, tol=value[4], num=value[2])
            # elif isinstance(py_obj, set):
            #     return '{{}}'.format(','.join(['{:.{prec}f}'.format(f, prec=self._prec) for f in py_obj if isinstance(f, float)]))
            return value

    class Histogram(MPLCanvas):
        def __init__(self, parent, y_log_scale=False, x_log_scale=False, allow_range_select=True):
            self._frequencies = None
            self._unique_frequencies = None
            self._title = None
            self._unit = None
            self._press = None
            self._tol = None
            MPLCanvas.__init__(self, parent, 5, 1.5)
            self._lx = {}
            self._cursor = None
            self._select_existing_only = False
            self._show_existing = False
            self._x_log_scale = x_log_scale
            self._y_log_scale = y_log_scale
            self._select_range = allow_range_select

            if self._select_range:
                self.mpl_connect('button_press_event', self.on_press)
            self.mpl_connect('button_release_event', self.on_release)

        def add_marker(self, x):
            if isinstance(x, float):
                lx = self._lx.setdefault(x, self._draw_v_line(x))
                # self._axes.draw_artist(lx)
                self.draw_idle()
            elif isinstance(x, tuple):
                if len(x) == 3:
                    lx = self._lx.setdefault(x, self._fill_v_area(x[0], x[1]))
                elif len(x) == 5:
                    lx = self._lx.setdefault(x, (
                        self._draw_v_line(x[3]),
                        self._fill_v_area(x[0], x[1])
                    ))
            else:
                raise NotImplemented
            self.draw_idle()

        def remove_marker(self, x):
            if x in self._lx:
                marker = self._lx[x]
                if isinstance(marker, tuple):
                    for m in marker:
                        m.remove()
                else:
                    marker.remove()
                self.draw_idle()
                del self._lx[x]

        def clear_all_drawing(self):
            for key in list(self._lx.keys()):
                marker = self._lx[key]
                if isinstance(marker, tuple):
                    for m in marker:
                        m.remove()
                else:
                    marker.remove()
            self._lx.clear()
            self.draw_idle()

        def set_unit(self, unit):
            if unit != self._unit:
                self._unit = unit
                self._cursor = Cursor(self._axes,
                                      track_y=False,
                                      show_drag=self._select_range,
                                      text_format="%f" + self._unit,
                                      useblit=True)

        def select_existing(self, select_existing):
            self._select_existing_only = select_existing
            self.clear_all_drawing()

        def set_tol(self, tol):
            self._tol = tol

        def show_existing(self, show_existing):
            self._show_existing = show_existing
            self.update_figure()

        def set_data(self, frequencies, unique_frequencies=None):
            self._frequencies = frequencies
            if unique_frequencies is not None:
                self._unique_frequencies = unique_frequencies
            else:
                self._unique_frequencies = sorted(list(set(frequencies)))
            self._lx.clear()

        def set_y_log_scale(self, ischecked):
            self._y_log_scale = ischecked
            self.update_figure()

        def set_x_log_scale(self, isChecked):
            self._x_log_scale = isChecked
            self.update_figure()

        frequency_selected = Signal(float)
        frequency_range_selected = Signal(tuple)

        def _get_valid_cursor_loc(self, event):
            if not event.inaxes:
                pos = self._axes.get_position()
                if self.height() * pos.y0 < event.y < self.height() * pos.y1:
                    x = -np.inf if event.x < self.width() * pos.x0 else np.inf
                else:
                    x = None
            else:
                x = event.xdata
            return x

        def on_press(self, event):
            self._press = self._get_valid_cursor_loc(event)

        def on_release(self, event):
            x = self._get_valid_cursor_loc(event)
            if x:
                if self._press and self._press != x:  # emit (min, max, num)
                    if self._press < x:
                        self.frequency_range_selected.emit(
                            (
                                self._press,
                                x,
                                len([freq for freq in self._unique_frequencies
                                     if self._press <= freq <= x])
                            )
                        )
                    elif self._press > x:
                        self.frequency_range_selected.emit(
                            (
                                x,
                                self._press,
                                len([freq for freq in self._unique_frequencies
                                     if x <= freq <= self._press])
                            )
                        )
                elif not self._select_range or self._select_existing_only:
                    x = self._find_closest(x)
                    self.frequency_selected.emit(x)
                else:  # emit (min, max, num, freq, tol)
                    tol = x * self._tol / 100.
                    min = x - tol
                    max = x + tol
                    self.frequency_range_selected.emit(
                        (
                            min,
                            max,
                            len([freq for freq in self._unique_frequencies
                                 if min <= freq <= max]),
                            x,
                            self._tol
                        )
                    )
            self._press = None

        def _find_closest(self, x):
            return min(self._frequencies, key=lambda freq: abs(freq - x))

        def compute_initial_figure(self):
            self._axes.tick_params(axis='both', which='major', labelsize=6)
            self._axes.tick_params(axis='both', which='minor', labelsize=4)
            if self._frequencies is not None:
                bins = gen_hist_bins(self._unique_frequencies)
                self._axes.hist(self._frequencies, bins=bins)  # , 50, normed=1)
                if self._y_log_scale:
                    self._axes.set_yscale('log', nonposy='clip')
                if self._x_log_scale:
                    self._axes.set_xscale('log', nonposx='clip')
                if self._show_existing:
                    for freq in self._unique_frequencies:
                        self._axes.axvline(freq, linewidth=1, color='black', alpha=0.2)
            if self._title and self._unit:
                self._axes.set_xlabel("%s (%s)" % (self._title, self._unit), fontsize=8)
                self.figure.suptitle('%s Distribution in Selected Stations' %
                                     self._title, fontsize=8)

            self._fig.set_tight_layout(True)

        def update_figure(self):
            self._axes.cla()
            self.compute_initial_figure()
            for key in list(self._lx.keys()):
                if isinstance(key, float):
                    self._lx[key] = self._draw_v_line(key)
                elif isinstance(key, tuple):
                    if len(key) == 3:
                        self._lx[key] = self._fill_v_area(key[0], key[1])
                    elif len(key) == 5:
                        self._lx[key] = (self._draw_v_line(key[3]), self._fill_v_area(key[0], key[1]))
            self.draw()

        def _draw_v_line(self, x):
            if x == -np.inf:
                x = self._axes.get_xlim()[0]
            if x == np.inf:
                x = self._axes.get_xlim()[1]
            return self._axes.axvline(x=x, linewidth=1, color="red")

        def _fill_v_area(self, x1, x2):
            if x1 == -np.inf:
                x1 = self._axes.get_xlim()[0]
            if x2 == np.inf:
                x2 = self._axes.get_xlim()[1]
            return self._axes.axvspan(x1, x2, alpha=0.5, color='red')
예제 #8
0
class PreprocessWidget(QSplitter):
    def __init__(self, headermodel, selectionmodel):
        super(PreprocessWidget, self).__init__()
        self.headermodel = headermodel
        self.mapselectmodel = selectionmodel
        self.selectMapidx = 0
        self.resultDict = {}
        self.isBatchProcessOn = False
        self.out = None
        self.dfDict = None
        self.reportList = ['preprocess_method', 'wav_anchor', 'interp_method', 'w_regions']
        self.arrayList = ['kohlerDebased', 'kohlerBaseline', 'rubberDebased', 'deriv2_kohler', 'deriv2_rubber']
        self.mousePosList = []

        # split between spectrum parameters and viewwindow, vertical split
        self.params_and_specview = QSplitter()
        self.params_and_specview.setOrientation(Qt.Vertical)
        # split between buttons and parameters
        self.buttons_and_params = QSplitter()
        self.buttons_and_params.setOrientation(Qt.Horizontal)
        # split between speclist and report
        self.speclist_and_report = QSplitter()
        self.speclist_and_report.setOrientation(Qt.Vertical)

        # buttons layout
        self.buttons = QWidget()
        self.buttonlayout = QGridLayout()
        self.buttons.setLayout(self.buttonlayout)
        # set up buttons
        self.fontSize = 12
        font = QFont("Helvetica [Cronyx]", self.fontSize)
        self.loadBtn = QPushButton()
        self.loadBtn.setText('Load spectra')
        self.loadBtn.setFont(font)
        self.removeBtn = QPushButton()
        self.removeBtn.setText('Remove spectrum')
        self.removeBtn.setFont(font)
        self.normBox = QComboBox()
        self.normBox.addItems(['Raw spectrum',
                               'Kohler EMSC baseline',
                               'Rubberband baseline',
                               'Kohler EMSC + 2nd derivative',
                               'Rubberband + 2nd derivative',
                               ])
        self.normBox.setFont(font)
        self.batchBtn = QPushButton()
        self.batchBtn.setText('Batch process')
        self.batchBtn.setFont(font)
        self.saveResultBox = QComboBox()
        self.saveResultBox.addItems(['Save kohler',
                                     'Save kohler baseline',
                                     'Save rubberband',
                                     'Save kohler 2nd derivative',
                                     'Save rubberband 2nd derivative',
                                     'Save all',
                                     ])
        self.saveResultBox.setFont(font)
        # add all buttons
        self.buttonlayout.addWidget(self.loadBtn)
        self.buttonlayout.addWidget(self.removeBtn)
        self.buttonlayout.addWidget(self.normBox)
        self.buttonlayout.addWidget(self.batchBtn)
        self.buttonlayout.addWidget(self.saveResultBox)
        # define report
        self.reportWidget = QWidget()
        self.reportWidget.setLayout(QVBoxLayout())
        self.infoBox = QTextEdit()
        reportTitle = QLabel('Preprocess results')
        reportTitle.setFont(font)
        self.reportWidget.layout().addWidget(reportTitle)
        self.reportWidget.layout().addWidget(self.infoBox)
        # spectrum list view
        self.specItemModel = QStandardItemModel()
        self.specSelectModel = QItemSelectionModel(self.specItemModel)
        self.speclistview = QListView()
        self.speclistview.setModel(self.specItemModel)
        self.speclistview.setSelectionModel(self.specSelectModel)
        # add title to list view
        self.specListWidget = QWidget()
        self.listLayout = QVBoxLayout()
        self.specListWidget.setLayout(self.listLayout)
        specListTitle = QLabel('Spectrum List')
        specListTitle.setFont(font)
        self.listLayout.addWidget(specListTitle)
        self.listLayout.addWidget(self.speclistview)

        # spectrum plot
        self.rawSpectra = baselinePlotWidget()
        self.resultSpectra = baselinePlotWidget()
        # ParameterTree
        self.parametertree = PreprocessParameters()
        self.parameter = self.parametertree.parameter
        self.processArgs = self.parametertree.processArgs
        self.argMap = self.parametertree.argMap

        # assemble widgets
        self.buttons_and_params.addWidget(self.parametertree)
        self.buttons_and_params.addWidget(self.buttons)
        self.buttons_and_params.setSizes([1000, 100])
        self.params_and_specview.addWidget(self.buttons_and_params)
        self.params_and_specview.addWidget(self.rawSpectra)
        self.params_and_specview.addWidget(self.resultSpectra)
        self.params_and_specview.setSizes([150, 50, 50])
        self.speclist_and_report.addWidget(self.specListWidget)
        self.speclist_and_report.addWidget(self.reportWidget)
        self.speclist_and_report.setSizes([150, 100])
        self.addWidget(self.params_and_specview)
        self.addWidget(self.speclist_and_report)
        self.setSizes([1000, 200])

        # Connect signals
        self.loadBtn.clicked.connect(self.loadData)
        self.removeBtn.clicked.connect(self.removeSpec)
        self.batchBtn.clicked.connect(self.batchProcess)
        self.saveResultBox.currentIndexChanged.connect(self.saveResults)
        self.specSelectModel.selectionChanged.connect(self.updateSpecPlot)
        self.normBox.currentIndexChanged.connect(self.updateSpecPlot)
        self.parametertree.sigParamChanged.connect(self.updateSpecPlot)
        self.rawSpectra.scene().sigMouseClicked.connect(self.setAnchors)
        self.parameter.child('Preprocess method').sigValueChanged.connect(self.updateMethod)

    def setHeader(self, field: str):
        self.headers = [self.headermodel.item(i).header for i in range(self.headermodel.rowCount())]
        self.field = field
        self.wavenumberList = []
        self.rc2indList = []
        self.ind2rcList = []
        self.pathList = []
        self.dataSets = []

        # get wavenumbers, rc2ind
        for header in self.headers:
            dataEvent = next(header.events(fields=[field]))
            self.wavenumberList.append(dataEvent['wavenumbers'])
            self.rc2indList.append(dataEvent['rc_index'])
            self.ind2rcList.append(dataEvent['index_rc'])
            self.pathList.append(dataEvent['path'])
            # get raw spectra
            data = None
            try:  # spectra datasets
                data = header.meta_array('spectra')
            except IndexError:
                msg.logMessage('Header object contained no frames with field ''{field}''.', msg.ERROR)
            if data is not None:
                self.dataSets.append(data)

    def isMapOpen(self):
        if not self.mapselectmodel.selectedIndexes():  # no map is open
            return False
        else:
            self.selectMapidx = self.mapselectmodel.selectedIndexes()[0].row()
            return True

    def setAnchors(self, event):
        # get current map idx and selected spectrum idx
        specidx = self.getCurrentSpecid()
        plotChoice = self.normBox.currentIndex()
        if (not self.isMapOpen()) or (self.specItemModel.rowCount() == 0) or (specidx is None) or (plotChoice not in [2, 4]):
            return

        pos = event.pos()
        button = event.button()
        parser = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][specidx])
        parser.parse_anchors(self.parameter['Anchor points'])
        anchor_low, anchor_high = parser.wav_anchor[0], parser.wav_anchor[-1]
        if self.rawSpectra.getViewBox().sceneBoundingRect().contains(pos):
            mousePoint = self.rawSpectra.getViewBox().mapToView(pos)
            x = mousePoint.x()
            if anchor_low < x < anchor_high:
                if button == Qt.LeftButton:# left click, add point to mousePosList
                    self.mousePosList.append(x)
                elif (button == Qt.MidButton) and self.mousePosList :# right click, remove last point from mousePosList
                    self.mousePosList.pop()
                # set anchors list
                anchors = [anchor_low] + sorted(self.mousePosList) + [anchor_high]
                txt = ', '.join([str(int(round(x))) for x in anchors])
                self.parameter.child('Anchor points').setValue(txt)

    def getCurrentSpecid(self):
        # get selected spectrum idx
        specidx = None  # default value
        if self.specSelectModel.selectedIndexes():
            selectedSpecRow = self.specSelectModel.selectedIndexes()[0].row()
            currentSpecItem = self.specItemModel.item(selectedSpecRow)
            specidx = currentSpecItem.idx
        return specidx

    def updateMethod(self):
        if self.parameter["Preprocess method"] == 'Kohler_EMSC':
            self.normBox.setCurrentIndex(1)
        else:
            self.normBox.setCurrentIndex(2)

    def updateSpecPlot(self):
        # get current map idx and selected spectrum idx
        specidx = self.getCurrentSpecid()
        if not self.isMapOpen():
            return
        elif self.specItemModel.rowCount() == 0:
            MsgBox('No spectrum is loaded.\nPlease click "Load spectra" to import data.')
            return
        elif specidx is None:
            return

        # get plotchoice
        plotChoice = self.normBox.currentIndex()

        # create Preprocessor object
        self.out = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][specidx])
        baselineOK = self.out.rubber_band(**self.processArgs) and self.out.kohler(**self.processArgs)

        if not baselineOK:
            return

        # make results report
        if plotChoice != 0:
            self.getReport(self.out, plotChoice)

        # if not batch processing, show plots
        if not self.isBatchProcessOn:
            # clean up plots
            self.rawSpectra.clearAll()
            self.resultSpectra.clearAll()
            if plotChoice == 0:  # plot raw spectrum
                self.infoBox.setText('')  # clear txt
                self.rawSpectra.plotBase(self.out, plotType='raw')
            elif plotChoice == 1:  # plot raw, kohler
                self.rawSpectra.plotBase(self.out, plotType='kohler_base')
                self.resultSpectra.plotBase(self.out, plotType='kohler')
            elif plotChoice == 2:  # plot raw, rubberband
                self.rawSpectra.plotBase(self.out, plotType='rubber_base')
                self.resultSpectra.plotBase(self.out, plotType='rubberband')
            elif plotChoice == 3:  # plot raw, kohler 2nd derivative
                self.rawSpectra.plotBase(self.out, plotType='kohler_base')
                self.resultSpectra.plotBase(self.out, plotType='deriv2_kohler')
            elif plotChoice == 4:  # plot raw, rubberband 2nd derivative
                self.rawSpectra.plotBase(self.out, plotType='rubber_base')
                self.resultSpectra.plotBase(self.out, plotType='deriv2_rubberband')

            if plotChoice in [1, 3]:
                self.parameter.child('Preprocess method').setValue('Kohler_EMSC', blockSignal=self.updateMethod)
            elif plotChoice in [2, 4]:
                self.parameter.child('Preprocess method').setValue('Rubberband', blockSignal=self.updateMethod)

    def getReport(self, output, plotChoice):
        resultTxt = ''
        # get baseline results
        reportList = self.reportList.copy()
        if plotChoice in [2, 4]:
            reportList = self.reportList[:-1]
            output.preprocess_method = 'rubberband'
        elif plotChoice in [1, 3]:
            reportList = [self.reportList[0], self.reportList[-1]]
            output.preprocess_method = 'kohler'

        for item in dir(output):
            if item in reportList:
                if item == 'wav_anchor':
                    val = getattr(output, item)
                    printFormat = ('{:.2f}, ' * len(val))[:-1]
                    resultTxt += item + ': ' + printFormat.format(*val) + '\n'
                else:
                    resultTxt += item + ': ' + str(getattr(output, item)) + '\n'
            if (item in self.arrayList) or (item in self.reportList):
                self.resultDict[item] = getattr(output, item)

        # send text to report info box
        self.infoBox.setText(resultTxt)

    def loadData(self):
        # get current map idx
        if not self.isMapOpen():
            return
        # pass the selected map data to plotwidget
        self.rawSpectra.setHeader(self.headers[self.selectMapidx], 'spectra')
        currentMapItem = self.headermodel.item(self.selectMapidx)
        rc2ind = self.rc2indList[self.selectMapidx]
        # get current map name
        mapName = currentMapItem.data(0)
        # get current selected pixels
        pixelCoord = currentMapItem.selectedPixels
        # get selected specIds
        spectraIds = []
        if currentMapItem.selectedPixels is None:  # select all
            spectraIds = list(range(len(rc2ind)))
        else:
            for i in range(len(pixelCoord)):
                row_col = tuple(pixelCoord[i])
                spectraIds.append(rc2ind[row_col])
            spectraIds = sorted(spectraIds)
        # add specitem model
        self.specItemModel.clear()
        for idx in spectraIds:
            item = QStandardItem(mapName + '# ' + str(idx))
            item.idx = idx
            self.specItemModel.appendRow(item)

    def removeSpec(self):
        # get current selectedSpecRow
        if self.specSelectModel.selectedIndexes():
            selectedSpecRow = self.specSelectModel.selectedIndexes()[0].row()
            self.specSelectModel.blockSignals(True)
            self.specItemModel.removeRow(selectedSpecRow)
            self.specSelectModel.blockSignals(False)
            # clean up plots
            self.rawSpectra.clearAll()
            self.resultSpectra.clearAll()
            self.infoBox.setText('')

    def cleanUp(self):
        self.specItemModel.clear()
        self.rawSpectra.clearAll()
        self.resultSpectra.clearAll()
        self.infoBox.setText('')
        self.mousePosList = []
        self.normBox.setCurrentIndex(0)

    def batchProcess(self):
        # get current map idx
        if not self.isMapOpen():
            return
        elif self.specItemModel.rowCount() == 0:
            MsgBox('No spectrum is loaded.\nPlease click "Load spectra" to import data.')
            return
        # check if baseline fit OK
        if self.out is None:
            self.out = Preprocessor(self.wavenumberList[self.selectMapidx], self.dataSets[self.selectMapidx][0])

        # get plotchoice
        plotChoice = self.normBox.currentIndex()
        if plotChoice != 0:
            # calculate rubberband and kohler baseline
            baselineOK = self.out.rubber_band(**self.processArgs) and self.out.kohler(**self.processArgs)
        else:
            MsgBox('Plot type is "Raw spectrum".\nPlease change plot type to "Kohler" or "Rubberband".')
            return
        if not baselineOK:
            return

        # notice to user
        userMsg = YesNoDialog(f'Ready to batch process selected spectra.\nDo you want to continue?')
        userChoice = userMsg.choice()
        if userChoice == QMessageBox.No:  # user choose to stop
            return

        self.isBatchProcessOn = True

        # init resultSetsDict, paramsDict
        self.resultSetsDict = {}
        self.paramsDict = {}
        self.paramsDict['specID'] = []
        self.paramsDict['row_column'] = []
        ind2rc = self.ind2rcList[self.selectMapidx]
        energy = self.out.energy
        n_energy = len(energy)
        for item in self.arrayList:
            self.resultSetsDict[item] = np.empty((0, n_energy))
        for item in self.reportList:
            self.paramsDict[item] = []
        # batch process begins
        n_spectra = self.specItemModel.rowCount()
        for i in range(n_spectra):
            msg.showMessage(f'Processing {i + 1}/{n_spectra} spectra')
            # select each spec and collect results
            self.specSelectModel.select(self.specItemModel.index(i, 0), QItemSelectionModel.ClearAndSelect)
            # get spec idx
            currentSpecItem = self.specItemModel.item(i)
            self.paramsDict['specID'].append(currentSpecItem.idx)
            self.paramsDict['row_column'].append(ind2rc[currentSpecItem.idx])
            # append all results into a single array/list
            for item in self.arrayList:
                self.resultSetsDict[item] = np.append(self.resultSetsDict[item], self.resultDict[item].reshape(1, -1),
                                                      axis=0)
            for item in self.reportList:
                self.paramsDict[item].append(self.resultDict[item])

        # result collection completed. convert paramsDict to df
        self.dfDict = {}
        self.dfDict['param'] = pd.DataFrame(self.paramsDict).set_index('specID')
        for item in self.arrayList:
            # convert resultSetsDict to df
            self.dfDict[item] = pd.DataFrame(self.resultSetsDict[item], columns=energy.tolist(),
                                        index=self.paramsDict['specID'])

        # batch process completed
        self.isBatchProcessOn = False
        msg.showMessage(f'Batch processing is completed! Saving results to csv files.')
        #  save df to files
        self.saveResults()

    def saveResults(self):
        if self.dfDict is None:
            return
        filePath = self.pathList[self.selectMapidx]
        energy = self.out.energy
        saveDataChoice = self.saveResultBox.currentIndex()
        if saveDataChoice != 5:  # save a single result
            saveDataType = self.arrayList[saveDataChoice]
            dirName, csvName, h5Name = self.saveToFiles(energy, self.dfDict, filePath, saveDataType)
            if h5Name is None:
                MsgBox(f'Processed data was saved as csv file at: \n{dirName + csvName}')
            else:
                MsgBox(
                    f'Processed data was saved as: \n\ncsv file at: {dirName + csvName} and \n\nHDF5 file at: {dirName + h5Name}')
        else:  # save all results
            csvList = []
            h5List = []
            for saveDataType in self.arrayList:
                dirName, csvName, h5Name = self.saveToFiles(energy, self.dfDict, filePath, saveDataType)
                csvList.append(csvName)
                h5List.append(h5Name)

            allcsvName = (', ').join(csvList)
            if h5Name is None:
                MsgBox(f'Processed data was saved as csv files at: \n{dirName + allcsvName}')
            else:
                allh5Name = (', ').join(h5List)
                MsgBox(
                    f'Processed data was saved as: \n\ncsv files at: {dirName + allcsvName} and \n\nHDF5 files at: {dirName + allh5Name}')

        # save parameter
        xlsName = csvName[:-4] + '_param.xlsx'
        self.dfDict['param'].to_excel(dirName + xlsName)

    def saveToFiles(self, energy, dfDict, filePath, saveDataType):

        ind2rc = self.ind2rcList[self.selectMapidx]
        n_spectra = self.specItemModel.rowCount()

        # get dirname and old filename
        dirName = os.path.dirname(filePath)
        dirName += '/'
        oldFileName = os.path.basename(filePath)

        # save dataFrames to csv file
        csvName = oldFileName[:-3] + '_' + saveDataType + '.csv'
        dfDict[saveDataType].to_csv(dirName + csvName)

        # if a full map is processed, also save results to a h5 file
        h5Name = None
        if n_spectra == len(ind2rc):
            fullMap = ir_map(filename=filePath)
            fullMap.add_image_cube()
            fullMap.wavenumbers = energy
            fullMap.N_w = len(energy)
            fullMap.data = np.zeros((fullMap.data.shape[0], fullMap.N_w))
            fullMap.imageCube = np.zeros((fullMap.imageCube.shape[0], fullMap.imageCube.shape[1], fullMap.N_w))
            for i in self.paramsDict['specID']:
                fullMap.data[i, :] = self.resultSetsDict[saveDataType][i, :]
                row, col = ind2rc[i]
                fullMap.imageCube[row, col, :] = fullMap.data[i, :] = self.resultSetsDict[saveDataType][i, :]
            # save data as hdf5
            h5Name = oldFileName[:-3] + '_' + saveDataType + '.h5'
            fullMap.write_as_hdf5(dirName + h5Name)

        return dirName, csvName, h5Name
예제 #9
0
파일: switcher.py 프로젝트: wtheis/spyder
class Switcher(QDialog):
    """
    A multi purpose switcher.

    Example
    -------
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherSeparator: [---------------------------------------]
      SwitcherItem:      [title description    <shortcut> section]
      SwitcherItem:      [title description    <shortcut> section]
    """

    # Search/Filter text changes
    sig_text_changed = Signal(TEXT_TYPES[-1])
    # List item selected, mode and cleaned search text
    sig_item_selected = Signal(
        object,
        TEXT_TYPES[-1],
        TEXT_TYPES[-1],
    )
    sig_mode_selected = Signal(TEXT_TYPES[-1])

    _MIN_WIDTH = 500

    def __init__(self,
                 parent,
                 help_text=None,
                 item_styles=ITEM_STYLES,
                 item_separator_styles=ITEM_SEPARATOR_STYLES):
        """Multi purpose switcher."""
        super(Switcher, self).__init__(parent)
        self._visible_rows = 0
        self._modes = {}
        self._mode_on = ''
        self._item_styles = item_styles
        self._item_separator_styles = item_separator_styles

        # Widgets
        self.edit = QLineEdit(self)
        self.list = QListView(self)
        self.model = QStandardItemModel(self.list)
        self.proxy = SwitcherProxyModel(self.list)
        self.filter = KeyPressFilter()

        # Widgets setup
        # self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setPlaceholderText(help_text if help_text else '')
        self.list.setMinimumWidth(self._MIN_WIDTH)
        self.list.setItemDelegate(HTMLDelegate(self))
        self.list.setFocusPolicy(Qt.NoFocus)
        self.list.setSelectionBehavior(self.list.SelectRows)
        self.list.setSelectionMode(self.list.SingleSelection)
        self.proxy.setSourceModel(self.model)
        self.list.setModel(self.proxy)

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.filter.sig_enter_key_pressed.connect(self.enter)
        self.edit.textChanged.connect(self.setup)
        self.edit.textChanged.connect(self.sig_text_changed)
        self.edit.returnPressed.connect(self.enter)
        self.list.clicked.connect(self.enter)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Helper methods
    def _add_item(self, item):
        """Perform common actions when adding items."""
        item.set_width(self._MIN_WIDTH)
        self.model.appendRow(item)
        self.set_current_row(0)
        self._visible_rows = self.model.rowCount()
        self.setup_sections()

    # --- API
    def clear(self):
        """Remove all items from the list and clear the search text."""
        self.set_placeholder_text('')
        self.model.beginResetModel()
        self.model.clear()
        self.model.endResetModel()

    def set_placeholder_text(self, text):
        """Set the text appearing on the empty line edit."""
        self.edit.setPlaceholderText(text)

    def add_mode(self, token, description):
        """Add mode by token key and description."""
        if len(token) == 1:
            self._modes[token] = description
        else:
            raise Exception('Token must be of length 1!')

    def remove_mode(self, token):
        """Remove mode by token key."""
        if token in self._modes:
            self._modes.pop(token)

    def clear_modes(self):
        """Delete all modes spreviously defined."""
        del self._modes
        self._modes = {}

    def add_item(self,
                 icon=None,
                 title=None,
                 description=None,
                 shortcut=None,
                 section=None,
                 data=None,
                 tool_tip=None,
                 action_item=False):
        """Add switcher list item."""
        item = SwitcherItem(parent=self.list,
                            icon=icon,
                            title=title,
                            description=description,
                            data=data,
                            shortcut=shortcut,
                            section=section,
                            action_item=action_item,
                            tool_tip=tool_tip,
                            styles=self._item_styles)
        self._add_item(item)

    def add_separator(self):
        """Add separator item."""
        item = SwitcherSeparatorItem(parent=self.list,
                                     styles=self._item_separator_styles)
        self._add_item(item)

    def setup(self):
        """Set-up list widget content based on the filtering."""
        # Check exited mode
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        # Check exited mode
        if mode and self.search_text() == '':
            self._mode_on = ''
            self.sig_mode_selected.emit(self._mode_on)
            return

        # Check entered mode
        for key in self._modes:
            if self.search_text().startswith(key) and not mode:
                self._mode_on = key
                self.sig_mode_selected.emit(key)
                return

        # Filter by text
        titles = []
        for row in range(self.model.rowCount()):
            item = self.model.item(row)
            if isinstance(item, SwitcherItem):
                title = item.get_title()
            else:
                title = ''
            titles.append(title)

        search_text = clean_string(search_text)
        scores = get_search_scores(to_text_string(search_text),
                                   titles,
                                   template=u"<b>{0}</b>")

        self._visible_rows = self.model.rowCount()
        for idx, score in enumerate(scores):
            title, rich_title, score_value = score
            item = self.model.item(idx)

            if not self._is_separator(item):
                item.set_rich_title(rich_title)

            item.set_score(score_value)
            proxy_index = self.proxy.mapFromSource(self.model.index(idx, 0))

            if not item.is_action_item():
                self.list.setRowHidden(proxy_index.row(), score_value == -1)

                if score_value == -1:
                    self._visible_rows -= 1

        if self._visible_rows:
            self.set_current_row(0)
        else:
            self.set_current_row(-1)

        self.setup_sections()

    def setup_sections(self):
        """Set-up which sections appear on the item list."""
        mode = self._mode_on
        if mode:
            search_text = self.search_text()[len(mode):]
        else:
            search_text = self.search_text()

        if search_text:
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    item.set_section_visible(False)
        else:
            sections = []
            for row in range(self.model.rowCount()):
                item = self.model.item(row)
                if isinstance(item, SwitcherItem):
                    sections.append(item.get_section())
                    item.set_section_visible(bool(search_text))
                else:
                    sections.append('')

                if row != 0:
                    visible = sections[row] != sections[row - 1]
                    if not self._is_separator(item):
                        item.set_section_visible(visible)
                else:
                    item.set_section_visible(True)

        self.proxy.sortBy('_score')

    # --- Qt overrides
    # ------------------------------------------------------------------------
    @Slot()
    @Slot(QListWidgetItem)
    def enter(self, itemClicked=None):
        """Override Qt method."""
        row = self.current_row()
        model_index = self.proxy.mapToSource(self.proxy.index(row, 0))
        item = self.model.item(model_index.row())
        if item:
            mode = self._mode_on
            self.sig_item_selected.emit(item, mode,
                                        self.search_text()[len(mode):])

    def accept(self):
        """Override Qt method."""
        super(Switcher, self).accept()

    def resizeEvent(self, event):
        """Override Qt method."""
        super(Switcher, self).resizeEvent(event)

    # --- Helper methods: Lineedit widget
    def search_text(self):
        """Get the normalized (lowecase) content of the search text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, string):
        """Set the content of the search text."""
        self.edit.setText(string)

    # --- Helper methods: List widget
    def _is_separator(self, item):
        """Check if item is an separator item (SwitcherSeparatorItem)."""
        return isinstance(item, SwitcherSeparatorItem)

    def _select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def count(self):
        """Get the item count in the list widget."""
        return self._visible_rows

    def current_row(self):
        """Return the current selected row in the list widget."""
        return self.list.currentIndex().row()

    def set_current_row(self, row):
        """Set the current selected row in the list widget."""
        index = self.model.index(row, 0)
        selection_model = self.list.selectionModel()

        # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum
        selection_model.setCurrentIndex(index, selection_model.ClearAndSelect)

    def previous_row(self):
        """Select previous row in list widget."""
        steps = 1
        prev_row = self.current_row() - steps

        if prev_row == -1:
            self.set_current_row(self.count() - 1)
        else:
            if prev_row >= 0:
                # Need to map the filtered list to the actual model items
                list_index = self.proxy.index(prev_row, 0)
                model_index = self.proxy.mapToSource(list_index)
                item = self.model.item(model_index.row(), 0)
                if self._is_separator(item):
                    steps += 1
            self._select_row(-steps)

    def next_row(self):
        """Select next row in list widget."""
        steps = 1
        next_row = self.current_row() + steps

        # Need to map the filtered list to the actual model items
        list_index = self.proxy.index(next_row, 0)
        model_index = self.proxy.mapToSource(list_index)
        item = self.model.item(model_index.row(), 0)

        if next_row >= self.count():
            self.set_current_row(0)
        else:
            if item:
                if self._is_separator(item):
                    steps += 1
            self._select_row(steps)
예제 #10
0
class ProgressView(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, parent, batch_manager):
        super().__init__(parent)
        self.task_count = 0
        self.calculation_manager = batch_manager
        self.whole_progress = QProgressBar(self)
        self.whole_progress.setMinimum(0)
        self.whole_progress.setMaximum(1)
        self.whole_progress.setFormat("%v of %m")
        self.whole_progress.setTextVisible(True)
        self.part_progress = QProgressBar(self)
        self.part_progress.setMinimum(0)
        self.part_progress.setMaximum(1)
        self.part_progress.setFormat("%v of %m")
        self.whole_label = QLabel("All batch progress:", self)
        self.part_label = QLabel("Single batch progress:", self)
        self.cancel_remove_btn = QPushButton("Remove task")
        self.cancel_remove_btn.setDisabled(True)
        self.logs = ExceptionList(self)
        self.logs.setToolTip("Logs")
        self.task_view = QListView()
        self.task_que = QStandardItemModel(self)
        self.task_view.setModel(self.task_que)
        self.process_num_timer = QTimer()
        self.process_num_timer.setInterval(1000)
        self.process_num_timer.setSingleShot(True)
        self.process_num_timer.timeout.connect(self.change_number_of_workers)
        self.number_of_process = QSpinBox(self)
        self.number_of_process.setRange(1, multiprocessing.cpu_count())
        self.number_of_process.setValue(1)
        self.number_of_process.setToolTip(
            "Number of process used in batch calculation")
        self.number_of_process.valueChanged.connect(
            self.process_num_timer_start)
        self.progress_item_dict = {}
        layout = QGridLayout()
        layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight)
        layout.addWidget(self.whole_progress, 0, 1, 1, 2)
        layout.addWidget(self.part_label, 1, 0, Qt.AlignRight)
        layout.addWidget(self.part_progress, 1, 1, 1, 2)
        lab = QLabel("Number of process:")
        lab.setToolTip("Number of process used in batch calculation")
        layout.addWidget(lab, 2, 0)
        layout.addWidget(self.number_of_process, 2, 1)
        layout.addWidget(self.logs, 3, 0, 2, 3)
        layout.addWidget(self.task_view, 0, 4, 4, 1)
        layout.addWidget(self.cancel_remove_btn, 4, 4, 1, 1)
        layout.setColumnMinimumWidth(2, 10)
        layout.setColumnStretch(2, 1)
        self.setLayout(layout)
        self.preview_timer = QTimer()
        self.preview_timer.setInterval(1000)
        self.preview_timer.timeout.connect(self.update_info)
        self.task_view.selectionModel().currentChanged.connect(
            self.task_selection_change)
        self.cancel_remove_btn.clicked.connect(self.task_cancel_remove)

    def task_selection_change(self, new, old):
        task: CalculationProcessItem = self.task_que.item(
            new.row(), new.column())
        if task is None:
            self.cancel_remove_btn.setDisabled(True)
            return
        self.cancel_remove_btn.setEnabled(True)
        if task.is_finished():
            self.cancel_remove_btn.setText(f"Remove task {task.num}")
        else:
            self.cancel_remove_btn.setText(f"Cancel task {task.num}")

    def task_cancel_remove(self):
        index = self.task_view.selectionModel().currentIndex()
        task: CalculationProcessItem = self.task_que.item(
            index.row(), index.column())
        if task.is_finished():
            self.calculation_manager.remove_calculation(task.calculation)
            self.task_que.takeRow(index.row())
        else:
            self.calculation_manager.cancel_calculation(task.calculation)
        print(task)

    def new_task(self):
        self.whole_progress.setMaximum(
            self.calculation_manager.calculation_size)
        if not self.preview_timer.isActive():
            self.update_info()
            self.preview_timer.start()

    def update_info(self):
        res = self.calculation_manager.get_results()
        for el in res.errors:
            if el[0]:
                QListWidgetItem(el[0], self.logs)
            ExceptionListItem(el[1], self.logs)
            if (state_store.report_errors and parsed_version.is_devrelease
                    and not isinstance(el[1][0], SegmentationLimitException)
                    and isinstance(el[1][1], tuple)):
                with sentry_sdk.push_scope() as scope:
                    scope.set_tag("auto_report", "true")
                    sentry_sdk.capture_event(el[1][1][0])
        self.whole_progress.setValue(res.global_counter)
        working_search = True
        for uuid, progress in res.jobs_status.items():
            calculation = self.calculation_manager.calculation_dict[uuid]
            total = len(calculation.file_list)
            if uuid in self.progress_item_dict:
                item = self.progress_item_dict[uuid]
                item.update_count(progress)
            else:
                item = CalculationProcessItem(calculation, self.task_count,
                                              progress)
                self.task_count += 1
                self.task_que.appendRow(item)
                self.progress_item_dict[uuid] = item

            if working_search and progress != total:
                self.part_progress.setMaximum(total)
                self.part_progress.setValue(progress)
                working_search = False
        if not self.calculation_manager.has_work:
            self.part_progress.setValue(self.part_progress.maximum())
            self.preview_timer.stop()
            logging.info("Progress stop")

    def process_num_timer_start(self):
        self.process_num_timer.start()

    def update_progress(self, total_progress, part_progress):
        self.whole_progress.setValue(total_progress)
        self.part_progress.setValue(part_progress)

    def set_total_size(self, size):
        self.whole_progress.setMaximum(size)

    def set_part_size(self, size):
        self.part_progress.setMaximum(size)

    def change_number_of_workers(self):
        self.calculation_manager.set_number_of_workers(
            self.number_of_process.value())