def font(self, text_style): device_font = text_style.fontfacename in LIBERATION_FONT_MAP try: if device_font: face = self.font_map[text_style.fontfacename] else: face = self.face_map[text_style.fontfacename] except KeyError: # Bad fontfacename field in LRF face = self.font_map['Dutch801 Rm BT Roman'] sz = text_style.fontsize wt = text_style.fontweight style = text_style.fontstyle font = ( face, wt, style, sz, ) if font in self.cache: rfont = self.cache[font] else: italic = font[2] == QFont.Style.StyleItalic rfont = QFont(font[0], font[3], font[1], italic) rfont.setPixelSize(font[3]) rfont.setBold(wt >= 69) self.cache[font] = rfont qfont = rfont if text_style.emplinetype != 'none': qfont = QFont(rfont) qfont.setOverline(text_style.emplineposition == 'before') qfont.setUnderline(text_style.emplineposition == 'after') return qfont
class NewsCategory(NewsTreeItem): def __init__(self, category, builtin, custom, scheduler_config, parent): NewsTreeItem.__init__(self, builtin, custom, scheduler_config, parent) self.category = category self.cdata = get_language(self.category) if self.category == _('Scheduled'): self.sortq = 0, '' elif self.category == _('Custom'): self.sortq = 1, '' else: self.sortq = 2, self.cdata self.bold_font = QFont() self.bold_font.setBold(True) self.bold_font = (self.bold_font) def data(self, role): if role == Qt.ItemDataRole.DisplayRole: return (self.cdata + ' [%d]'%len(self.children)) elif role == Qt.ItemDataRole.FontRole: return self.bold_font elif role == Qt.ItemDataRole.ForegroundRole and self.category == _('Scheduled'): return QApplication.instance().palette().color(QPalette.ColorRole.Link) elif role == Qt.ItemDataRole.UserRole: return f'::category::{self.sortq[0]}' return None def flags(self): return Qt.ItemFlag.ItemIsEnabled def __eq__(self, other): return self.cdata == other.cdata def __lt__(self, other): return self.sortq < getattr(other, 'sortq', (3, ''))
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(): if current == num: painter.save() painter.setPen(self.line_number_palette.color(QPalette.ColorRole.BrightText)) f = QFont(self.font()) f.setBold(True) painter.setFont(f) self.last_current_lnum = (top, bottom - top) painter.drawText(0, top, self.line_number_area.width() - 5, self.fontMetrics().height(), Qt.AlignmentFlag.AlignRight, unicode_type(num + 1)) if current == num: painter.restore() block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) num += 1
def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) result = index.data(Qt.ItemDataRole.UserRole) is_hidden, result_before, result_text, result_after, show_leading_dot = self.result_data( result) if result_text is None: return painter.save() try: p = option.palette c = QPalette.ColorRole.HighlightedText if option.state & QStyle.StateFlag.State_Selected else QPalette.ColorRole.Text group = (QPalette.ColorGroup.Active if option.state & QStyle.StateFlag.State_Active else QPalette.ColorGroup.Inactive) c = p.color(group, c) painter.setPen(c) font = option.font if self.emphasize_text: emphasis_font = QFont(font) emphasis_font.setBold(True) else: emphasis_font = font flags = Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextSingleLine | Qt.TextFlag.TextIncludeTrailingSpaces rect = option.rect.adjusted( option.decorationSize.width() + 4 if is_hidden else 0, 0, 0, 0) painter.setClipRect(rect) before = re.sub(r'\s+', ' ', result_before) if show_leading_dot: before = '•' + before before_width = 0 if before: before_width = painter.boundingRect(rect, flags, before).width() after = re.sub(r'\s+', ' ', result_after.rstrip()) after_width = 0 if after: after_width = painter.boundingRect(rect, flags, after).width() ellipsis_width = painter.boundingRect(rect, flags, '...').width() painter.setFont(emphasis_font) text = re.sub(r'\s+', ' ', result_text) match_width = painter.boundingRect(rect, flags, text).width() if match_width >= rect.width() - 3 * ellipsis_width: efm = QFontMetrics(emphasis_font) if show_leading_dot: text = '•' + text text = efm.elidedText(text, Qt.TextElideMode.ElideRight, rect.width()) painter.drawText(rect, flags, text) else: self.draw_match(painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, font) except Exception: import traceback traceback.print_exc() painter.restore()
def capture_clicked(self, which=1): self.capture = which for w in 1, 2: self.clear_button(w) button = getattr(self, 'button%d' % which) button.setText(_('Press a key...')) button.setFocus(Qt.FocusReason.OtherFocusReason) font = QFont() font.setBold(True) button.setFont(font)
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 __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}
def data(self, index, role): try: widget = self.widgets[index.row()] except: return None if role == Qt.ItemDataRole.DisplayRole: return (widget.config_title()) if role == Qt.ItemDataRole.DecorationRole: return (widget.config_icon()) if role == Qt.ItemDataRole.FontRole: f = QFont() f.setBold(True) return (f) return None
def generate_masthead(title, output_path=None, width=600, height=60, as_qimage=False, font_family=None): init_environment() font_family = font_family or cprefs[ 'title_font_family'] or 'Liberation Serif' img = QImage(width, height, QImage.Format.Format_ARGB32) img.fill(Qt.GlobalColor.white) p = QPainter(img) p.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.TextAntialiasing) f = QFont(font_family) f.setStyleStrategy(QFont.StyleStrategy.PreferAntialias) f.setPixelSize((height * 3) // 4), f.setBold(True) p.setFont(f) p.drawText(img.rect(), Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, sanitize(title)) p.end() if as_qimage: return img data = pixmap_to_data(img) if output_path is None: return data with open(output_path, 'wb') as f: f.write(data)
class Category(QWidget): # {{{ plugin_activated = pyqtSignal(object) def __init__(self, name, plugins, gui_name, parent=None): QWidget.__init__(self, parent) self._layout = QVBoxLayout() self.setLayout(self._layout) self.label = QLabel(gui_name) self.sep = QFrame(self) self.bf = QFont() self.bf.setBold(True) self.label.setFont(self.bf) self.sep.setFrameShape(QFrame.Shape.HLine) self._layout.addWidget(self.label) self._layout.addWidget(self.sep) self.plugins = plugins self.bar = QToolBar(self) self.bar.setStyleSheet('QToolBar { border: none; background: none }') lh = QApplication.instance().line_height self.bar.setIconSize(QSize(2 * lh, 2 * lh)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon) self._layout.addWidget(self.bar) self.actions = [] for p in plugins: target = partial(self.triggered, p) ac = self.bar.addAction(QIcon(p.icon), p.gui_name.replace('&', '&&'), target) ac.setToolTip(textwrap.fill(p.description)) ac.setWhatsThis(textwrap.fill(p.description)) ac.setStatusTip(p.description) self.actions.append(ac) w = self.bar.widgetForAction(ac) w.setCursor(Qt.CursorShape.PointingHandCursor) if hasattr(w, 'setAutoRaise'): w.setAutoRaise(True) w.setMinimumWidth(100) def triggered(self, plugin, *args): self.plugin_activated.emit(plugin)
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, str(num + 1)) painter.restore() block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) num += 1
def data(self, index, role): row = index.row() try: tweak = self.tweaks[row] except: return None if role == Qt.ItemDataRole.DisplayRole: return textwrap.fill(tweak.name, 40) if role == Qt.ItemDataRole.FontRole and tweak.is_customized: ans = QFont() ans.setBold(True) return ans if role == Qt.ItemDataRole.ToolTipRole: tt = _('This tweak has its default value') if tweak.is_customized: tt = '<p>' + _('This tweak has been customized') tt += '<pre>' for varn, val in iteritems(tweak.custom_values): tt += '%s = %r\n\n' % (varn, val) return textwrap.fill(tt) if role == Qt.ItemDataRole.UserRole: return tweak return None
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()) painter.setPen(self.line_number_palette.color(QPalette.ColorRole.Text)) change_starts = {x[0] for x in self.changes} while block.isValid() and top <= ev.rect().bottom(): r = ev.rect() if block.isVisible() and bottom >= r.top(): text = unicode_type(self.line_number_map.get(num, '')) is_start = text != '-' and num in change_starts if is_start: painter.save() f = QFont(self.font()) f.setBold(True) painter.setFont(f) painter.setPen(self.line_number_palette.color(QPalette.ColorRole.BrightText)) if text == '-': painter.drawLine(r.left() + 2, (top + bottom)//2, r.right() - 2, (top + bottom)//2) else: if self.right: painter.drawText(r.left() + 3, top, r.right(), self.fontMetrics().height(), Qt.AlignmentFlag.AlignLeft, text) else: painter.drawText(r.left() + 2, top, r.right() - 5, self.fontMetrics().height(), Qt.AlignmentFlag.AlignRight, text) if is_start: painter.restore() block = block.next() top = bottom bottom = top + int(self.blockBoundingRect(block).height()) num += 1
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 EmailAccounts(QAbstractTableModel): # {{{ def __init__(self, accounts, subjects, aliases={}, tags={}): QAbstractTableModel.__init__(self) self.accounts = accounts self.subjects = subjects self.aliases = aliases self.tags = tags self.sorted_on = (0, True) self.account_order = list(self.accounts) self.do_sort() self.headers = [ _('Email'), _('Formats'), _('Subject'), _('Auto send'), _('Alias'), _('Auto send only tags') ] self.default_font = QFont() self.default_font.setBold(True) self.default_font = (self.default_font) self.tooltips = [None] + list( map(textwrap.fill, [ _('Formats to email. The first matching format will be sent.'), _('Subject of the email to use when sending. When left blank ' 'the title will be used for the subject. Also, the same ' 'templates used for "Save to disk" such as {title} and ' '{author_sort} can be used here.'), '<p>' + _('If checked, downloaded news will be automatically ' 'mailed to this email address ' '(provided it is in one of the listed formats and has not been filtered by tags).' ), _('Friendly name to use for this email address'), _('If specified, only news with one of these tags will be sent to' ' this email address. All news downloads have their title as a' ' tag, so you can use this to easily control which news downloads' ' are sent to this email address.') ])) def do_sort(self): col = self.sorted_on[0] if col == 0: def key(account_key): return numeric_sort_key(account_key) elif col == 1: def key(account_key): return numeric_sort_key(self.accounts[account_key][0] or '') elif col == 2: def key(account_key): return numeric_sort_key(self.subjects.get(account_key) or '') elif col == 3: def key(account_key): return numeric_sort_key( as_unicode(self.accounts[account_key][0]) or '') elif col == 4: def key(account_key): return numeric_sort_key(self.aliases.get(account_key) or '') elif col == 5: def key(account_key): return numeric_sort_key(self.tags.get(account_key) or '') self.account_order.sort(key=key, reverse=not self.sorted_on[1]) def sort(self, column, order=Qt.SortOrder.AscendingOrder): nsort = (column, order == Qt.SortOrder.AscendingOrder) if nsort != self.sorted_on: self.sorted_on = nsort self.beginResetModel() try: self.do_sort() finally: self.endResetModel() def rowCount(self, *args): return len(self.account_order) def columnCount(self, *args): return len(self.headers) def headerData(self, section, orientation, role): if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: return self.headers[section] return None def data(self, index, role): row, col = index.row(), index.column() if row < 0 or row >= self.rowCount(): return None account = self.account_order[row] if account not in self.accounts: return None if role == Qt.ItemDataRole.UserRole: return (account, self.accounts[account]) if role == Qt.ItemDataRole.ToolTipRole: return self.tooltips[col] if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]: if col == 0: return (account) if col == 1: return ', '.join( x.strip() for x in (self.accounts[account][0] or '').split(',')) if col == 2: return (self.subjects.get(account, '')) if col == 4: return (self.aliases.get(account, '')) if col == 5: return (self.tags.get(account, '')) if role == Qt.ItemDataRole.FontRole and self.accounts[account][2]: return self.default_font if role == Qt.ItemDataRole.CheckStateRole and col == 3: return (Qt.CheckState.Checked if self.accounts[account][1] else Qt.CheckState.Unchecked) return None def flags(self, index): if index.column() == 3: return QAbstractTableModel.flags( self, index) | Qt.ItemFlag.ItemIsUserCheckable else: return QAbstractTableModel.flags( self, index) | Qt.ItemFlag.ItemIsEditable def setData(self, index, value, role): if not index.isValid(): return False row, col = index.row(), index.column() account = self.account_order[row] if col == 3: self.accounts[account][1] ^= True elif col == 2: self.subjects[account] = as_unicode(value or '') elif col == 4: self.aliases.pop(account, None) aval = as_unicode(value or '').strip() if aval: self.aliases[account] = aval elif col == 5: self.tags.pop(account, None) aval = as_unicode(value or '').strip() if aval: self.tags[account] = aval elif col == 1: self.accounts[account][0] = re.sub( ',+', ',', re.sub(r'\s+', ',', as_unicode(value or '').upper())) elif col == 0: na = as_unicode(value or '') from email.utils import parseaddr addr = parseaddr(na)[-1] if not addr or '@' not in na: return False self.accounts[na] = self.accounts.pop(account) self.account_order[row] = na if '@kindle.com' in addr: self.accounts[na][0] = 'AZW, MOBI, TPZ, PRC, AZW1' self.dataChanged.emit(self.index(index.row(), 0), self.index(index.row(), 3)) return True def make_default(self, index): if index.isValid(): self.beginResetModel() row = index.row() for x in self.accounts.values(): x[2] = False self.accounts[self.account_order[row]][2] = True self.endResetModel() def add(self): x = _('new email address') y = x c = 0 while y in self.accounts: c += 1 y = x + str(c) auto_send = len(self.accounts) < 1 self.beginResetModel() self.accounts[y] = [ 'MOBI, EPUB', auto_send, len(self.account_order) == 0 ] self.account_order = list(self.accounts) self.do_sort() self.endResetModel() return self.index(self.account_order.index(y), 0) def remove_rows(self, *rows): for row in sorted(rows, reverse=True): try: account = self.account_order[row] except Exception: continue self.accounts.pop(account) self.account_order = sorted(self.accounts) has_default = False for account in self.account_order: if self.accounts[account][2]: has_default = True break if not has_default and self.account_order: self.accounts[self.account_order[0]][2] = True self.beginResetModel() self.endResetModel() self.do_sort() def remove(self, index): if index.isValid(): self.remove(index.row())