class CoverDelegate(QStyledItemDelegate): MARGIN = 4 @pyqtProperty(float) def animated_size(self): return self._animated_size @animated_size.setter def animated_size(self, val): self._animated_size = val def __init__(self, parent): super(CoverDelegate, self).__init__(parent) self._animated_size = 1.0 self.animation = QPropertyAnimation(self, 'animated_size', self) self.animation.setEasingCurve(QEasingCurve.OutInCirc) self.animation.setDuration(500) self.set_dimensions() self.cover_cache = CoverCache(limit=gprefs['cover_grid_cache_size']) self.render_queue = LifoQueue() self.animating = None self.highlight_color = QColor(Qt.white) def set_dimensions(self): width = self.original_width = gprefs['cover_grid_width'] height = self.original_height = gprefs['cover_grid_height'] self.original_show_title = show_title = gprefs['cover_grid_show_title'] if height < 0.1: height = auto_height(self.parent()) else: height *= self.parent().logicalDpiY() * CM_TO_INCH if width < 0.1: width = 0.75 * height else: width *= self.parent().logicalDpiX() * CM_TO_INCH self.cover_size = QSize(width, height) self.title_height = 0 if show_title: f = self.parent().font() sz = f.pixelSize() if sz < 5: sz = f.pointSize() * self.parent().logicalDpiY() / 72.0 self.title_height = max(25, sz + 10) self.item_size = self.cover_size + QSize( 2 * self.MARGIN, (2 * self.MARGIN) + self.title_height) self.calculate_spacing() self.animation.setStartValue(1.0) self.animation.setKeyValueAt(0.5, 0.5) self.animation.setEndValue(1.0) def calculate_spacing(self): spc = self.original_spacing = gprefs['cover_grid_spacing'] if spc < 0.01: self.spacing = max(10, min(50, int(0.1 * self.original_width))) else: self.spacing = self.parent().logicalDpiX() * CM_TO_INCH * spc def sizeHint(self, option, index): return self.item_size def render_field(self, db, book_id): try: field = db.pref('field_under_covers_in_grid', 'title') if field == 'size': ans = human_readable( db.field_for(field, book_id, default_value=0)) else: mi = db.get_proxy_metadata(book_id) display_name, ans, val, fm = mi.format_field_extended(field) if fm and fm['datatype'] == 'rating': ans = u'\u2605' * int(val / 2.0) if val is not None else '' return '' if ans is None else unicode(ans) except Exception: if DEBUG: import traceback traceback.print_exc() return '' def paint(self, painter, option, index): QStyledItemDelegate.paint( self, painter, option, QModelIndex()) # draw the hover and selection highlights m = index.model() db = m.db try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return if book_id in m.ids_to_highlight_set: painter.save() try: painter.setPen(self.highlight_color) painter.setRenderHint(QPainter.Antialiasing, True) painter.drawRoundedRect(option.rect, 10, 10, Qt.RelativeSize) finally: painter.restore() marked = db.data.get_marked(book_id) db = db.new_api cdata = self.cover_cache[book_id] device_connected = self.parent().gui.device_connected is not None on_device = device_connected and db.field_for('ondevice', book_id) painter.save() right_adjust = 0 try: rect = option.rect rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN) orect = QRect(rect) if cdata is None or cdata is False: title = db.field_for('title', book_id, default_value='') authors = ' & '.join( db.field_for('authors', book_id, default_value=())) painter.setRenderHint(QPainter.TextAntialiasing, True) painter.drawText(rect, Qt.AlignCenter | Qt.TextWordWrap, '%s\n\n%s' % (title, authors)) if cdata is False: self.render_queue.put(book_id) else: if self.title_height != 0: trect = QRect(rect) rect.setBottom(rect.bottom() - self.title_height) if self.animating is not None and self.animating.row( ) == index.row(): cdata = cdata.scaled(cdata.size() * self._animated_size) dx = max(0, int((rect.width() - cdata.width()) / 2.0)) dy = max(0, rect.height() - cdata.height()) right_adjust = dx rect.adjust(dx, dy, -dx, 0) painter.drawPixmap(rect, cdata) if self.title_height != 0: rect = trect rect.setTop(rect.bottom() - self.title_height + 5) painter.setRenderHint(QPainter.TextAntialiasing, True) title = self.render_field(db, book_id) metrics = painter.fontMetrics() painter.setPen(self.highlight_color) painter.drawText( rect, Qt.AlignCenter | Qt.TextSingleLine, metrics.elidedText(title, Qt.ElideRight, rect.width())) if marked: try: p = self.marked_emblem except AttributeError: p = self.marked_emblem = m.marked_icon.pixmap(48, 48) drect = QRect(orect) drect.setLeft(drect.left() + right_adjust) drect.setRight(drect.left() + p.width()) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - p.height()) painter.drawPixmap(drect, p) if on_device: try: p = self.on_device_emblem except AttributeError: p = self.on_device_emblem = QPixmap(I('ok.png')).scaled( 48, 48, transformMode=Qt.SmoothTransformation) drect = QRect(orect) drect.setRight(drect.right() - right_adjust) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - p.height() + 1) drect.setLeft(drect.right() - p.width() + 1) painter.drawPixmap(drect, p) finally: painter.restore() @pyqtSlot(QHelpEvent, QAbstractItemView, QStyleOptionViewItem, QModelIndex, result=bool) def helpEvent(self, event, view, option, index): if event is not None and view is not None and event.type( ) == QEvent.ToolTip: try: db = index.model().db except AttributeError: return False try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return False db = db.new_api device_connected = self.parent().gui.device_connected on_device = device_connected is not None and db.field_for( 'ondevice', book_id) p = prepare_string_for_xml title = db.field_for('title', book_id) authors = db.field_for('authors', book_id) if title and authors: title = '<b>%s</b>' % ('<br>'.join(wrap(p(title), 120))) authors = '<br>'.join(wrap(p(' & '.join(authors)), 120)) tt = '%s<br><br>%s' % (title, authors) series = db.field_for('series', book_id) if series: use_roman_numbers = config[ 'use_roman_numerals_for_series_number'] val = _( 'Book %(sidx)s of <span class="series_name">%(series)s</span>' ) % dict(sidx=fmt_sidx(db.field_for( 'series_index', book_id), use_roman=use_roman_numbers), series=p(series)) tt += '<br><br>' + val if on_device: val = _('This book is on the device in %s') % on_device tt += '<br><br>' + val QToolTip.showText(event.globalPos(), tt, view) return True return False
class CoverDelegate(QStyledItemDelegate): MARGIN = 4 @pyqtProperty(float) def animated_size(self): return self._animated_size @animated_size.setter def animated_size(self, val): self._animated_size = val def __init__(self, parent): super(CoverDelegate, self).__init__(parent) self._animated_size = 1.0 self.animation = QPropertyAnimation(self, 'animated_size', self) self.animation.setEasingCurve(QEasingCurve.OutInCirc) self.animation.setDuration(500) self.set_dimensions() self.cover_cache = CoverCache(limit=gprefs['cover_grid_cache_size']) self.render_queue = Queue() self.animating = None self.highlight_color = QColor(Qt.white) def set_dimensions(self): width = self.original_width = gprefs['cover_grid_width'] height = self.original_height = gprefs['cover_grid_height'] self.original_show_title = show_title = gprefs['cover_grid_show_title'] if height < 0.1: height = auto_height(self.parent()) else: height *= self.parent().logicalDpiY() * CM_TO_INCH if width < 0.1: width = 0.75 * height else: width *= self.parent().logicalDpiX() * CM_TO_INCH self.cover_size = QSize(width, height) self.title_height = 0 if show_title: f = self.parent().font() sz = f.pixelSize() if sz < 5: sz = f.pointSize() * self.parent().logicalDpiY() / 72.0 self.title_height = max(25, sz + 10) self.item_size = self.cover_size + QSize(2 * self.MARGIN, (2 * self.MARGIN) + self.title_height) self.calculate_spacing() self.animation.setStartValue(1.0) self.animation.setKeyValueAt(0.5, 0.5) self.animation.setEndValue(1.0) def calculate_spacing(self): spc = self.original_spacing = gprefs['cover_grid_spacing'] if spc < 0.01: self.spacing = max(10, min(50, int(0.1 * self.original_width))) else: self.spacing = self.parent().logicalDpiX() * CM_TO_INCH * spc def sizeHint(self, option, index): return self.item_size def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, QModelIndex()) # draw the hover and selection highlights m = index.model() db = m.db try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return if book_id in m.ids_to_highlight_set: painter.save() try: painter.setPen(self.highlight_color) painter.setRenderHint(QPainter.Antialiasing, True) painter.drawRoundedRect(option.rect, 10, 10, Qt.RelativeSize) finally: painter.restore() marked = db.data.get_marked(book_id) db = db.new_api cdata = self.cover_cache[book_id] device_connected = self.parent().gui.device_connected is not None on_device = device_connected and db.field_for('ondevice', book_id) painter.save() right_adjust = 0 try: rect = option.rect rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN) orect = QRect(rect) if cdata is None or cdata is False: title = db.field_for('title', book_id, default_value='') authors = ' & '.join(db.field_for('authors', book_id, default_value=())) painter.setRenderHint(QPainter.TextAntialiasing, True) painter.drawText(rect, Qt.AlignCenter|Qt.TextWordWrap, '%s\n\n%s' % (title, authors)) if cdata is False: self.render_queue.put(book_id) else: if self.title_height != 0: trect = QRect(rect) rect.setBottom(rect.bottom() - self.title_height) if self.animating is not None and self.animating.row() == index.row(): cdata = cdata.scaled(cdata.size() * self._animated_size) dx = max(0, int((rect.width() - cdata.width())/2.0)) dy = max(0, rect.height() - cdata.height()) right_adjust = dx rect.adjust(dx, dy, -dx, 0) painter.drawPixmap(rect, cdata) if self.title_height != 0: rect = trect rect.setTop(rect.bottom() - self.title_height + 5) painter.setRenderHint(QPainter.TextAntialiasing, True) title = db.field_for('title', book_id, default_value='') metrics = painter.fontMetrics() painter.drawText(rect, Qt.AlignCenter|Qt.TextSingleLine, metrics.elidedText(title, Qt.ElideRight, rect.width())) if marked: try: p = self.marked_emblem except AttributeError: p = self.marked_emblem = QPixmap(I('rating.png')).scaled(48, 48, transformMode=Qt.SmoothTransformation) drect = QRect(orect) drect.setLeft(drect.left() + right_adjust) drect.setRight(drect.left() + p.width()) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - p.height()) painter.drawPixmap(drect, p) if on_device: try: p = self.on_device_emblem except AttributeError: p = self.on_device_emblem = QPixmap(I('ok.png')).scaled(48, 48, transformMode=Qt.SmoothTransformation) drect = QRect(orect) drect.setRight(drect.right() - right_adjust) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - p.height() + 1) drect.setLeft(drect.right() - p.width() + 1) painter.drawPixmap(drect, p) finally: painter.restore() @pyqtSlot(QHelpEvent, QAbstractItemView, QStyleOptionViewItem, QModelIndex, result=bool) def helpEvent(self, event, view, option, index): if event is not None and view is not None and event.type() == QEvent.ToolTip: try: db = index.model().db except AttributeError: return False try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return False db = db.new_api device_connected = self.parent().gui.device_connected on_device = device_connected is not None and db.field_for('ondevice', book_id) p = prepare_string_for_xml title = db.field_for('title', book_id) authors = db.field_for('authors', book_id) if title and authors: title = '<b>%s</b>' % ('<br>'.join(wrap(p(title), 120))) authors = '<br>'.join(wrap(p(' & '.join(authors)), 120)) tt = '%s<br><br>%s' % (title, authors) series = db.field_for('series', book_id) if series: use_roman_numbers=config['use_roman_numerals_for_series_number'] val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( sidx=fmt_sidx(db.field_for('series_index', book_id), use_roman=use_roman_numbers), series=p(series)) tt += '<br><br>' + val if on_device: val = _('This book is on the device in %s') % on_device tt += '<br><br>' + val QToolTip.showText(event.globalPos(), tt, view) return True return False
class CoverDelegate(QStyledItemDelegate): MARGIN = 4 @pyqtProperty(float) def animated_size(self): return self._animated_size @animated_size.setter def animated_size(self, val): self._animated_size = val def __init__(self, parent): super(CoverDelegate, self).__init__(parent) self._animated_size = 1.0 self.animation = QPropertyAnimation(self, 'animated_size', self) self.animation.setEasingCurve(QEasingCurve.OutInCirc) self.animation.setDuration(500) self.set_dimensions() self.cover_cache = CoverCache(limit=gprefs['cover_grid_cache_size']) self.render_queue = Queue() self.animating = None self.highlight_color = QColor(Qt.white) def set_dimensions(self): width = self.original_width = gprefs['cover_grid_width'] height = self.original_height = gprefs['cover_grid_height'] self.original_show_title = show_title = gprefs['cover_grid_show_title'] if height < 0.1: height = max(185, QApplication.instance().desktop().availableGeometry(self.parent()).height() / 5.0) else: height *= self.parent().logicalDpiY() * CM_TO_INCH if width < 0.1: width = 0.75 * height else: width *= self.parent().logicalDpiX() * CM_TO_INCH self.cover_size = QSize(width, height) self.title_height = 0 if show_title: f = self.parent().font() sz = f.pixelSize() if sz < 5: sz = f.pointSize() * self.parent().logicalDpiY() / 72.0 self.title_height = max(25, sz + 10) self.item_size = self.cover_size + QSize(2 * self.MARGIN, (2 * self.MARGIN) + self.title_height) self.calculate_spacing() self.animation.setStartValue(1.0) self.animation.setKeyValueAt(0.5, 0.5) self.animation.setEndValue(1.0) def calculate_spacing(self): spc = self.original_spacing = gprefs['cover_grid_spacing'] if spc < 0.1: self.spacing = max(10, min(50, int(0.1 * self.original_width))) else: self.spacing = self.parent().logicalDpiX() * CM_TO_INCH * spc def sizeHint(self, option, index): return self.item_size def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, QModelIndex()) # draw the hover and selection highlights m = index.model() db = m.db try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return if book_id in m.ids_to_highlight_set: painter.save() try: painter.setPen(self.highlight_color) painter.setRenderHint(QPainter.Antialiasing, True) painter.drawRoundedRect(option.rect, 10, 10, Qt.RelativeSize) finally: painter.restore() db = db.new_api cdata = self.cover_cache[book_id] painter.save() try: rect = option.rect rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN) if cdata is None or cdata is False: title = db.field_for('title', book_id, default_value='') authors = ' & '.join(db.field_for('authors', book_id, default_value=())) painter.setRenderHint(QPainter.TextAntialiasing, True) painter.drawText(rect, Qt.AlignCenter|Qt.TextWordWrap, '%s\n\n%s' % (title, authors)) if cdata is False: self.render_queue.put(book_id) else: if self.title_height != 0: orect = QRect(rect) rect.setBottom(rect.bottom() - self.title_height) if self.animating is not None and self.animating.row() == index.row(): cdata = cdata.scaled(cdata.size() * self._animated_size) dx = max(0, int((rect.width() - cdata.width())/2.0)) dy = max(0, rect.height() - cdata.height()) rect.adjust(dx, dy, -dx, 0) painter.drawPixmap(rect, cdata) if self.title_height != 0: rect = orect rect.setTop(rect.bottom() - self.title_height + 5) painter.setRenderHint(QPainter.TextAntialiasing, True) title = db.field_for('title', book_id, default_value='') metrics = painter.fontMetrics() painter.drawText(rect, Qt.AlignCenter|Qt.TextSingleLine, metrics.elidedText(title, Qt.ElideRight, rect.width())) finally: painter.restore() @pyqtSlot(QHelpEvent, QAbstractItemView, QStyleOptionViewItem, QModelIndex, result=bool) def helpEvent(self, event, view, option, index): if event is not None and view is not None and event.type() == QEvent.ToolTip: try: db = index.model().db except AttributeError: return False try: book_id = db.id(index.row()) except (ValueError, IndexError, KeyError): return False title = db.new_api.field_for('title', book_id) authors = db.new_api.field_for('authors', book_id) if title and authors: title = '<b>%s</b>' % ('\n'.join(wrap(title, 100))) authors = '\n'.join(wrap(' & '.join(authors), 100)) QToolTip.showText(event.globalPos(), '%s<br><br>%s' % (title, authors), view) return True return False
class Pointer(QWidget): def __init__(self, gui): QWidget.__init__(self, gui) self.setObjectName('jobs_pointer') self.setVisible(False) self.resize(100, 80) self.animation = QPropertyAnimation(self, "geometry", self) self.animation.setDuration(750) self.animation.setLoopCount(2) self.animation.setEasingCurve(QEasingCurve.Linear) self.animation.finished.connect(self.hide) taily, heady = 0, 55 self.arrow_path = QPainterPath(QPointF(40, taily)) self.arrow_path.lineTo(40, heady) self.arrow_path.lineTo(20, heady) self.arrow_path.lineTo(50, self.height()) self.arrow_path.lineTo(80, heady) self.arrow_path.lineTo(60, heady) self.arrow_path.lineTo(60, taily) self.arrow_path.closeSubpath() c = self.palette().color(QPalette.Active, QPalette.WindowText) self.color = QColor(c) self.color.setAlpha(100) self.brush = QBrush(self.color, Qt.SolidPattern) # from PyQt4.Qt import QTimer # QTimer.singleShot(1000, self.start) @property def gui(self): return self.parent() def point_at(self, frac): return (self.path.pointAtPercent(frac).toPoint() - QPoint(self.rect().center().x(), self.height())) def rect_at(self, frac): return QRect(self.point_at(frac), self.size()) def abspos(self, widget): pos = widget.pos() parent = widget.parent() while parent is not self.gui: pos += parent.pos() parent = parent.parent() return pos def start(self): if config['disable_animations']: return self.setVisible(True) self.raise_() end = self.abspos(self.gui.jobs_button) end = QPointF(end.x() + self.gui.jobs_button.width() / 3.0, end.y() + 20) start = QPointF(end.x(), end.y() - 0.5 * self.height()) self.path = QPainterPath(QPointF(start)) self.path.lineTo(end) self.path.closeSubpath() self.animation.setStartValue(self.rect_at(0.0)) self.animation.setEndValue(self.rect_at(1.0)) self.animation.setDirection(self.animation.Backward) num_keys = 100 for i in xrange(1, num_keys): i /= num_keys self.animation.setKeyValueAt(i, self.rect_at(i)) self.animation.start() def paintEvent(self, ev): p = QPainter(self) p.setRenderHints(p.Antialiasing) p.setBrush(self.brush) p.setPen(Qt.NoPen) p.drawPath(self.arrow_path) p.end()
class Pointer(QWidget): def __init__(self, gui): QWidget.__init__(self, gui) self.setObjectName('jobs_pointer') self.setVisible(False) self.resize(100, 80) self.animation = QPropertyAnimation(self, "geometry", self) self.animation.setDuration(750) self.animation.setLoopCount(2) self.animation.setEasingCurve(QEasingCurve.Linear) self.animation.finished.connect(self.hide) taily, heady = 0, 55 self.arrow_path = QPainterPath(QPointF(40, taily)) self.arrow_path.lineTo(40, heady) self.arrow_path.lineTo(20, heady) self.arrow_path.lineTo(50, self.height()) self.arrow_path.lineTo(80, heady) self.arrow_path.lineTo(60, heady) self.arrow_path.lineTo(60, taily) self.arrow_path.closeSubpath() c = self.palette().color(QPalette.Active, QPalette.WindowText) self.color = QColor(c) self.color.setAlpha(100) self.brush = QBrush(self.color, Qt.SolidPattern) # from PyQt4.Qt import QTimer # QTimer.singleShot(1000, self.start) @property def gui(self): return self.parent() def point_at(self, frac): return (self.path.pointAtPercent(frac).toPoint() - QPoint(self.rect().center().x(), self.height())) def rect_at(self, frac): return QRect(self.point_at(frac), self.size()) def abspos(self, widget): pos = widget.pos() parent = widget.parent() while parent is not self.gui: pos += parent.pos() parent = parent.parent() return pos def start(self): if config['disable_animations']: return self.setVisible(True) self.raise_() end = self.abspos(self.gui.jobs_button) end = QPointF( end.x() + self.gui.jobs_button.width()/3.0, end.y()+20) start = QPointF(end.x(), end.y() - 0.5*self.height()) self.path = QPainterPath(QPointF(start)) self.path.lineTo(end) self.path.closeSubpath() self.animation.setStartValue(self.rect_at(0.0)) self.animation.setEndValue(self.rect_at(1.0)) self.animation.setDirection(self.animation.Backward) num_keys = 100 for i in xrange(1, num_keys): i /= num_keys self.animation.setKeyValueAt(i, self.rect_at(i)) self.animation.start() def paintEvent(self, ev): p = QPainter(self) p.setRenderHints(p.Antialiasing) p.setBrush(self.brush) p.setPen(Qt.NoPen) p.drawPath(self.arrow_path) p.end()