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)
class RichTextDelegate(QStyledItemDelegate): # {{{ def __init__(self, parent=None, max_width=160): QStyledItemDelegate.__init__(self, parent) self.max_width = max_width self.dummy_model = QStringListModel([' '], self) self.dummy_index = self.dummy_model.index(0) def to_doc(self, index, option=None): doc = QTextDocument() if option is not None and option.state & QStyle.StateFlag.State_Selected: p = option.palette group = (QPalette.ColorGroup.Active if option.state & QStyle.StateFlag.State_Active else QPalette.ColorGroup.Inactive) c = p.color(group, QPalette.ColorRole.HighlightedText) c = 'rgb(%d, %d, %d)' % c.getRgb()[:3] doc.setDefaultStyleSheet(' * { color: %s }' % c) doc.setHtml(index.data() or '') return doc def sizeHint(self, option, index): doc = self.to_doc(index, option=option) ans = doc.size().toSize() if ans.width() > self.max_width - 10: ans.setWidth(self.max_width) ans.setHeight(ans.height() + 10) return ans def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, self.dummy_index) painter.save() painter.setClipRect(QRectF(option.rect)) painter.translate(option.rect.topLeft()) self.to_doc(index, option).drawContents(painter) painter.restore()
def update(self, text_items, completion_prefix): items = list(self.all_items.difference(text_items)) model = QStringListModel(items, self) self.setModel(model) self.setCompletionPrefix(completion_prefix) if completion_prefix.strip(): self.complete()
def __init__(self, names, txt, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout(self) self.setLayout(l) self.la = la = QLabel(_('Create a Virtual library based on %s') % txt) l.addWidget(la) self.filter = f = QLineEdit(self) f.setPlaceholderText(_('Filter {}').format(txt)) f.setClearButtonEnabled(True) l.addWidget(f) self.model = QStringListModel(sorted(names, key=sort_key)) self.pmodel = QSortFilterProxyModel(self) self.pmodel.setFilterCaseSensitivity( Qt.CaseSensitivity.CaseInsensitive) f.textChanged.connect(self.pmodel.setFilterFixedString) self.pmodel.setSourceModel(self.model) self._names = QListView(self) self._names.setModel(self.pmodel) self._names.setSelectionMode( QAbstractItemView.SelectionMode.MultiSelection) l.addWidget(self._names) self._or = QRadioButton(_('Match any of the selected %s') % txt) self._and = QRadioButton(_('Match all of the selected %s') % txt) self._or.setChecked(True) l.addWidget(self._or) l.addWidget(self._and) self.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.bb.accepted.connect(self.accept) self.bb.rejected.connect(self.reject) l.addWidget(self.bb) self.resize(self.sizeHint())
def update_items_cache(self, items): self.all_items = set(items) model = QStringListModel(items, self) self.setModel(model)
def __init__(self, window, db, id_=None, key=None, current_tags=None): QDialog.__init__(self, window) Ui_TagEditor.__init__(self) self.setupUi(self) self.db = db self.sep = ',' self.is_names = False if key: # Assume that if given a key then it is a custom column try: fm = db.field_metadata[key] self.is_names = fm['display'].get('is_names', False) if self.is_names: self.sep = '&' self.setWindowTitle(self.windowTitle() + ': ' + fm['name']) except Exception: pass key = db.field_metadata.key_to_label(key) else: self.setWindowTitle(self.windowTitle() + ': ' + db.field_metadata['tags']['name']) if self.sep == '&': self.add_tag_input.setToolTip( '<p>' + _('If the item you want is not in the available list, ' 'you can add it here. Accepts an ampersand-separated ' 'list of items. The items will be applied to ' 'the book.') + '</p>') else: self.add_tag_input.setToolTip( '<p>' + _('If the item you want is not in the available list, ' 'you can add it here. Accepts a comma-separated ' 'list of items. The items will be applied to ' 'the book.') + '</p>') self.key = key self.index = db.row(id_) if id_ is not None else None if self.index is not None: if key is None: tags = self.db.tags(self.index) if tags: tags = [ tag.strip() for tag in tags.split(',') if tag.strip() ] else: tags = self.db.get_custom(self.index, label=key) else: tags = [] if current_tags is not None: tags = sorted(set(current_tags), key=sort_key) if tags: if not self.is_names: tags.sort(key=sort_key) else: tags = [] self.applied_model = QStringListModel(tags) p = QSortFilterProxyModel() p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) p.setSourceModel(self.applied_model) self.applied_tags.setModel(p) if self.is_names: self.applied_tags.setDragDropMode( QAbstractItemView.DragDropMode.InternalMove) self.applied_tags.setSelectionMode( QAbstractItemView.SelectionMode.ExtendedSelection) if key: all_tags = [tag for tag in self.db.all_custom(label=key)] else: all_tags = [tag for tag in self.db.all_tags()] all_tags = sorted(set(all_tags) - set(tags), key=sort_key) self.all_tags_model = QStringListModel(all_tags) p = QSortFilterProxyModel() p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) p.setSourceModel(self.all_tags_model) self.available_tags.setModel(p) connect_lambda(self.apply_button.clicked, self, lambda self: self.apply_tags()) connect_lambda(self.unapply_button.clicked, self, lambda self: self.unapply_tags()) self.add_tag_button.clicked.connect(self.add_tag) connect_lambda(self.delete_button.clicked, self, lambda self: self.delete_tags()) self.add_tag_input.returnPressed[()].connect(self.add_tag) # add the handlers for the filter input fields connect_lambda(self.available_filter_input.textChanged, self, lambda self, text: self.filter_tags(text)) connect_lambda( self.applied_filter_input.textChanged, self, lambda self, text: self.filter_tags(text, which='applied_tags')) # Restore the focus to the last input box used (typed into) for x in ('add_tag_input', 'available_filter_input', 'applied_filter_input'): ibox = getattr(self, x) ibox.setObjectName(x) connect_lambda( ibox.textChanged, self, lambda self: self.edit_box_changed(self.sender().objectName())) getattr(self, gprefs.get('tag_editor_last_filter', 'add_tag_input')).setFocus() self.available_tags.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers) self.applied_tags.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers) if islinux: self.available_tags.doubleClicked.connect(self.apply_tags) self.applied_tags.doubleClicked.connect(self.unapply_tags) else: self.available_tags.activated.connect(self.apply_tags) self.applied_tags.activated.connect(self.unapply_tags) geom = gprefs.get('tag_editor_geometry', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, geom)
class TagEditor(QDialog, Ui_TagEditor): def __init__(self, window, db, id_=None, key=None, current_tags=None): QDialog.__init__(self, window) Ui_TagEditor.__init__(self) self.setupUi(self) self.db = db self.sep = ',' self.is_names = False if key: # Assume that if given a key then it is a custom column try: fm = db.field_metadata[key] self.is_names = fm['display'].get('is_names', False) if self.is_names: self.sep = '&' self.setWindowTitle(self.windowTitle() + ': ' + fm['name']) except Exception: pass key = db.field_metadata.key_to_label(key) else: self.setWindowTitle(self.windowTitle() + ': ' + db.field_metadata['tags']['name']) if self.sep == '&': self.add_tag_input.setToolTip( '<p>' + _('If the item you want is not in the available list, ' 'you can add it here. Accepts an ampersand-separated ' 'list of items. The items will be applied to ' 'the book.') + '</p>') else: self.add_tag_input.setToolTip( '<p>' + _('If the item you want is not in the available list, ' 'you can add it here. Accepts a comma-separated ' 'list of items. The items will be applied to ' 'the book.') + '</p>') self.key = key self.index = db.row(id_) if id_ is not None else None if self.index is not None: if key is None: tags = self.db.tags(self.index) if tags: tags = [ tag.strip() for tag in tags.split(',') if tag.strip() ] else: tags = self.db.get_custom(self.index, label=key) else: tags = [] if current_tags is not None: tags = sorted(set(current_tags), key=sort_key) if tags: if not self.is_names: tags.sort(key=sort_key) else: tags = [] self.applied_model = QStringListModel(tags) p = QSortFilterProxyModel() p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) p.setSourceModel(self.applied_model) self.applied_tags.setModel(p) if self.is_names: self.applied_tags.setDragDropMode( QAbstractItemView.DragDropMode.InternalMove) self.applied_tags.setSelectionMode( QAbstractItemView.SelectionMode.ExtendedSelection) if key: all_tags = [tag for tag in self.db.all_custom(label=key)] else: all_tags = [tag for tag in self.db.all_tags()] all_tags = sorted(set(all_tags) - set(tags), key=sort_key) self.all_tags_model = QStringListModel(all_tags) p = QSortFilterProxyModel() p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) p.setSourceModel(self.all_tags_model) self.available_tags.setModel(p) connect_lambda(self.apply_button.clicked, self, lambda self: self.apply_tags()) connect_lambda(self.unapply_button.clicked, self, lambda self: self.unapply_tags()) self.add_tag_button.clicked.connect(self.add_tag) connect_lambda(self.delete_button.clicked, self, lambda self: self.delete_tags()) self.add_tag_input.returnPressed[()].connect(self.add_tag) # add the handlers for the filter input fields connect_lambda(self.available_filter_input.textChanged, self, lambda self, text: self.filter_tags(text)) connect_lambda( self.applied_filter_input.textChanged, self, lambda self, text: self.filter_tags(text, which='applied_tags')) # Restore the focus to the last input box used (typed into) for x in ('add_tag_input', 'available_filter_input', 'applied_filter_input'): ibox = getattr(self, x) ibox.setObjectName(x) connect_lambda( ibox.textChanged, self, lambda self: self.edit_box_changed(self.sender().objectName())) getattr(self, gprefs.get('tag_editor_last_filter', 'add_tag_input')).setFocus() self.available_tags.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers) self.applied_tags.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers) if islinux: self.available_tags.doubleClicked.connect(self.apply_tags) self.applied_tags.doubleClicked.connect(self.unapply_tags) else: self.available_tags.activated.connect(self.apply_tags) self.applied_tags.activated.connect(self.unapply_tags) geom = gprefs.get('tag_editor_geometry', None) if geom is not None: QApplication.instance().safe_restore_geometry(self, geom) def edit_box_changed(self, which): gprefs['tag_editor_last_filter'] = which def delete_tags(self): confirms, deletes = [], [] row_indices = list(self.available_tags.selectionModel().selectedRows()) if not row_indices: error_dialog( self, 'No tags selected', 'You must select at least one tag from the list of Available tags.' ).exec() return if not confirm( _('Deleting tags is done immediately and there is no undo.'), 'tag_editor_delete'): return pos = self.available_tags.verticalScrollBar().value() for ri in row_indices: tag = ri.data() used = self.db.is_tag_used(tag) \ if self.key is None else \ self.db.is_item_used_in_multiple(tag, label=self.key) if used: confirms.append(ri) else: deletes.append(ri) if confirms: ct = ', '.join(item.data() for item in confirms) if question_dialog( self, _('Are your sure?'), '<p>' + _('The following tags are used by one or more books. ' 'Are you certain you want to delete them?') + '<br>' + ct): deletes += confirms for item in sorted(deletes, key=lambda r: r.row(), reverse=True): tag = item.data() if self.key is None: self.db.delete_tag(tag) else: bks = self.db.delete_item_from_multiple(tag, label=self.key) self.db.refresh_ids(bks) self.available_tags.model().removeRows(item.row(), 1) self.available_tags.verticalScrollBar().setValue(pos) def apply_tags(self, item=None): row_indices = list(self.available_tags.selectionModel().selectedRows()) row_indices.sort(key=lambda r: r.row(), reverse=True) if not row_indices: text = self.available_filter_input.text() if text and text.strip(): self.add_tag_input.setText(text) self.add_tag_input.setFocus(Qt.FocusReason.OtherFocusReason) return pos = self.available_tags.verticalScrollBar().value() tags = self._get_applied_tags_box_contents() for item in row_indices: tag = item.data() tags.append(tag) self.available_tags.model().removeRows(item.row(), 1) self.available_tags.verticalScrollBar().setValue(pos) if not self.is_names: tags.sort(key=sort_key) self.applied_model.setStringList(tags) def _get_applied_tags_box_contents(self): return list(self.applied_model.stringList()) def unapply_tags(self, item=None): row_indices = list(self.applied_tags.selectionModel().selectedRows()) tags = [r.data() for r in row_indices] row_indices.sort(key=lambda r: r.row(), reverse=True) for item in row_indices: self.applied_model.removeRows(item.row(), 1) all_tags = self.all_tags_model.stringList() + tags all_tags.sort(key=sort_key) self.all_tags_model.setStringList(all_tags) def add_tag(self): tags = str(self.add_tag_input.text()).split(self.sep) tags_in_box = self._get_applied_tags_box_contents() for tag in tags: tag = tag.strip() if not tag: continue if self.all_tags_model.rowCount(): for index in self.all_tags_model.match( self.all_tags_model.index(0), Qt.ItemDataRole.DisplayRole, tag, -1, Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchCaseSensitive | Qt.MatchFlag.MatchWrap): self.all_tags_model.removeRow(index.row()) if tag not in tags_in_box: tags_in_box.append(tag) if not self.is_names: tags_in_box.sort(key=sort_key) self.applied_model.setStringList(tags_in_box) self.add_tag_input.setText('') def filter_tags(self, filter_value, which='available_tags'): collection = getattr(self, which) collection.model().setFilterFixedString(filter_value or '') def accept(self): self.tags = self._get_applied_tags_box_contents() self.save_state() return QDialog.accept(self) def reject(self): self.save_state() return QDialog.reject(self) def save_state(self): gprefs['tag_editor_geometry'] = bytearray(self.saveGeometry())
def data(self, index, role): if role == Qt.ItemDataRole.DecorationRole: w = self.widgets[index.row()] if w.ICON: return (QIcon(w.ICON)) return QStringListModel.data(self, index, role)
def __init__(self, widgets): QStringListModel.__init__(self) self.widgets = widgets self.setStringList([w.TITLE for w in widgets])
def __init__(self, parent=None, max_width=160): QStyledItemDelegate.__init__(self, parent) self.max_width = max_width self.dummy_model = QStringListModel([' '], self) self.dummy_index = self.dummy_model.index(0)
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)