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()
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)
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(" ", " ") 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)
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()
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()
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
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')
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
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)
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())