def create_filterable_names_list(names, filter_text=None, parent=None, model=NamesModel): nl = QListView(parent) nl.m = m = model(names, parent=nl) connect_lambda(m.filtered, nl, lambda nl, all_items: nl.scrollTo(m.index(0))) nl.setModel(m) if model is NamesModel: nl.d = NamesDelegate(nl) nl.setItemDelegate(nl.d) f = QLineEdit(parent) f.setPlaceholderText(filter_text or '') f.textEdited.connect(m.filter) return nl, f
class ItemEdit(QWidget): def __init__(self, parent, prefs=None): QWidget.__init__(self, parent) self.prefs = prefs or gprefs self.pending_search = None self.current_frag = None self.setLayout(QVBoxLayout()) self.la = la = QLabel( '<b>' + _('Select a destination for the Table of Contents entry')) self.layout().addWidget(la) self.splitter = sp = QSplitter(self) self.layout().addWidget(sp) self.layout().setStretch(1, 10) sp.setOpaqueResize(False) sp.setChildrenCollapsible(False) self.dest_list = dl = QListWidget(self) dl.setMinimumWidth(250) dl.currentItemChanged.connect(self.current_changed) sp.addWidget(dl) w = self.w = QWidget(self) l = w.l = QGridLayout() w.setLayout(l) self.view = WebView(self, self.prefs) self.view.elem_clicked.connect(self.elem_clicked) self.view.frag_shown.connect(self.update_dest_label, type=Qt.ConnectionType.QueuedConnection) self.view.loadFinished.connect(self.load_finished, type=Qt.ConnectionType.QueuedConnection) l.addWidget(self.view, 0, 0, 1, 3) sp.addWidget(w) self.search_text = s = QLineEdit(self) s.setPlaceholderText(_('Search for text...')) s.returnPressed.connect(self.find_next) l.addWidget(s, 1, 0) self.ns_button = b = QPushButton(QIcon(I('arrow-down.png')), _('Find &next'), self) b.clicked.connect(self.find_next) l.addWidget(b, 1, 1) self.ps_button = b = QPushButton(QIcon(I('arrow-up.png')), _('Find &previous'), self) l.addWidget(b, 1, 2) b.clicked.connect(self.find_previous) self.f = f = QFrame() f.setFrameShape(QFrame.Shape.StyledPanel) f.setMinimumWidth(250) l = f.l = QVBoxLayout() f.setLayout(l) sp.addWidget(f) f.la = la = QLabel('<p>' + _( 'Here you can choose a destination for the Table of Contents\' entry' ' to point to. First choose a file from the book in the left-most panel. The' ' file will open in the central panel.<p>' 'Then choose a location inside the file. To do so, simply click on' ' the place in the central panel that you want to use as the' ' destination. As you move the mouse around the central panel, a' ' thick green line appears, indicating the precise location' ' that will be selected when you click.')) la.setStyleSheet('QLabel { margin-bottom: 20px }') la.setWordWrap(True) l.addWidget(la) f.la2 = la = QLabel('<b>' + _('Na&me of the ToC entry:')) l.addWidget(la) self.name = QLineEdit(self) self.name.setPlaceholderText(_('(Untitled)')) la.setBuddy(self.name) l.addWidget(self.name) self.base_msg = '<b>' + _('Currently selected destination:') + '</b>' self.dest_label = la = QLabel(self.base_msg) la.setWordWrap(True) la.setStyleSheet('QLabel { margin-top: 20px }') l.addWidget(la) l.addStretch() state = self.prefs.get('toc_edit_splitter_state', None) if state is not None: sp.restoreState(state) def load_finished(self, ok): if self.pending_search: self.pending_search() self.pending_search = None def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter) and self.search_text.hasFocus(): # Prevent pressing enter in the search box from triggering the dialog's accept() method ev.accept() return return super().keyPressEvent(ev) def find(self, forwards=True): text = str(self.search_text.text()).strip() flags = QWebEnginePage.FindFlags( 0) if forwards else QWebEnginePage.FindFlag.FindBackward self.find_data = text, flags, forwards self.view.findText(text, flags, self.find_callback) def find_callback(self, found): d = self.dest_list text, flags, forwards = self.find_data if not found and text: if d.count() == 1: return error_dialog(self, _('No match found'), _('No match found for: %s') % text, show=True) delta = 1 if forwards else -1 current = str(d.currentItem().data(Qt.ItemDataRole.DisplayRole) or '') next_index = (d.currentRow() + delta) % d.count() next = str( d.item(next_index).data(Qt.ItemDataRole.DisplayRole) or '') msg = '<p>' + _( 'No matches for %(text)s found in the current file [%(current)s].' ' Do you want to search in the %(which)s file [%(next)s]?') msg = msg % dict(text=text, current=current, next=next, which=_('next') if forwards else _('previous')) if question_dialog(self, _('No match found'), msg): self.pending_search = self.find_next if forwards else self.find_previous d.setCurrentRow(next_index) def find_next(self): return self.find() def find_previous(self): return self.find(forwards=False) def load(self, container): self.container = container spine_names = [ container.abspath_to_name(p) for p in container.spine_items ] spine_names = [n for n in spine_names if container.has_name(n)] self.dest_list.addItems(spine_names) def current_changed(self, item): name = self.current_name = str( item.data(Qt.ItemDataRole.DisplayRole) or '') path = self.container.name_to_abspath(name) # Ensure encoding map is populated root = self.container.parsed(name) nasty = root.xpath('//*[local-name()="head"]/*[local-name()="p"]') if nasty: body = root.xpath('//*[local-name()="body"]') if not body: return error_dialog( self, _('Bad markup'), _('This book has severely broken markup, its ToC cannot be edited.' ), show=True) for x in reversed(nasty): body[0].insert(0, x) self.container.commit_item(name, keep_parsed=True) self.view.load_path(path, self.current_frag) self.current_frag = None self.dest_label.setText(self.base_msg + '<br>' + _('File:') + ' ' + name + '<br>' + _('Top of the file')) def __call__(self, item, where): self.current_item, self.current_where = item, where self.current_name = None self.current_frag = None self.name.setText('') dest_index, frag = 0, None if item is not None: if where is None: self.name.setText( item.data(0, Qt.ItemDataRole.DisplayRole) or '') self.name.setCursorPosition(0) toc = item.data(0, Qt.ItemDataRole.UserRole) if toc.dest: for i in range(self.dest_list.count()): litem = self.dest_list.item(i) if str(litem.data(Qt.ItemDataRole.DisplayRole) or '') == toc.dest: dest_index = i frag = toc.frag break self.dest_list.blockSignals(True) self.dest_list.setCurrentRow(dest_index) self.dest_list.blockSignals(False) item = self.dest_list.item(dest_index) if frag: self.current_frag = frag self.current_changed(item) def get_loctext(self, frac): frac = int(round(frac * 100)) if frac == 0: loctext = _('Top of the file') else: loctext = _('Approximately %d%% from the top') % frac return loctext def elem_clicked(self, tag, frac, elem_id, loc, totals): self.current_frag = elem_id or (loc, totals) base = _('Location: A <%s> tag inside the file') % tag loctext = base + ' [%s]' % self.get_loctext(frac) self.dest_label.setText(self.base_msg + '<br>' + _('File:') + ' ' + self.current_name + '<br>' + loctext) def update_dest_label(self, val): self.dest_label.setText(self.base_msg + '<br>' + _('File:') + ' ' + self.current_name + '<br>' + self.get_loctext(val)) @property def result(self): return (self.current_item, self.current_where, self.current_name, self.current_frag, self.name.text().strip() or _('(Untitled)'))
class FontFamilyDialog(QDialog): def __init__(self, current_family, parent=None): QDialog.__init__(self, parent) self.setWindowTitle(_('Choose font family')) self.setWindowIcon(QIcon(I('font.png'))) from calibre.utils.fonts.scanner import font_scanner self.font_scanner = font_scanner self.m = QStringListModel(self) self.build_font_list() self.l = l = QGridLayout() self.setLayout(l) self.view = FontsView(self) self.view.setModel(self.m) self.view.setCurrentIndex(self.m.index(0)) if current_family: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(current_family): self.view.setCurrentIndex(self.m.index(i)) break self.view.doubleClicked.connect( self.accept, type=Qt.ConnectionType.QueuedConnection) self.view.changed.connect(self.current_changed, type=Qt.ConnectionType.QueuedConnection) self.faces = Typefaces(self) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) self.add_fonts_button = afb = self.bb.addButton( _('Add &fonts'), QDialogButtonBox.ButtonRole.ActionRole) afb.setIcon(QIcon(I('plus.png'))) afb.clicked.connect(self.add_fonts) self.ml = QLabel(_('Choose a font family from the list below:')) self.search = QLineEdit(self) self.search.setPlaceholderText(_('Search')) self.search.returnPressed.connect(self.find) self.nb = QToolButton(self) self.nb.setIcon(QIcon(I('arrow-down.png'))) self.nb.setToolTip(_('Find next')) self.pb = QToolButton(self) self.pb.setIcon(QIcon(I('arrow-up.png'))) self.pb.setToolTip(_('Find previous')) self.nb.clicked.connect(self.find_next) self.pb.clicked.connect(self.find_previous) l.addWidget(self.ml, 0, 0, 1, 4) l.addWidget(self.search, 1, 0, 1, 1) l.addWidget(self.nb, 1, 1, 1, 1) l.addWidget(self.pb, 1, 2, 1, 1) l.addWidget(self.view, 2, 0, 1, 3) l.addWidget(self.faces, 1, 3, 2, 1) l.addWidget(self.bb, 3, 0, 1, 4) l.setAlignment(self.faces, Qt.AlignmentFlag.AlignTop) self.resize(800, 600) def set_current(self, i): self.view.setCurrentIndex(self.m.index(i)) def keyPressEvent(self, e): if e.key() == Qt.Key.Key_Return: return return QDialog.keyPressEvent(self, e) def find(self, backwards=False): i = self.view.currentIndex().row() if i < 0: i = 0 q = icu_lower(unicode_type(self.search.text())).strip() if not q: return r = (range(i - 1, -1, -1) if backwards else range(i + 1, len(self.families))) for j in r: f = self.families[j] if q in icu_lower(f): self.set_current(j) return def find_next(self): self.find() def find_previous(self): self.find(backwards=True) def build_font_list(self): try: self.families = list(self.font_scanner.find_font_families()) except: self.families = [] print('WARNING: Could not load fonts') import traceback traceback.print_exc() self.families.insert(0, _('None')) self.m.setStringList(self.families) def add_fonts(self): families = add_fonts(self) if not families: return self.font_scanner.do_scan() self.m.beginResetModel() self.build_font_list() self.m.endResetModel() self.view.setCurrentIndex(self.m.index(0)) if families: for i, val in enumerate(self.families): if icu_lower(val) == icu_lower(families[0]): self.view.setCurrentIndex(self.m.index(i)) break info_dialog(self, _('Added fonts'), _('Added font families: %s') % (', '.join(families)), show=True) @property def font_family(self): idx = self.view.currentIndex().row() if idx == 0: return None return self.families[idx] def current_changed(self): fam = self.font_family self.faces.show_family( fam, self.font_scanner.fonts_for_family(fam) if fam else None)