def process_duplicates(self, db, duplicates): ta = _('%(title)s by %(author)s [%(formats)s]') bf = QFont(self.dup_list.font()) bf.setBold(True) itf = QFont(self.dup_list.font()) itf.setItalic(True) for mi, cover, formats in duplicates: # formats is a list of file paths # Grab just the extension and display to the user # Based only off the file name, no file type tests are done. incoming_formats = ', '.join( os.path.splitext(path)[-1].replace('.', '').upper() for path in formats) item = QTreeWidgetItem([ ta % dict(title=mi.title, author=mi.format_field('authors')[1], formats=incoming_formats) ], 0) item.setCheckState(0, Qt.CheckState.Checked) item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) item.setData(0, Qt.ItemDataRole.FontRole, bf) item.setData(0, Qt.ItemDataRole.UserRole, (mi, cover, formats)) matching_books = db.books_with_same_title(mi) def add_child(text): c = QTreeWidgetItem([text], 1) c.setFlags(Qt.ItemFlag.ItemIsEnabled) item.addChild(c) return c add_child(_('Already in calibre:')).setData( 0, Qt.ItemDataRole.FontRole, itf) author_text = {} for book_id in matching_books: author_text[book_id] = authors_to_string([ a.replace('|', ',') for a in ( db.authors(book_id, index_is_id=True) or '').split(',') ]) def key(x): return primary_sort_key(str(author_text[x])) for book_id in sorted(matching_books, key=key): add_child( ta % dict(title=db.title(book_id, index_is_id=True), author=author_text[book_id], formats=db.formats( book_id, index_is_id=True, verify_formats=False))) add_child('') yield item
def paint_line_numbers(self, ev): painter = QPainter(self.line_number_area) painter.fillRect( ev.rect(), self.line_number_palette.color(QPalette.ColorRole.Base)) block = self.firstVisibleBlock() num = block.blockNumber() top = int( self.blockBoundingGeometry(block).translated( self.contentOffset()).top()) bottom = top + int(self.blockBoundingRect(block).height()) current = self.textCursor().block().blockNumber() painter.setPen(self.line_number_palette.color(QPalette.ColorRole.Text)) while block.isValid() and top <= ev.rect().bottom(): if block.isVisible() and bottom >= ev.rect().top(): set_bold = False set_italic = False if current == num: set_bold = True if num + 1 in self.clicked_line_numbers: set_italic = True painter.save() if set_bold or set_italic: f = QFont(self.font()) if set_bold: f.setBold(set_bold) painter.setPen( self.line_number_palette.color( QPalette.ColorRole.BrightText)) f.setItalic(set_italic) painter.setFont(f) else: painter.setFont(self.font()) painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(), Qt.AlignmentFlag.AlignRight, unicode_type(num + 1)) painter.restore() block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) num += 1
def __init__(self, toc=None): QStandardItemModel.__init__(self) self.current_query = {'text': '', 'index': -1, 'items': ()} self.all_items = depth_first = [] normal_font = QApplication.instance().font() emphasis_font = QFont(normal_font) emphasis_font.setBold(True), emphasis_font.setItalic(True) if toc: for t in toc['children']: self.appendRow( TOCItem(t, 0, depth_first, normal_font, emphasis_font)) self.node_id_map = {x.node_id: x for x in self.all_items}
class Results(QTreeWidget): # {{{ show_search_result = pyqtSignal(object) current_result_changed = pyqtSignal(object) count_changed = pyqtSignal(object) def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.setHeaderHidden(True) self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.delegate = ResultsDelegate(self) self.setItemDelegate(self.delegate) self.itemClicked.connect(self.item_activated) self.blank_icon = QIcon(I('blank.png')) self.not_found_icon = QIcon(I('dialog_warning.png')) self.currentItemChanged.connect(self.current_item_changed) self.section_font = QFont(self.font()) self.section_font.setItalic(True) self.section_map = {} self.search_results = [] self.item_map = {} self.gesture_manager = GestureManager(self) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) def viewportEvent(self, ev): try: ret = self.gesture_manager.handle_event(ev) except AttributeError: ret = None if ret is not None: return ret return super().viewportEvent(ev) def current_item_changed(self, current, previous): if current is not None: r = current.data(0, SEARCH_RESULT_ROLE) if isinstance(r, SearchResult): self.current_result_changed.emit(r) else: self.current_result_changed.emit(None) def add_result(self, result): section_title = _('Unknown') section_id = -1 toc_nodes = getattr(result, 'toc_nodes', ()) or () if toc_nodes: section_title = toc_nodes[-1].get('title') or _('Unknown') section_id = toc_nodes[-1].get('id') if section_id is None: section_id = -1 section_key = section_id section = self.section_map.get(section_key) spine_idx = getattr(result, 'spine_idx', -1) if section is None: section = QTreeWidgetItem([section_title], 1) section.setFlags(Qt.ItemFlag.ItemIsEnabled) section.setFont(0, self.section_font) section.setData(0, SPINE_IDX_ROLE, spine_idx) lines = [] for i, node in enumerate(toc_nodes): lines.append('\xa0\xa0' * i + '➤ ' + (node.get('title') or _('Unknown'))) if lines: tt = ngettext('Table of Contents section:', 'Table of Contents sections:', len(lines)) tt += '\n' + '\n'.join(lines) section.setToolTip(0, tt) self.section_map[section_key] = section for s in range(self.topLevelItemCount()): ti = self.topLevelItem(s) if ti.data(0, SPINE_IDX_ROLE) > spine_idx: self.insertTopLevelItem(s, section) break else: self.addTopLevelItem(section) section.setExpanded(True) item = QTreeWidgetItem(section, [' '], 2) item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemNeverHasChildren) item.setData(0, SEARCH_RESULT_ROLE, result) item.setData(0, RESULT_NUMBER_ROLE, len(self.search_results)) item.setData(0, SPINE_IDX_ROLE, spine_idx) if isinstance(result, SearchResult): tt = '<p>…' + escape(result.before, False) + '<b>' + escape( result.text, False) + '</b>' + escape(result.after, False) + '…' item.setData(0, Qt.ItemDataRole.ToolTipRole, tt) item.setIcon(0, self.blank_icon) self.item_map[len(self.search_results)] = item self.search_results.append(result) n = self.number_of_results self.count_changed.emit(n) def item_activated(self): i = self.currentItem() if i: sr = i.data(0, SEARCH_RESULT_ROLE) if isinstance(sr, SearchResult): if not sr.is_hidden: self.show_search_result.emit(sr) def find_next(self, previous): if self.number_of_results < 1: return item = self.currentItem() if item is None: return i = int(item.data(0, RESULT_NUMBER_ROLE)) i += -1 if previous else 1 i %= self.number_of_results self.setCurrentItem(self.item_map[i]) self.item_activated() def search_result_not_found(self, sr): for i in range(self.number_of_results): item = self.item_map[i] r = item.data(0, SEARCH_RESULT_ROLE) if r.is_result(sr): r.is_hidden = True item.setIcon(0, self.not_found_icon) break def search_result_discovered(self, sr): q = sr['result_num'] for i in range(self.number_of_results): item = self.item_map[i] r = item.data(0, SEARCH_RESULT_ROLE) if r.result_num == q: self.setCurrentItem(item) @property def current_result_is_hidden(self): item = self.currentItem() if item is not None: sr = item.data(0, SEARCH_RESULT_ROLE) if isinstance(sr, SearchResult) and sr.is_hidden: return True return False @property def number_of_results(self): return len(self.search_results) def clear_all_results(self): self.section_map = {} self.item_map = {} self.search_results = [] self.clear() self.count_changed.emit(-1) def select_first_result(self): if self.number_of_results: item = self.item_map[0] self.setCurrentItem(item) def ensure_current_result_visible(self): item = self.currentItem() if item is not None: self.scrollToItem(item)
def mark_item_as_current(self, item): font = QFont(self.font()) font.setItalic(True) font.setBold(True) item.setData(0, Qt.ItemDataRole.FontRole, font)
class CompareSingle(QWidget): def __init__(self, field_metadata, parent=None, revert_tooltip=None, datetime_fmt='MMMM yyyy', blank_as_equal=True, fields=('title', 'authors', 'series', 'tags', 'rating', 'publisher', 'pubdate', 'identifiers', 'languages', 'comments', 'cover'), db=None): QWidget.__init__(self, parent) self.l = l = QGridLayout() # l.setContentsMargins(0, 0, 0, 0) self.setLayout(l) revert_tooltip = revert_tooltip or _('Revert %s') self.current_mi = None self.changed_font = QFont(QApplication.font()) self.changed_font.setBold(True) self.changed_font.setItalic(True) self.blank_as_equal = blank_as_equal self.widgets = OrderedDict() row = 0 for field in fields: m = field_metadata[field] dt = m['datatype'] extra = None if 'series' in {field, dt}: cls = SeriesEdit elif field == 'identifiers': cls = IdentifiersEdit elif field == 'languages': cls = LanguagesEdit elif 'comments' in {field, dt}: cls = CommentsEdit elif 'rating' in {field, dt}: cls = RatingsEdit elif dt == 'datetime': extra = datetime_fmt cls = DateEdit elif field == 'cover': cls = CoverView elif dt in {'text', 'enum'}: cls = LineEdit else: continue neww = cls(field, True, self, m, extra) neww.setObjectName(field) connect_lambda( neww.changed, self, lambda self: self.changed(self.sender().objectName())) if isinstance(neww, EditWithComplete): try: neww.update_items_cache(db.new_api.all_field_names(field)) except ValueError: pass # A one-one field like title if isinstance(neww, SeriesEdit): neww.set_db(db.new_api) oldw = cls(field, False, self, m, extra) newl = QLabel('&%s:' % m['name']) newl.setBuddy(neww) button = RightClickButton(self) button.setIcon(QIcon(I('back.png'))) button.setObjectName(field) connect_lambda( button.clicked, self, lambda self: self.revert(self.sender().objectName())) button.setToolTip(revert_tooltip % m['name']) if field == 'identifiers': button.m = m = QMenu(button) button.setMenu(m) button.setPopupMode( QToolButton.ToolButtonPopupMode.DelayedPopup) m.addAction(button.toolTip()).triggered.connect(button.click) m.actions()[0].setIcon(button.icon()) m.addAction(_('Merge identifiers')).triggered.connect( self.merge_identifiers) m.actions()[1].setIcon(QIcon(I('merge.png'))) elif field == 'tags': button.m = m = QMenu(button) button.setMenu(m) button.setPopupMode( QToolButton.ToolButtonPopupMode.DelayedPopup) m.addAction(button.toolTip()).triggered.connect(button.click) m.actions()[0].setIcon(button.icon()) m.addAction(_('Merge tags')).triggered.connect(self.merge_tags) m.actions()[1].setIcon(QIcon(I('merge.png'))) self.widgets[field] = Widgets(neww, oldw, newl, button) for i, w in enumerate((newl, neww, button, oldw)): c = i if i < 2 else i + 1 if w is oldw: c += 1 l.addWidget(w, row, c) row += 1 if 'comments' in self.widgets and not gprefs.get( 'diff_widget_show_comments_controls', True): self.widgets['comments'].new.hide_toolbars() def save_comments_controls_state(self): if 'comments' in self.widgets: vis = self.widgets['comments'].new.toolbars_visible if vis != gprefs.get('diff_widget_show_comments_controls', True): gprefs.set('diff_widget_show_comments_controls', vis) def changed(self, field): w = self.widgets[field] if not w.new.same_as(w.old) and (not self.blank_as_equal or not w.new.is_blank): w.label.setFont(self.changed_font) else: w.label.setFont(QApplication.font()) def revert(self, field): widgets = self.widgets[field] neww, oldw = widgets[:2] if hasattr(neww, 'set_undoable'): neww.set_undoable(oldw.current_val) else: neww.current_val = oldw.current_val def merge_identifiers(self): widgets = self.widgets['identifiers'] neww, oldw = widgets[:2] val = neww.as_dict val.update(oldw.as_dict) neww.as_dict = val def merge_tags(self): widgets = self.widgets['tags'] neww, oldw = widgets[:2] val = oldw.value lval = {icu_lower(x) for x in val} extra = [x for x in neww.value if icu_lower(x) not in lval] if extra: neww.value = val + extra def __call__(self, oldmi, newmi): self.current_mi = newmi self.initial_vals = {} for field, widgets in iteritems(self.widgets): widgets.old.from_mi(oldmi) widgets.new.from_mi(newmi) self.initial_vals[field] = widgets.new.current_val def apply_changes(self): changed = False for field, widgets in iteritems(self.widgets): val = widgets.new.current_val if val != self.initial_vals[field]: widgets.new.to_mi(self.current_mi) changed = True return changed
class ResultsList(QTreeWidget): current_result_changed = pyqtSignal(object) open_annotation = pyqtSignal(object, object, object) show_book = pyqtSignal(object, object) delete_requested = pyqtSignal() export_requested = pyqtSignal() edit_annotation = pyqtSignal(object, object) def __init__(self, parent): QTreeWidget.__init__(self, parent) self.setHeaderHidden(True) self.setSelectionMode( QAbstractItemView.SelectionMode.ExtendedSelection) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) self.delegate = AnnotsResultsDelegate(self) self.setItemDelegate(self.delegate) self.section_font = QFont(self.font()) self.itemDoubleClicked.connect(self.item_activated) self.section_font.setItalic(True) self.currentItemChanged.connect(self.current_item_changed) self.number_of_results = 0 self.item_map = [] def show_context_menu(self, pos): item = self.itemAt(pos) if item is not None: result = item.data(0, Qt.ItemDataRole.UserRole) else: result = None items = self.selectedItems() m = QMenu(self) if isinstance(result, dict): m.addAction(_('Open in viewer'), partial(self.item_activated, item)) m.addAction(_('Show in calibre'), partial(self.show_in_calibre, item)) if result.get('annotation', {}).get('type') == 'highlight': m.addAction(_('Edit notes'), partial(self.edit_notes, item)) if items: m.addSeparator() m.addAction( ngettext('Export selected item', 'Export {} selected items', len(items)).format(len(items)), self.export_requested.emit) m.addAction( ngettext('Delete selected item', 'Delete {} selected items', len(items)).format(len(items)), self.delete_requested.emit) m.addSeparator() m.addAction(_('Expand all'), self.expandAll) m.addAction(_('Collapse all'), self.collapseAll) m.exec(self.mapToGlobal(pos)) def edit_notes(self, item): r = item.data(0, Qt.ItemDataRole.UserRole) if isinstance(r, dict): self.edit_annotation.emit(r['id'], r['annotation']) def show_in_calibre(self, item): r = item.data(0, Qt.ItemDataRole.UserRole) if isinstance(r, dict): self.show_book.emit(r['book_id'], r['format']) def item_activated(self, item): r = item.data(0, Qt.ItemDataRole.UserRole) if isinstance(r, dict): self.open_annotation.emit(r['book_id'], r['format'], r['annotation']) def set_results(self, results, emphasize_text): self.clear() self.delegate.emphasize_text = emphasize_text self.number_of_results = 0 self.item_map = [] book_id_map = {} db = current_db() for result in results: book_id = result['book_id'] if book_id not in book_id_map: book_id_map[book_id] = { 'title': db.field_for('title', book_id), 'matches': [] } book_id_map[book_id]['matches'].append(result) for book_id, entry in book_id_map.items(): section = QTreeWidgetItem([entry['title']], 1) section.setFlags(Qt.ItemFlag.ItemIsEnabled) section.setFont(0, self.section_font) section.setData(0, Qt.ItemDataRole.UserRole, book_id) self.addTopLevelItem(section) section.setExpanded(True) for result in sorted_items(entry['matches']): item = QTreeWidgetItem(section, [' '], 2) self.item_map.append(item) item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemNeverHasChildren) item.setData(0, Qt.ItemDataRole.UserRole, result) item.setData(0, Qt.ItemDataRole.UserRole + 1, self.number_of_results) self.number_of_results += 1 if self.item_map: self.setCurrentItem(self.item_map[0]) def current_item_changed(self, current, previous): if current is not None: r = current.data(0, Qt.ItemDataRole.UserRole) if isinstance(r, dict): self.current_result_changed.emit(r) else: self.current_result_changed.emit(None) def show_next(self, backwards=False): item = self.currentItem() if item is None: return i = int(item.data(0, Qt.ItemDataRole.UserRole + 1)) i += -1 if backwards else 1 i %= self.number_of_results self.setCurrentItem(self.item_map[i]) @property def selected_annot_ids(self): for item in self.selectedItems(): yield item.data(0, Qt.ItemDataRole.UserRole)['id'] @property def selected_annotations(self): for item in self.selectedItems(): x = item.data(0, Qt.ItemDataRole.UserRole) ans = x['annotation'].copy() for key in ('book_id', 'format'): ans[key] = x[key] yield ans def keyPressEvent(self, ev): if ev.matches(QKeySequence.StandardKey.Delete): self.delete_requested.emit() ev.accept() return if ev.key() == Qt.Key.Key_F2: item = self.currentItem() if item: self.edit_notes(item) ev.accept() return return QTreeWidget.keyPressEvent(self, ev) @property def tree_state(self): ans = {'closed': set()} item = self.currentItem() if item is not None: ans['current'] = item.data(0, Qt.ItemDataRole.UserRole) for item in (self.topLevelItem(i) for i in range(self.topLevelItemCount())): if not item.isExpanded(): ans['closed'].add(item.data(0, Qt.ItemDataRole.UserRole)) return ans @tree_state.setter def tree_state(self, state): closed = state['closed'] for item in (self.topLevelItem(i) for i in range(self.topLevelItemCount())): if item.data(0, Qt.ItemDataRole.UserRole) in closed: item.setExpanded(False) cur = state.get('current') if cur is not None: for item in self.item_map: if item.data(0, Qt.ItemDataRole.UserRole) == cur: self.setCurrentItem(item) break
class Highlights(QTreeWidget): jump_to_highlight = pyqtSignal(object) current_highlight_changed = pyqtSignal(object) delete_requested = pyqtSignal() edit_requested = pyqtSignal() edit_notes_requested = pyqtSignal() def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) self.default_decoration = QIcon(I('blank.png')) self.setHeaderHidden(True) self.num_of_items = 0 self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) set_no_activate_on_click(self) self.itemActivated.connect(self.item_activated) self.currentItemChanged.connect(self.current_item_changed) self.uuid_map = {} self.section_font = QFont(self.font()) self.section_font.setItalic(True) self.gesture_manager = GestureManager(self) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) def viewportEvent(self, ev): try: ret = self.gesture_manager.handle_event(ev) except AttributeError: ret = None if ret is not None: return ret return super().viewportEvent(ev) def show_context_menu(self, point): index = self.indexAt(point) h = index.data(Qt.ItemDataRole.UserRole) self.context_menu = m = QMenu(self) if h is not None: m.addAction(QIcon(I('edit_input.png')), _('Modify this highlight'), self.edit_requested.emit) m.addAction(QIcon(I('modified.png')), _('Edit notes for this highlight'), self.edit_notes_requested.emit) m.addAction(QIcon(I('trash.png')), ngettext( 'Delete this highlight', 'Delete selected highlights', len(self.selectedItems()) ), self.delete_requested.emit) m.addSeparator() m.addAction(QIcon.ic('plus.png'), _('Expand all'), self.expandAll) m.addAction(QIcon.ic('minus.png'), _('Collapse all'), self.collapseAll) self.context_menu.popup(self.mapToGlobal(point)) return True def current_item_changed(self, current, previous): self.current_highlight_changed.emit(current.data(0, Qt.ItemDataRole.UserRole) if current is not None else None) def load(self, highlights, preserve_state=False): s = self.style() expanded_chapters = set() if preserve_state: root = self.invisibleRootItem() for i in range(root.childCount()): chapter = root.child(i) if chapter.isExpanded(): expanded_chapters.add(chapter.data(0, Qt.ItemDataRole.DisplayRole)) icon_size = s.pixelMetric(QStyle.PixelMetric.PM_SmallIconSize, None, self) dpr = self.devicePixelRatioF() is_dark = is_dark_theme() self.clear() self.uuid_map = {} highlights = (h for h in highlights if not h.get('removed') and h.get('highlighted_text')) section_map = defaultdict(list) section_tt_map = {} for h in self.sorted_highlights(highlights): tfam = h.get('toc_family_titles') or () if tfam: tsec = tfam[0] lsec = tfam[-1] else: tsec = h.get('top_level_section_title') lsec = h.get('lowest_level_section_title') sec = lsec or tsec or _('Unknown') if len(tfam) > 1: lines = [] for i, node in enumerate(tfam): lines.append('\xa0\xa0' * i + '➤ ' + node) tt = ngettext('Table of Contents section:', 'Table of Contents sections:', len(lines)) tt += '\n' + '\n'.join(lines) section_tt_map[sec] = tt section_map[sec].append(h) for secnum, (sec, items) in enumerate(section_map.items()): section = QTreeWidgetItem([sec], 1) section.setFlags(Qt.ItemFlag.ItemIsEnabled) section.setFont(0, self.section_font) tt = section_tt_map.get(sec) if tt: section.setToolTip(0, tt) self.addTopLevelItem(section) section.setExpanded(not preserve_state or sec in expanded_chapters) for itemnum, h in enumerate(items): txt = h.get('highlighted_text') txt = txt.replace('\n', ' ') if h.get('notes'): txt = '•' + txt if len(txt) > 100: txt = txt[:100] + '…' item = QTreeWidgetItem(section, [txt], 2) item.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemNeverHasChildren) item.setData(0, Qt.ItemDataRole.UserRole, h) try: dec = decoration_for_style(self.palette(), h.get('style') or {}, icon_size, dpr, is_dark) except Exception: import traceback traceback.print_exc() dec = None if dec is None: dec = self.default_decoration item.setData(0, Qt.ItemDataRole.DecorationRole, dec) self.uuid_map[h['uuid']] = secnum, itemnum self.num_of_items += 1 def sorted_highlights(self, highlights): def_idx = 999999999999999 defval = def_idx, cfi_sort_key('/99999999') def cfi_key(h): cfi = h.get('start_cfi') si = h.get('spine_index', def_idx) return (si, cfi_sort_key(cfi)) if cfi else defval return sorted(highlights, key=cfi_key) def refresh(self, highlights): h = self.current_highlight self.load(highlights, preserve_state=True) if h is not None: idx = self.uuid_map.get(h['uuid']) if idx is not None: sec_idx, item_idx = idx self.set_current_row(sec_idx, item_idx) def iteritems(self): root = self.invisibleRootItem() for i in range(root.childCount()): sec = root.child(i) for k in range(sec.childCount()): yield sec.child(k) def count(self): return self.num_of_items def find_query(self, query): pat = query.regex items = tuple(self.iteritems()) count = len(items) cr = -1 ch = self.current_highlight if ch: q = ch['uuid'] for i, item in enumerate(items): h = item.data(0, Qt.ItemDataRole.UserRole) if h['uuid'] == q: cr = i if query.backwards: if cr < 0: cr = count indices = chain(range(cr - 1, -1, -1), range(count - 1, cr, -1)) else: if cr < 0: cr = -1 indices = chain(range(cr + 1, count), range(0, cr + 1)) for i in indices: h = items[i].data(0, Qt.ItemDataRole.UserRole) if pat.search(h['highlighted_text']) is not None or pat.search(h.get('notes') or '') is not None: self.set_current_row(*self.uuid_map[h['uuid']]) return True return False def find_annot_id(self, annot_id): q = self.uuid_map.get(annot_id) if q is not None: self.set_current_row(*q) return True return False def set_current_row(self, sec_idx, item_idx): sec = self.topLevelItem(sec_idx) if sec is not None: item = sec.child(item_idx) if item is not None: self.setCurrentItem(item, 0, QItemSelectionModel.SelectionFlag.ClearAndSelect) return True return False def item_activated(self, item): h = item.data(0, Qt.ItemDataRole.UserRole) if h is not None: self.jump_to_highlight.emit(h) @property def current_highlight(self): i = self.currentItem() if i is not None: return i.data(0, Qt.ItemDataRole.UserRole) @property def all_highlights(self): for item in self.iteritems(): yield item.data(0, Qt.ItemDataRole.UserRole) @property def selected_highlights(self): for item in self.selectedItems(): yield item.data(0, Qt.ItemDataRole.UserRole) def keyPressEvent(self, ev): if ev.matches(QKeySequence.StandardKey.Delete): self.delete_requested.emit() ev.accept() return if ev.key() == Qt.Key.Key_F2: self.edit_requested.emit() ev.accept() return return super().keyPressEvent(ev)