def pressedAlarmButton(self): try: alarmButtonCurrentGeometry = QtCore.QRect(self.alarmToolButton.geometry()) alarmButtonAnimation = QPropertyAnimation(self.alarmToolButton,'geometry'.encode(encoding='utf_8')) alarmButtonAnimation.setDuration(self.ANIMATIONDURATION) alarmButtonAnimation.setStartValue(alarmButtonCurrentGeometry) alarmWidgetCurrentGeometry = QtCore.QRect(self.alarmWidget.geometry()) alarmWidgetAnimation = QPropertyAnimation(self.alarmWidget,'geometry'.encode(encoding='utf_8')) alarmWidgetAnimation.setDuration(self.ANIMATIONDURATION) alarmWidgetAnimation.setStartValue(alarmWidgetCurrentGeometry) if self.AlarmCurrentState == 'Closed': alarmButtonEndTopLeftCorner = QtCore.QPoint(self.alarmToolButton.pos() - QtCore.QPoint(275, 0)) alarmWidgetEndTopLeftCorner = QtCore.QPoint(self.alarmWidget.pos() - QtCore.QPoint(275, 0)) self.AlarmCurrentState = 'Open' else: alarmButtonEndTopLeftCorner = QtCore.QPoint(self.alarmToolButton.pos() + QtCore.QPoint(275, 0)) alarmWidgetEndTopLeftCorner = QtCore.QPoint(self.alarmWidget.pos() + QtCore.QPoint(275, 0)) self.AlarmCurrentState = 'Closed' alarmButtonFinalGeometry = QtCore.QRect(alarmButtonEndTopLeftCorner, QtCore.QSize(self.alarmToolButton.width(), self.alarmToolButton.height())) alarmButtonAnimation.setEndValue(alarmButtonFinalGeometry) alarmWidgetFinalGeometry = QtCore.QRect(alarmWidgetEndTopLeftCorner, QtCore.QSize(self.alarmWidget.width(), self.alarmWidget.height())) alarmWidgetAnimation.setEndValue(alarmWidgetFinalGeometry) alarmButtonAnimation.start() alarmWidgetAnimation.start() #self.setAlarmWidgetStyleSheet() self.AlarmIconAnimation = alarmButtonAnimation self.AlarmWidgetAnimation = alarmWidgetAnimation except Exception as e: print (e.strerror)
def pressedPandoraButton(self): pandoraButtonCurrentGeometry = QtCore.QRect(self.pandoraToolButton.geometry()) pandoraButtonAnimation = QPropertyAnimation(self.pandoraToolButton,'geometry'.encode(encoding='utf_8')) pandoraButtonAnimation.setDuration(self.ANIMATIONDURATION) pandoraButtonAnimation.setStartValue(pandoraButtonCurrentGeometry) pandoraWidgetCurrentGeometry = QtCore.QRect(self.pandoraWidget.geometry()) pandoraWidgetAnimation = QPropertyAnimation(self.pandoraWidget,'geometry'.encode(encoding='utf_8')) pandoraWidgetAnimation.setDuration(self.ANIMATIONDURATION) pandoraWidgetAnimation.setStartValue(pandoraWidgetCurrentGeometry) if self.PandoraCurrentState == 'Closed': pandoraButtonEndTopLeftCorner = QtCore.QPoint(self.pandoraToolButton.pos() - QtCore.QPoint(275, 0)) pandoraWidgetEndTopLeftCorner = QtCore.QPoint(self.pandoraWidget.pos() - QtCore.QPoint(275, 0)) self.PandoraCurrentState = 'Open' if self.Pandora.started == True: self.playToolButton.hide() self.pauseToolButton.show() else: pandoraButtonEndTopLeftCorner = QtCore.QPoint(self.pandoraToolButton.pos() + QtCore.QPoint(275, 0)) pandoraWidgetEndTopLeftCorner = QtCore.QPoint(self.pandoraWidget.pos() + QtCore.QPoint(275, 0)) self.PandoraCurrentState = 'Closed' pandoraButtonFinalGeometry = QtCore.QRect(pandoraButtonEndTopLeftCorner, QtCore.QSize(self.pandoraToolButton.width(), self.pandoraToolButton.height())) pandoraButtonAnimation.setEndValue(pandoraButtonFinalGeometry) pandoraWidgetFinalGeometry = QtCore.QRect(pandoraWidgetEndTopLeftCorner, QtCore.QSize(self.pandoraWidget.width(), self.pandoraWidget.height())) pandoraWidgetAnimation.setEndValue(pandoraWidgetFinalGeometry) pandoraButtonAnimation.start() pandoraWidgetAnimation.start() #self.setpandoraWidgetStyleSheet() self.PandoraIconAnimation = pandoraButtonAnimation self.PandoraWidgetAnimation = pandoraWidgetAnimation
class ThrobbingButton(QToolButton): @pyqtProperty(int) def icon_size(self): return self._icon_size @icon_size.setter def icon_size(self, value): self._icon_size = value def __init__(self, *args): QToolButton.__init__(self, *args) # vertically size policy must be expanding for it to align inside a # toolbar self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self._icon_size = -1 QToolButton.setIcon(self, QIcon(I('donate.png'))) self.setText('\xa0') self.animation = QPropertyAnimation(self, b'icon_size', self) self.animation.setDuration(60/72.*1000) self.animation.setLoopCount(4) self.animation.valueChanged.connect(self.value_changed) self.setCursor(Qt.PointingHandCursor) self.animation.finished.connect(self.animation_finished) def animation_finished(self): self.icon_size = self.iconSize().width() def enterEvent(self, ev): self.start_animation() def leaveEvent(self, ev): self.stop_animation() def value_changed(self, val): self.update() def start_animation(self): if config['disable_animations']: return if self.animation.state() != self.animation.Stopped or not self.isVisible(): return size = self.iconSize().width() smaller = int(0.7 * size) self.animation.setStartValue(smaller) self.animation.setEndValue(size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished() def paintEvent(self, ev): size = self._icon_size if self._icon_size > 10 else self.iconSize().width() p = QPainter(self) opt = QStyleOptionToolButton() self.initStyleOption(opt) s = self.style() opt.iconSize = QSize(size, size) s.drawComplexControl(s.CC_ToolButton, opt, p, self)
class ThrobbingButton(QToolButton): @pyqtProperty(int) def icon_size(self): return self._icon_size @icon_size.setter def icon_size(self, value): self._icon_size = value def __init__(self, *args): QToolButton.__init__(self, *args) self._icon_size = -1 QToolButton.setIcon(self, QIcon(I('donate.png'))) self.setText('\xa0') self.animation = QPropertyAnimation(self, b'icon_size', self) self.animation.setDuration(60 / 72. * 1000) self.animation.setLoopCount(4) self.animation.valueChanged.connect(self.value_changed) self.setCursor(Qt.PointingHandCursor) self.animation.finished.connect(self.animation_finished) def animation_finished(self): self.icon_size = self.iconSize().width() def enterEvent(self, ev): self.start_animation() def leaveEvent(self, ev): self.stop_animation() def value_changed(self, val): self.update() def start_animation(self): if config['disable_animations']: return if self.animation.state( ) != self.animation.Stopped or not self.isVisible(): return size = self.iconSize().width() smaller = int(0.7 * size) self.animation.setStartValue(smaller) self.animation.setEndValue(size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished() def paintEvent(self, ev): size = self._icon_size if self._icon_size > 10 else self.iconSize( ).width() p = QPainter(self) opt = QStyleOptionToolButton() self.initStyleOption(opt) s = self.style() opt.iconSize = QSize(size, size) s.drawComplexControl(s.CC_ToolButton, opt, p, self)
def pressedCalendarButton(self): try: calendarButtonCurrentGeometry = QtCore.QRect( self.calendarToolButton.geometry()) calendarButtonAnimation = QPropertyAnimation( self.calendarToolButton, 'geometry'.encode(encoding='utf_8')) calendarButtonAnimation.setDuration(self.ANIMATIONDURATION) calendarButtonAnimation.setStartValue( calendarButtonCurrentGeometry) calendarWidgetCurrentGeometry = QtCore.QRect( self.calendarWidget.geometry()) calendarWidgetAnimation = QPropertyAnimation( self.calendarWidget, 'geometry'.encode(encoding='utf_8')) calendarWidgetAnimation.setDuration(self.ANIMATIONDURATION) calendarWidgetAnimation.setStartValue( calendarWidgetCurrentGeometry) if self.CalendarCurrentState == 'Closed': calendarButtonEndTopLeftCorner = QtCore.QPoint( self.calendarToolButton.pos() - QtCore.QPoint(275, 0)) calendarWidgetEndTopLeftCorner = QtCore.QPoint( self.calendarWidget.pos() - QtCore.QPoint(275, 0)) self.CalendarCurrentState = 'Open' self.Calendar = Calendar() self.calendarLabel.setText(self.Calendar.EventList) else: calendarButtonEndTopLeftCorner = QtCore.QPoint( self.calendarToolButton.pos() + QtCore.QPoint(275, 0)) calendarWidgetEndTopLeftCorner = QtCore.QPoint( self.calendarWidget.pos() + QtCore.QPoint(275, 0)) self.CalendarCurrentState = 'Closed' calendarButtonFinalGeometry = QtCore.QRect( calendarButtonEndTopLeftCorner, QtCore.QSize(self.calendarToolButton.width(), self.calendarToolButton.height())) calendarButtonAnimation.setEndValue(calendarButtonFinalGeometry) calendarWidgetFinalGeometry = QtCore.QRect( calendarWidgetEndTopLeftCorner, QtCore.QSize(self.calendarWidget.width(), self.calendarWidget.height())) calendarWidgetAnimation.setEndValue(calendarWidgetFinalGeometry) calendarButtonAnimation.start() calendarWidgetAnimation.start() #self.setcalendarWidgetStyleSheet() self.CalendarIconAnimation = calendarButtonAnimation self.CalendarWidgetAnimation = calendarWidgetAnimation except Exception as e: print(e.strerror)
def show_error(self): # 窗体抖动效果 animation = QPropertyAnimation(self) animation.setTargetObject(self) animation.setPropertyName(b'pos') animation.setKeyValueAt(0, self.pos()) animation.setKeyValueAt(0.2, self.pos() + QPoint(15, 0)) animation.setKeyValueAt(0.5, self.pos()) animation.setKeyValueAt(0.7, self.pos() + QPoint(-15, 0)) animation.setKeyValueAt(1, self.pos()) animation.setDuration(100) animation.setLoopCount(3) animation.start(QAbstractAnimation.DeleteWhenStopped)
def pressedAlarmButton(self): try: alarmButtonCurrentGeometry = QtCore.QRect( self.alarmToolButton.geometry()) alarmButtonAnimation = QPropertyAnimation( self.alarmToolButton, 'geometry'.encode(encoding='utf_8')) alarmButtonAnimation.setDuration(self.ANIMATIONDURATION) alarmButtonAnimation.setStartValue(alarmButtonCurrentGeometry) alarmWidgetCurrentGeometry = QtCore.QRect( self.alarmWidget.geometry()) alarmWidgetAnimation = QPropertyAnimation( self.alarmWidget, 'geometry'.encode(encoding='utf_8')) alarmWidgetAnimation.setDuration(self.ANIMATIONDURATION) alarmWidgetAnimation.setStartValue(alarmWidgetCurrentGeometry) if self.AlarmCurrentState == 'Closed': alarmButtonEndTopLeftCorner = QtCore.QPoint( self.alarmToolButton.pos() - QtCore.QPoint(275, 0)) alarmWidgetEndTopLeftCorner = QtCore.QPoint( self.alarmWidget.pos() - QtCore.QPoint(275, 0)) self.AlarmCurrentState = 'Open' else: alarmButtonEndTopLeftCorner = QtCore.QPoint( self.alarmToolButton.pos() + QtCore.QPoint(275, 0)) alarmWidgetEndTopLeftCorner = QtCore.QPoint( self.alarmWidget.pos() + QtCore.QPoint(275, 0)) self.AlarmCurrentState = 'Closed' alarmButtonFinalGeometry = QtCore.QRect( alarmButtonEndTopLeftCorner, QtCore.QSize(self.alarmToolButton.width(), self.alarmToolButton.height())) alarmButtonAnimation.setEndValue(alarmButtonFinalGeometry) alarmWidgetFinalGeometry = QtCore.QRect( alarmWidgetEndTopLeftCorner, QtCore.QSize(self.alarmWidget.width(), self.alarmWidget.height())) alarmWidgetAnimation.setEndValue(alarmWidgetFinalGeometry) alarmButtonAnimation.start() alarmWidgetAnimation.start() #self.setAlarmWidgetStyleSheet() self.AlarmIconAnimation = alarmButtonAnimation self.AlarmWidgetAnimation = alarmWidgetAnimation except Exception as e: print(e.strerror)
def pressedPandoraButton(self): pandoraButtonCurrentGeometry = QtCore.QRect( self.pandoraToolButton.geometry()) pandoraButtonAnimation = QPropertyAnimation( self.pandoraToolButton, 'geometry'.encode(encoding='utf_8')) pandoraButtonAnimation.setDuration(self.ANIMATIONDURATION) pandoraButtonAnimation.setStartValue(pandoraButtonCurrentGeometry) pandoraWidgetCurrentGeometry = QtCore.QRect( self.pandoraWidget.geometry()) pandoraWidgetAnimation = QPropertyAnimation( self.pandoraWidget, 'geometry'.encode(encoding='utf_8')) pandoraWidgetAnimation.setDuration(self.ANIMATIONDURATION) pandoraWidgetAnimation.setStartValue(pandoraWidgetCurrentGeometry) if self.PandoraCurrentState == 'Closed': pandoraButtonEndTopLeftCorner = QtCore.QPoint( self.pandoraToolButton.pos() - QtCore.QPoint(275, 0)) pandoraWidgetEndTopLeftCorner = QtCore.QPoint( self.pandoraWidget.pos() - QtCore.QPoint(275, 0)) self.PandoraCurrentState = 'Open' if self.Pandora.started == True: self.playToolButton.hide() self.pauseToolButton.show() else: pandoraButtonEndTopLeftCorner = QtCore.QPoint( self.pandoraToolButton.pos() + QtCore.QPoint(275, 0)) pandoraWidgetEndTopLeftCorner = QtCore.QPoint( self.pandoraWidget.pos() + QtCore.QPoint(275, 0)) self.PandoraCurrentState = 'Closed' pandoraButtonFinalGeometry = QtCore.QRect( pandoraButtonEndTopLeftCorner, QtCore.QSize(self.pandoraToolButton.width(), self.pandoraToolButton.height())) pandoraButtonAnimation.setEndValue(pandoraButtonFinalGeometry) pandoraWidgetFinalGeometry = QtCore.QRect( pandoraWidgetEndTopLeftCorner, QtCore.QSize(self.pandoraWidget.width(), self.pandoraWidget.height())) pandoraWidgetAnimation.setEndValue(pandoraWidgetFinalGeometry) pandoraButtonAnimation.start() pandoraWidgetAnimation.start() #self.setpandoraWidgetStyleSheet() self.PandoraIconAnimation = pandoraButtonAnimation self.PandoraWidgetAnimation = pandoraWidgetAnimation
class ThrobbingButton(QToolButton): def __init__(self, *args): QToolButton.__init__(self, *args) self.animation = QPropertyAnimation(self, 'iconSize', self) self.animation.setDuration(60 / 72. * 1000) self.animation.setLoopCount(4) self.normal_icon_size = QSize(64, 64) self.animation.valueChanged.connect(self.value_changed) self.setCursor(Qt.PointingHandCursor) self.animation.finished.connect(self.animation_finished) def set_normal_icon_size(self, w, h): self.normal_icon_size = QSize(w, h) self.setIconSize(self.normal_icon_size) try: self.setMinimumSize(self.sizeHint()) except: self.setMinimumSize(QSize(w + 5, h + 5)) def animation_finished(self): self.setIconSize(self.normal_icon_size) def enterEvent(self, ev): self.start_animation() def leaveEvent(self, ev): self.stop_animation() def value_changed(self, val): self.update() def start_animation(self): if config['disable_animations']: return if self.animation.state( ) != self.animation.Stopped or not self.isVisible(): return size = self.normal_icon_size.width() smaller = int(0.7 * size) self.animation.setStartValue(QSize(smaller, smaller)) self.animation.setEndValue(self.normal_icon_size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished()
class ThrobbingButton(QToolButton): def __init__(self, *args): QToolButton.__init__(self, *args) self.animation = QPropertyAnimation(self, 'iconSize', self) self.animation.setDuration(60/72.*1000) self.animation.setLoopCount(4) self.normal_icon_size = QSize(64, 64) self.animation.valueChanged.connect(self.value_changed) self.setCursor(Qt.PointingHandCursor) self.animation.finished.connect(self.animation_finished) def set_normal_icon_size(self, w, h): self.normal_icon_size = QSize(w, h) self.setIconSize(self.normal_icon_size) try: self.setMinimumSize(self.sizeHint()) except: self.setMinimumSize(QSize(w+5, h+5)) def animation_finished(self): self.setIconSize(self.normal_icon_size) def enterEvent(self, ev): self.start_animation() def leaveEvent(self, ev): self.stop_animation() def value_changed(self, val): self.update() def start_animation(self): if config['disable_animations']: return if self.animation.state() != self.animation.Stopped or not self.isVisible(): return size = self.normal_icon_size.width() smaller = int(0.7 * size) self.animation.setStartValue(QSize(smaller, smaller)) self.animation.setEndValue(self.normal_icon_size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished()
class RevealBar(QWidget): # {{{ def __init__(self, parent): QWidget.__init__(self, parent) self.setVisible(False) self._animated_size = 1.0 self.animation = QPropertyAnimation(self, b'animated_size', self) self.animation.setEasingCurve(QEasingCurve.Linear) self.animation.setDuration(1000), self.animation.setStartValue(0.0), self.animation.setEndValue(1.0) self.animation.valueChanged.connect(self.animation_value_changed) self.animation.finished.connect(self.animation_done) @pyqtProperty(float) def animated_size(self): return self._animated_size @animated_size.setter def animated_size(self, val): self._animated_size = val def animation_value_changed(self, *args): self.update() def animation_done(self): self.setVisible(False) self.update() def start(self, bar): self.setGeometry(bar.geometry()) self.setVisible(True) self.animation.start() def paintEvent(self, ev): if self._animated_size < 1.0: rect = self.rect() painter = QPainter(self) pal = self.palette() col = pal.color(pal.Button) rect.setLeft(rect.left() + (rect.width() * self._animated_size)) painter.setClipRect(rect) painter.fillRect(self.rect(), col)
def pressedCalendarButton(self): try: calendarButtonCurrentGeometry = QtCore.QRect(self.calendarToolButton.geometry()) calendarButtonAnimation = QPropertyAnimation(self.calendarToolButton,'geometry'.encode(encoding='utf_8')) calendarButtonAnimation.setDuration(self.ANIMATIONDURATION) calendarButtonAnimation.setStartValue(calendarButtonCurrentGeometry) calendarWidgetCurrentGeometry = QtCore.QRect(self.calendarWidget.geometry()) calendarWidgetAnimation = QPropertyAnimation(self.calendarWidget,'geometry'.encode(encoding='utf_8')) calendarWidgetAnimation.setDuration(self.ANIMATIONDURATION) calendarWidgetAnimation.setStartValue(calendarWidgetCurrentGeometry) if self.CalendarCurrentState == 'Closed': calendarButtonEndTopLeftCorner = QtCore.QPoint(self.calendarToolButton.pos() - QtCore.QPoint(275, 0)) calendarWidgetEndTopLeftCorner = QtCore.QPoint(self.calendarWidget.pos() - QtCore.QPoint(275, 0)) self.CalendarCurrentState = 'Open' self.Calendar = Calendar() self.calendarLabel.setText(self.Calendar.EventList) else: calendarButtonEndTopLeftCorner = QtCore.QPoint(self.calendarToolButton.pos() + QtCore.QPoint(275, 0)) calendarWidgetEndTopLeftCorner = QtCore.QPoint(self.calendarWidget.pos() + QtCore.QPoint(275, 0)) self.CalendarCurrentState = 'Closed' calendarButtonFinalGeometry = QtCore.QRect(calendarButtonEndTopLeftCorner, QtCore.QSize(self.calendarToolButton.width(), self.calendarToolButton.height())) calendarButtonAnimation.setEndValue(calendarButtonFinalGeometry) calendarWidgetFinalGeometry = QtCore.QRect(calendarWidgetEndTopLeftCorner, QtCore.QSize(self.calendarWidget.width(), self.calendarWidget.height())) calendarWidgetAnimation.setEndValue(calendarWidgetFinalGeometry) calendarButtonAnimation.start() calendarWidgetAnimation.start() #self.setcalendarWidgetStyleSheet() self.CalendarIconAnimation = calendarButtonAnimation self.CalendarWidgetAnimation = calendarWidgetAnimation except Exception as e: print (e.strerror)
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 PyQt5.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 SlideFlip(QWidget): # API {{{ # In addition the isVisible() and setVisible() methods must be present def __init__(self, parent): QWidget.__init__(self, parent) self.setGeometry(0, 0, 1, 1) self._current_width = 0 self.before_image = self.after_image = None self.animation = QPropertyAnimation(self, 'current_width', self) self.setVisible(False) self.animation.valueChanged.connect(self.update) self.animation.finished.connect(self.finished) self.flip_forwards = True self.setAttribute(Qt.WA_OpaquePaintEvent) @property def running(self): 'True iff animation is currently running' return self.animation.state() == self.animation.Running def initialize(self, image, forwards=True): ''' Initialize the flipper, causes the flipper to show itself displaying the full `image`. :param image: The image to display as background :param forwards: If True flipper will flip forwards, otherwise backwards ''' self.flip_forwards = forwards self.before_image = QPixmap.fromImage(image) self.after_image = None self.setGeometry(0, 0, image.width(), image.height()) self.setVisible(True) def __call__(self, image, duration=0.5): ''' Start the animation. You must have called :meth:`initialize` first. :param duration: Animation duration in seconds. ''' if self.running: return self.after_image = QPixmap.fromImage(image) if self.flip_forwards: self.animation.setStartValue(image.width()) self.animation.setEndValue(0) t = self.before_image self.before_image = self.after_image self.after_image = t self.animation.setEasingCurve(QEasingCurve(QEasingCurve.InExpo)) else: self.animation.setStartValue(0) self.animation.setEndValue(image.width()) self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) self.animation.setDuration(duration * 1000) self.animation.start() # }}} def finished(self): self.setVisible(False) self.before_image = self.after_image = None def paintEvent(self, ev): if self.before_image is None: return canvas_size = self.rect() p = QPainter(self) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) p.drawPixmap(canvas_size, self.before_image, self.before_image.rect()) if self.after_image is not None: width = self._current_width iw = self.after_image.width() sh = min(self.after_image.height(), canvas_size.height()) if self.flip_forwards: source = QRect(max(0, iw - width), 0, width, sh) else: source = QRect(0, 0, width, sh) target = QRect(source) target.moveLeft(0) p.drawPixmap(target, self.after_image, source) p.end() def set_current_width(self, val): self._current_width = val current_width = pyqtProperty('int', fget=lambda self: self._current_width, fset=set_current_width )
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 PyQt5.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 SlideFlip(QWidget): # API {{{ # In addition the isVisible() and setVisible() methods must be present def __init__(self, parent): QWidget.__init__(self, parent) self.setGeometry(0, 0, 1, 1) self._current_width = 0 self.before_image = self.after_image = None self.animation = QPropertyAnimation(self, b'current_width', self) self.setVisible(False) self.animation.valueChanged.connect(self.update) self.animation.finished.connect(self.finished) self.flip_forwards = True self.setAttribute(Qt.WA_OpaquePaintEvent) @property def running(self): 'True iff animation is currently running' return self.animation.state() == self.animation.Running def initialize(self, image, forwards=True): ''' Initialize the flipper, causes the flipper to show itself displaying the full `image`. :param image: The image to display as background :param forwards: If True flipper will flip forwards, otherwise backwards ''' self.flip_forwards = forwards self.before_image = QPixmap.fromImage(image) self.after_image = None self.setGeometry(0, 0, image.width(), image.height()) self.setVisible(True) def __call__(self, image, duration=0.5): ''' Start the animation. You must have called :meth:`initialize` first. :param duration: Animation duration in seconds. ''' if self.running: return self.after_image = QPixmap.fromImage(image) if self.flip_forwards: self.animation.setStartValue(image.width()) self.animation.setEndValue(0) t = self.before_image self.before_image = self.after_image self.after_image = t self.animation.setEasingCurve(QEasingCurve(QEasingCurve.InExpo)) else: self.animation.setStartValue(0) self.animation.setEndValue(image.width()) self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) self.animation.setDuration(duration * 1000) self.animation.start() # }}} def finished(self): self.setVisible(False) self.before_image = self.after_image = None def paintEvent(self, ev): if self.before_image is None: return canvas_size = self.rect() p = QPainter(self) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) p.drawPixmap(canvas_size, self.before_image, self.before_image.rect()) if self.after_image is not None: width = self._current_width iw = self.after_image.width() sh = min(self.after_image.height(), canvas_size.height()) if self.flip_forwards: source = QRect(max(0, iw - width), 0, width, sh) else: source = QRect(0, 0, width, sh) target = QRect(source) target.moveLeft(0) p.drawPixmap(target, self.after_image, source) p.end() def set_current_width(self, val): self._current_width = val current_width = pyqtProperty('int', fget=lambda self: self._current_width, fset=set_current_width)
def drawControl(self, element, option, painter, widget): ''' @param: element ControlElement @param: option QStyleOption @param: painter QPainter @param: widget QWidget ''' v_opt = option if element != self.CE_TabBarTab or not isinstance( v_opt, QStyleOptionTab): QProxyStyle.drawControl(element, option, painter, widget) return rect = v_opt.rect selected = v_opt.state & self.State_Selected vertical_tabs = v_opt.shape == QTabBar.RoundedWest text = v_opt.text if selected: # background painter.save() grad = QLinearGradient(rect.topLeft(), rect.topRight()) grad.setColorAt(0, QColor(255, 255, 255, 140)) grad.setColorAt(0, QColor(255, 255, 255, 210)) painter.fillRect(rect.adjusted(0, 0, 0, -1), grad) painter.restore() # shadows painter.setPen(QColor(0, 0, 0, 110)) painter.drawLine(rect.topLeft() + QPoint(1, -1), rect.topRight() - QPoint(0, 1)) painter.drawLine(rect.bottomLeft(), rect.bottomRight()) painter.setPen(QColor(0, 0, 0, 40)) painter.drawLine(rect.topLeft(), rect.bottomLeft()) # highlights painter.setPen(QColor(255, 255, 255, 50)) painter.drawLine(rect.topLeft() + QPoint(0, -2), rect.topRight() - QPoint(0, 2)) painter.drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(0, 1)) painter.setPen(QColor(255, 255, 255, 40)) painter.drawLine(rect.topLeft() + QPoint(0, 0), rect.topRight()) painter.drawLine(rect.topRight() + QPoint(0, 1), rect.bottomRight() - QPoint(0, 1)) painter.drawLine(rect.bottomLeft() + QPoint(0, -1), rect.bottomRight() - QPoint(0, 1)) m = QTransform() if vertical_tabs: m = QTransform.fromTranslate(rect.left(), rect.bottom()) m.rotate(-90) else: m = QTransform.fromTranslate(rect.left(), rect.top()) draw_rect = QRect(QPoint(0, 0), m.mapRect(rect).size()) painter.save() painter.setTransform(m) icon_rect = QRect(QPoint(8, 0), v_opt.iconSize) text_rect = QRect(icon_rect.topRight() + QPoint(4, 0), draw_rect.size()) text_rect.setRight(draw_rect.width()) icon_rect.translate(0, (draw_rect.height() - icon_rect.height()) / 2) boldFont = QFont(painter.font()) boldFont.setPointSizeF(styleHelper.sidebarFontSize()) boldFont.setBold(True) painter.setFont(boldFont) painter.setPen(selected and QColor(255, 255, 255, 160) or QColor(0, 0, 0, 110)) textFlags = Qt.AlignHCenter | Qt.AlignVCenter painter.drawText(text_rect, textFlags, text) painter.setPen(selected and QColor(60, 60, 60) or styleHelper.panelTextColor()) if widget: fader_key = 'tab_' + text + '_fader' animation_key = 'tab_' + text + '_animation' tab_hover = widget.property('tab_hover') # int fader = widget.property(fader_key) # QPropertyAnimation animation = widget.property(animation_key) if not animation: mut_widget = widget fader = 0 mut_widget.setProperty(fader_key, fader) animation = QPropertyAnimation(mut_widget, fader_key, mut_widget) animation.valueChanged.connect(mut_widget.update) mut_widget.setProperty(animation_key, animation) if text == tab_hover: if animation.state( ) != QAbstractAnimation.Running and fader != 40: animation.stop() animation.setDuration(80) animation.setEndValue(40) animation.start() else: if animation.state( ) != QAbstractAnimation.Running and fader != 0: animation.stop() animation.setDuration(160) animation.setEndValue(0) animation.start() if not selected: painter.save() painter.fillRect(draw_rect, QColor(255, 255, 255, fader)) painter.setPen(QPen(QColor(255, 255, 255, fader), 1.0)) painter.drawLine( draw_rect.topLeft(), vertical_tabs and draw_rect.bottomLeft() or draw_rect.topRight()) painter.drawLine( draw_rect.bottomRight(), vertical_tabs and draw_rect.topRight() or draw_rect.bottomLeft()) painter.restore() if selected: iconMode = QIcon.Selected else: iconMode = QIcon.Normal styleHelper.drawIconWithShadow(v_opt.icon, icon_rect, painter, iconMode) painter.drawText(text_rect.translated(0, -1), textFlags, text) painter.restore()
class CoverView(QWidget): # {{{ cover_changed = pyqtSignal(object, object) cover_removed = pyqtSignal(object) def __init__(self, vertical, parent=None): QWidget.__init__(self, parent) self._current_pixmap_size = QSize(120, 120) self.vertical = vertical self.animation = QPropertyAnimation(self, "current_pixmap_size", self) self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) self.animation.setDuration(1000) self.animation.setStartValue(QSize(0, 0)) self.animation.valueChanged.connect(self.value_changed) self.setSizePolicy(QSizePolicy.Expanding if vertical else QSizePolicy.Minimum, QSizePolicy.Expanding) self.default_pixmap = QPixmap(I("book.png")) self.pixmap = self.default_pixmap self.pwidth = self.pheight = None self.data = {} self.do_layout() def value_changed(self, val): self.update() def setCurrentPixmapSize(self, val): self._current_pixmap_size = val def do_layout(self): if self.rect().width() == 0 or self.rect().height() == 0: return pixmap = self.pixmap pwidth, pheight = pixmap.width(), pixmap.height() try: self.pwidth, self.pheight = fit_image(pwidth, pheight, self.rect().width(), self.rect().height())[1:] except: self.pwidth, self.pheight = self.rect().width() - 1, self.rect().height() - 1 self.current_pixmap_size = QSize(self.pwidth, self.pheight) self.animation.setEndValue(self.current_pixmap_size) def show_data(self, data): self.animation.stop() same_item = getattr(data, "id", True) == self.data.get("id", False) self.data = {"id": data.get("id", None)} if data.cover_data[1]: self.pixmap = QPixmap.fromImage(data.cover_data[1]) if self.pixmap.isNull() or self.pixmap.width() < 5 or self.pixmap.height() < 5: self.pixmap = self.default_pixmap else: self.pixmap = self.default_pixmap self.do_layout() self.update() if not same_item and not config["disable_animations"] and self.isVisible(): self.animation.start() def paintEvent(self, event): canvas_size = self.rect() width = self.current_pixmap_size.width() extrax = canvas_size.width() - width if extrax < 0: extrax = 0 x = int(extrax / 2.0) height = self.current_pixmap_size.height() extray = canvas_size.height() - height if extray < 0: extray = 0 y = int(extray / 2.0) target = QRect(x, y, width, height) p = QPainter(self) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) p.drawPixmap(target, self.pixmap.scaled(target.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) if gprefs["bd_overlay_cover_size"]: sztgt = target.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = u"\u00a0%d x %d\u00a0" % (self.pixmap.width(), self.pixmap.height()) flags = Qt.AlignBottom | Qt.AlignRight | Qt.TextSingleLine szrect = p.boundingRect(sztgt, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255, 255, 255))) p.drawText(sztgt, flags, sz) p.end() current_pixmap_size = pyqtProperty("QSize", fget=lambda self: self._current_pixmap_size, fset=setCurrentPixmapSize) def contextMenuEvent(self, ev): cm = QMenu(self) paste = cm.addAction(_("Paste Cover")) copy = cm.addAction(_("Copy Cover")) remove = cm.addAction(_("Remove Cover")) if not QApplication.instance().clipboard().mimeData().hasImage(): paste.setEnabled(False) copy.triggered.connect(self.copy_to_clipboard) paste.triggered.connect(self.paste_from_clipboard) remove.triggered.connect(self.remove_cover) cm.exec_(ev.globalPos()) def copy_to_clipboard(self): QApplication.instance().clipboard().setPixmap(self.pixmap) def paste_from_clipboard(self, pmap=None): if not isinstance(pmap, QPixmap): cb = QApplication.instance().clipboard() pmap = cb.pixmap() if pmap.isNull() and cb.supportsSelection(): pmap = cb.pixmap(cb.Selection) if not pmap.isNull(): self.pixmap = pmap self.do_layout() self.update() self.update_tooltip(getattr(self.parent(), "current_path", "")) if not config["disable_animations"]: self.animation.start() id_ = self.data.get("id", None) if id_ is not None: self.cover_changed.emit(id_, pixmap_to_data(pmap)) def remove_cover(self): id_ = self.data.get("id", None) self.pixmap = self.default_pixmap self.do_layout() self.update() if id_ is not None: self.cover_removed.emit(id_) def update_tooltip(self, current_path): try: sz = self.pixmap.size() except: sz = QSize(0, 0) self.setToolTip( "<p>" + _("Double-click to open Book Details window") + "<br><br>" + _("Path") + ": " + current_path + "<br><br>" + _("Cover size: %(width)d x %(height)d") % dict(width=sz.width(), height=sz.height()) )
class CoverView(QWidget): # {{{ cover_changed = pyqtSignal(object, object) cover_removed = pyqtSignal(object) def __init__(self, vertical, parent=None): QWidget.__init__(self, parent) self._current_pixmap_size = QSize(120, 120) self.vertical = vertical self.animation = QPropertyAnimation(self, 'current_pixmap_size', self) self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) self.animation.setDuration(1000) self.animation.setStartValue(QSize(0, 0)) self.animation.valueChanged.connect(self.value_changed) self.setSizePolicy( QSizePolicy.Expanding if vertical else QSizePolicy.Minimum, QSizePolicy.Expanding) self.default_pixmap = QPixmap(I('book.png')) self.pixmap = self.default_pixmap self.pwidth = self.pheight = None self.data = {} self.do_layout() def value_changed(self, val): self.update() def setCurrentPixmapSize(self, val): self._current_pixmap_size = val def do_layout(self): if self.rect().width() == 0 or self.rect().height() == 0: return pixmap = self.pixmap pwidth, pheight = pixmap.width(), pixmap.height() try: self.pwidth, self.pheight = fit_image(pwidth, pheight, self.rect().width(), self.rect().height())[1:] except: self.pwidth, self.pheight = self.rect().width()-1, \ self.rect().height()-1 self.current_pixmap_size = QSize(self.pwidth, self.pheight) self.animation.setEndValue(self.current_pixmap_size) def show_data(self, data): self.animation.stop() same_item = getattr(data, 'id', True) == self.data.get('id', False) self.data = {'id':data.get('id', None)} if data.cover_data[1]: self.pixmap = QPixmap.fromImage(data.cover_data[1]) if self.pixmap.isNull() or self.pixmap.width() < 5 or \ self.pixmap.height() < 5: self.pixmap = self.default_pixmap else: self.pixmap = self.default_pixmap self.do_layout() self.update() if (not same_item and not config['disable_animations'] and self.isVisible()): self.animation.start() def paintEvent(self, event): canvas_size = self.rect() width = self.current_pixmap_size.width() extrax = canvas_size.width() - width if extrax < 0: extrax = 0 x = int(extrax/2.) height = self.current_pixmap_size.height() extray = canvas_size.height() - height if extray < 0: extray = 0 y = int(extray/2.) target = QRect(x, y, width, height) p = QPainter(self) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) p.drawPixmap(target, self.pixmap.scaled(target.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) if gprefs['bd_overlay_cover_size']: sztgt = target.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height()) flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine szrect = p.boundingRect(sztgt, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255,255,255))) p.drawText(sztgt, flags, sz) p.end() current_pixmap_size = pyqtProperty('QSize', fget=lambda self: self._current_pixmap_size, fset=setCurrentPixmapSize ) def contextMenuEvent(self, ev): cm = QMenu(self) paste = cm.addAction(_('Paste Cover')) copy = cm.addAction(_('Copy Cover')) remove = cm.addAction(_('Remove Cover')) if not QApplication.instance().clipboard().mimeData().hasImage(): paste.setEnabled(False) copy.triggered.connect(self.copy_to_clipboard) paste.triggered.connect(self.paste_from_clipboard) remove.triggered.connect(self.remove_cover) cm.exec_(ev.globalPos()) def copy_to_clipboard(self): QApplication.instance().clipboard().setPixmap(self.pixmap) def paste_from_clipboard(self, pmap=None): if not isinstance(pmap, QPixmap): cb = QApplication.instance().clipboard() pmap = cb.pixmap() if pmap.isNull() and cb.supportsSelection(): pmap = cb.pixmap(cb.Selection) if not pmap.isNull(): self.pixmap = pmap self.do_layout() self.update() self.update_tooltip(getattr(self.parent(), 'current_path', '')) if not config['disable_animations']: self.animation.start() id_ = self.data.get('id', None) if id_ is not None: self.cover_changed.emit(id_, pixmap_to_data(pmap)) def remove_cover(self): id_ = self.data.get('id', None) self.pixmap = self.default_pixmap self.do_layout() self.update() if id_ is not None: self.cover_removed.emit(id_) def update_tooltip(self, current_path): try: sz = self.pixmap.size() except: sz = QSize(0, 0) self.setToolTip( '<p>'+_('Double-click to open Book Details window') + '<br><br>' + _('Path') + ': ' + current_path + '<br><br>' + _('Cover size: %(width)d x %(height)d')%dict( width=sz.width(), height=sz.height()) )
class CoverView(QWidget): # {{{ cover_changed = pyqtSignal(object, object) cover_removed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) search_internet = pyqtSignal(object) def __init__(self, vertical, parent=None): QWidget.__init__(self, parent) self._current_pixmap_size = QSize(120, 120) self.vertical = vertical self.animation = QPropertyAnimation(self, b'current_pixmap_size', self) self.animation.setEasingCurve(QEasingCurve(QEasingCurve.OutExpo)) self.animation.setDuration(1000) self.animation.setStartValue(QSize(0, 0)) self.animation.valueChanged.connect(self.value_changed) self.setSizePolicy( QSizePolicy.Expanding if vertical else QSizePolicy.Minimum, QSizePolicy.Expanding) self.default_pixmap = QPixmap(I('default_cover.png')) self.pixmap = self.default_pixmap self.pwidth = self.pheight = None self.data = {} self.do_layout() def value_changed(self, val): self.update() def setCurrentPixmapSize(self, val): self._current_pixmap_size = val def do_layout(self): if self.rect().width() == 0 or self.rect().height() == 0: return pixmap = self.pixmap pwidth, pheight = pixmap.width(), pixmap.height() try: self.pwidth, self.pheight = fit_image(pwidth, pheight, self.rect().width(), self.rect().height())[1:] except: self.pwidth, self.pheight = self.rect().width()-1, \ self.rect().height()-1 self.current_pixmap_size = QSize(self.pwidth, self.pheight) self.animation.setEndValue(self.current_pixmap_size) def show_data(self, data): self.animation.stop() same_item = getattr(data, 'id', True) == self.data.get('id', False) self.data = {'id':data.get('id', None)} if data.cover_data[1]: self.pixmap = QPixmap.fromImage(data.cover_data[1]) if self.pixmap.isNull() or self.pixmap.width() < 5 or \ self.pixmap.height() < 5: self.pixmap = self.default_pixmap else: self.pixmap = self.default_pixmap self.do_layout() self.update() if (not same_item and not config['disable_animations'] and self.isVisible()): self.animation.start() def paintEvent(self, event): canvas_size = self.rect() width = self.current_pixmap_size.width() extrax = canvas_size.width() - width if extrax < 0: extrax = 0 x = int(extrax/2.) height = self.current_pixmap_size.height() extray = canvas_size.height() - height if extray < 0: extray = 0 y = int(extray/2.) target = QRect(x, y, width, height) p = QPainter(self) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) try: dpr = self.devicePixelRatioF() except AttributeError: dpr = self.devicePixelRatio() spmap = self.pixmap.scaled(target.size() * dpr, Qt.KeepAspectRatio, Qt.SmoothTransformation) spmap.setDevicePixelRatio(dpr) p.drawPixmap(target, spmap) if gprefs['bd_overlay_cover_size']: sztgt = target.adjusted(0, 0, 0, -4) f = p.font() f.setBold(True) p.setFont(f) sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height()) flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine szrect = p.boundingRect(sztgt, flags, sz) p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200)) p.setPen(QPen(QColor(255,255,255))) p.drawText(sztgt, flags, sz) p.end() current_pixmap_size = pyqtProperty('QSize', fget=lambda self: self._current_pixmap_size, fset=setCurrentPixmapSize ) def contextMenuEvent(self, ev): from calibre.gui2.open_with import populate_menu, edit_programs cm = QMenu(self) paste = cm.addAction(_('Paste cover')) copy = cm.addAction(_('Copy cover')) remove = cm.addAction(_('Remove cover')) gc = cm.addAction(_('Generate cover from metadata')) cm.addSeparator() if not QApplication.instance().clipboard().mimeData().hasImage(): paste.setEnabled(False) copy.triggered.connect(self.copy_to_clipboard) paste.triggered.connect(self.paste_from_clipboard) remove.triggered.connect(self.remove_cover) gc.triggered.connect(self.generate_cover) m = QMenu(_('Open cover with...')) populate_menu(m, self.open_with, 'cover_image') if len(m.actions()) == 0: cm.addAction(_('Open cover with...'), self.choose_open_with) else: m.addSeparator() m.addAction(_('Add another application to open cover...'), self.choose_open_with) m.addAction(_('Edit Open with applications...'), partial(edit_programs, 'cover_image', self)) cm.ocw = m cm.addMenu(m) cm.si = m = create_search_internet_menu(self.search_internet.emit) cm.addMenu(m) cm.exec_(ev.globalPos()) def open_with(self, entry): id_ = self.data.get('id', None) if id_ is not None: self.open_cover_with.emit(id_, entry) def choose_open_with(self): from calibre.gui2.open_with import choose_program entry = choose_program('cover_image', self) if entry is not None: self.open_with(entry) def copy_to_clipboard(self): QApplication.instance().clipboard().setPixmap(self.pixmap) def paste_from_clipboard(self, pmap=None): if not isinstance(pmap, QPixmap): cb = QApplication.instance().clipboard() pmap = cb.pixmap() if pmap.isNull() and cb.supportsSelection(): pmap = cb.pixmap(cb.Selection) if not pmap.isNull(): self.update_cover(pmap) def update_cover(self, pmap=None, cdata=None): if pmap is None: pmap = QPixmap() pmap.loadFromData(cdata) if pmap.isNull(): return if pmap.hasAlphaChannel(): pmap = QPixmap.fromImage(blend_image(image_from_x(pmap))) self.pixmap = pmap self.do_layout() self.update() self.update_tooltip(getattr(self.parent(), 'current_path', '')) if not config['disable_animations']: self.animation.start() id_ = self.data.get('id', None) if id_ is not None: self.cover_changed.emit(id_, cdata or pixmap_to_data(pmap)) def generate_cover(self, *args): book_id = self.data.get('id') if book_id is not None: from calibre.ebooks.covers import generate_cover from calibre.gui2.ui import get_gui mi = get_gui().current_db.new_api.get_metadata(book_id) cdata = generate_cover(mi) self.update_cover(cdata=cdata) def remove_cover(self): id_ = self.data.get('id', None) self.pixmap = self.default_pixmap self.do_layout() self.update() if id_ is not None: self.cover_removed.emit(id_) def update_tooltip(self, current_path): try: sz = self.pixmap.size() except: sz = QSize(0, 0) self.setToolTip( '<p>'+_('Double click to open the Book details window') + '<br><br>' + _('Path') + ': ' + current_path + '<br><br>' + _('Cover size: %(width)d x %(height)d pixels')%dict( width=sz.width(), height=sz.height()) )
class CoverDelegate(QStyledItemDelegate): MARGIN = 4 TOP, LEFT, RIGHT, BOTTOM = object(), object(), object(), object() @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, b'animated_size', self) self.animation.setEasingCurve(QEasingCurve.OutInCirc) self.animation.setDuration(500) self.set_dimensions() self.cover_cache = CoverCache() self.render_queue = LifoQueue() self.animating = None self.highlight_color = QColor(Qt.white) self.rating_font = QFont(rating_font()) 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'] self.original_show_emblems = gprefs['show_emblems'] self.orginal_emblem_size = gprefs['emblem_size'] self.orginal_emblem_position = gprefs['emblem_position'] self.emblem_size = gprefs['emblem_size'] if self.original_show_emblems else 0 try: self.gutter_position = getattr(self, self.orginal_emblem_position.upper()) except Exception: self.gutter_position = self.TOP 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) if self.emblem_size > 0: extra = self.emblem_size + self.MARGIN self.item_size += QSize(extra, 0) if self.gutter_position in (self.LEFT, self.RIGHT) else QSize(0, extra) 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): is_stars = False 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 = rating_to_stars(val, fm['display'].get('allow_half_stars', False)) is_stars = True return ('' if ans is None else unicode_type(ans)), is_stars except Exception: if DEBUG: import traceback traceback.print_exc() return '', is_stars def render_emblem(self, book_id, rule, rule_index, cache, mi, db, formatter, template_cache): ans = cache[book_id].get(rule, False) if ans is not False: return ans, mi ans = None if mi is None: mi = db.get_proxy_metadata(book_id) ans = formatter.safe_format(rule, mi, '', mi, column_name='cover_grid%d' % rule_index, template_cache=template_cache) or None cache[book_id][rule] = ans return ans, mi def cached_emblem(self, cache, name, raw_icon=None): ans = cache.get(name, False) if ans is not False: return ans sz = self.emblem_size ans = None if raw_icon is not None: ans = raw_icon.pixmap(sz, sz) elif name == ':ondevice': ans = QIcon(I('ok.png')).pixmap(sz, sz) elif name: pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz) if not pmap.isNull(): ans = pmap cache[name] = ans return ans def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, empty_index) # 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) emblem_rules = db.pref('cover_grid_icon_rules', default=()) emblems = [] if self.emblem_size > 0: mi = None for i, (kind, column, rule) in enumerate(emblem_rules): icon_name, mi = self.render_emblem(book_id, rule, i, m.cover_grid_emblem_cache, mi, db, m.formatter, m.cover_grid_template_cache) if icon_name is not None: pixmap = self.cached_emblem(m.cover_grid_bitmap_cache, icon_name) if pixmap is not None: emblems.append(pixmap) if marked: emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':marked', m.marked_icon)) if on_device: emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':ondevice')) painter.save() right_adjust = 0 try: rect = option.rect rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN) if self.emblem_size > 0: self.paint_emblems(painter, rect, emblems) orect = QRect(rect) trect = QRect(rect) if self.title_height != 0: rect.setBottom(rect.bottom() - self.title_height) trect.setTop(trect.bottom() - self.title_height + 5) 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) if self.title_height != 0: self.paint_title(painter, trect, db, book_id) else: if self.animating is not None and self.animating.row() == index.row(): cdata = cdata.scaled(cdata.size() * self._animated_size) dpr = cdata.devicePixelRatio() cw, ch = int(cdata.width() / dpr), int(cdata.height() / dpr) dx = max(0, int((rect.width() - cw)/2.0)) dy = max(0, rect.height() - ch) right_adjust = dx rect.adjust(dx, dy, -dx, 0) painter.drawPixmap(rect, cdata) if self.title_height != 0: self.paint_title(painter, trect, db, book_id) if self.emblem_size > 0: return # We dont draw embossed emblems as the ondevice/marked emblems are drawn in the gutter if marked: try: p = self.marked_emblem except AttributeError: p = self.marked_emblem = m.marked_icon.pixmap(48, 48) self.paint_embossed_emblem(p, painter, orect, right_adjust) if on_device: try: p = self.on_device_emblem except AttributeError: p = self.on_device_emblem = QIcon(I('ok.png')).pixmap(48, 48) self.paint_embossed_emblem(p, painter, orect, right_adjust, left=False) finally: painter.restore() def paint_title(self, painter, rect, db, book_id): painter.setRenderHint(QPainter.TextAntialiasing, True) title, is_stars = self.render_field(db, book_id) if is_stars: painter.setFont(self.rating_font) metrics = painter.fontMetrics() painter.setPen(self.highlight_color) painter.drawText(rect, Qt.AlignCenter|Qt.TextSingleLine, metrics.elidedText(title, Qt.ElideRight, rect.width())) def paint_emblems(self, painter, rect, emblems): gutter = self.emblem_size + self.MARGIN grect = QRect(rect) gpos = self.gutter_position if gpos is self.TOP: grect.setBottom(grect.top() + gutter) rect.setTop(rect.top() + gutter) elif gpos is self.BOTTOM: grect.setTop(grect.bottom() - gutter + self.MARGIN) rect.setBottom(rect.bottom() - gutter) elif gpos is self.LEFT: grect.setRight(grect.left() + gutter) rect.setLeft(rect.left() + gutter) else: grect.setLeft(grect.right() - gutter + self.MARGIN) rect.setRight(rect.right() - gutter) horizontal = gpos in (self.TOP, self.BOTTOM) painter.save() painter.setClipRect(grect) try: for i, emblem in enumerate(emblems): delta = 0 if i == 0 else self.emblem_size + self.MARGIN grect.moveLeft(grect.left() + delta) if horizontal else grect.moveTop(grect.top() + delta) rect = QRect(grect) rect.setWidth(int(emblem.width() / emblem.devicePixelRatio())), rect.setHeight(int(emblem.height() / emblem.devicePixelRatio())) painter.drawPixmap(rect, emblem) finally: painter.restore() def paint_embossed_emblem(self, pixmap, painter, orect, right_adjust, left=True): drect = QRect(orect) pw = int(pixmap.width() / pixmap.devicePixelRatio()) ph = int(pixmap.height() / pixmap.devicePixelRatio()) if left: drect.setLeft(drect.left() + right_adjust) drect.setRight(drect.left() + pw) else: drect.setRight(drect.right() - right_adjust) drect.setLeft(drect.right() - pw + 1) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - ph) painter.drawPixmap(drect, pixmap) @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 FancyTab(QWidget): def __init__(self, tabbar): ''' @param: tabbar QWidget ''' super().__init__(tabbar) self.icon = QIcon() self.text = '' self._animator = QPropertyAnimation() self._tabbar = tabbar # QWidget self._fader = 0.0 self._animator.setPropertyName(b'fader') self._animator.setTargetObject(self) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) def fader(self): ''' @return: float ''' return self._fader def setFader(self, value): ''' @param: value float ''' self._fader = value self._tabbar.update() fader = pyqtProperty(float, fader, setFader) # override def sizeHint(self): ''' @return: QSize ''' boldFont = QFont(self.font()) boldFont.setPointSizeF(styleHelper.sidebarFontSize()) boldFont.setBold(True) fm = QFontMetrics(boldFont) spacing = 8 width = 60 + spacing + 2 iconHeight = 32 ret = QSize(width, iconHeight + spacing + fm.height()) return ret def fadeIn(self): self._animator.stop() self._animator.setDuration(80) self._animator.setEndValue(40) self._animator.start() def fadeOut(self): self._animator.stop() self._animator.setDuration(160) self._animator.setEndValue(0) self._animator.start() # protected: # override def enterEvent(self, event): self.fadeIn() # override def leaveEvent(self, event): self.fadeOut()
class CoverDelegate(QStyledItemDelegate): MARGIN = 4 TOP, LEFT, RIGHT, BOTTOM = object(), object(), object(), object() @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, b'animated_size', self) self.animation.setEasingCurve(QEasingCurve.OutInCirc) self.animation.setDuration(500) self.set_dimensions() self.cover_cache = CoverCache() self.render_queue = LifoQueue() self.animating = None self.highlight_color = QColor(Qt.white) self.rating_font = QFont(rating_font()) 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'] self.original_show_emblems = gprefs['show_emblems'] self.orginal_emblem_size = gprefs['emblem_size'] self.orginal_emblem_position = gprefs['emblem_position'] self.emblem_size = gprefs['emblem_size'] if self.original_show_emblems else 0 try: self.gutter_position = getattr(self, self.orginal_emblem_position.upper()) except Exception: self.gutter_position = self.TOP 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) if self.emblem_size > 0: extra = self.emblem_size + self.MARGIN self.item_size += QSize(extra, 0) if self.gutter_position in (self.LEFT, self.RIGHT) else QSize(0, extra) 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): is_stars = False 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 = rating_to_stars(val, fm['display'].get('allow_half_stars', False)) is_stars = True return ('' if ans is None else unicode_type(ans)), is_stars except Exception: if DEBUG: import traceback traceback.print_exc() return '', is_stars def render_emblem(self, book_id, rule, rule_index, cache, mi, db, formatter, template_cache): ans = cache[book_id].get(rule, False) if ans is not False: return ans, mi ans = None if mi is None: mi = db.get_proxy_metadata(book_id) ans = formatter.safe_format(rule, mi, '', mi, column_name='cover_grid%d' % rule_index, template_cache=template_cache) or None cache[book_id][rule] = ans return ans, mi def cached_emblem(self, cache, name, raw_icon=None): ans = cache.get(name, False) if ans is not False: return ans sz = self.emblem_size ans = None if raw_icon is not None: ans = raw_icon.pixmap(sz, sz) elif name == ':ondevice': ans = QIcon(I('ok.png')).pixmap(sz, sz) elif name: pmap = QIcon(os.path.join(config_dir, 'cc_icons', name)).pixmap(sz, sz) if not pmap.isNull(): ans = pmap cache[name] = ans return ans def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, empty_index) # 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) emblem_rules = db.pref('cover_grid_icon_rules', default=()) emblems = [] if self.emblem_size > 0: mi = None for i, (kind, column, rule) in enumerate(emblem_rules): icon_name, mi = self.render_emblem(book_id, rule, i, m.cover_grid_emblem_cache, mi, db, m.formatter, m.cover_grid_template_cache) if icon_name is not None: pixmap = self.cached_emblem(m.cover_grid_bitmap_cache, icon_name) if pixmap is not None: emblems.append(pixmap) if marked: emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':marked', m.marked_icon)) if on_device: emblems.insert(0, self.cached_emblem(m.cover_grid_bitmap_cache, ':ondevice')) painter.save() right_adjust = 0 try: rect = option.rect rect.adjust(self.MARGIN, self.MARGIN, -self.MARGIN, -self.MARGIN) if self.emblem_size > 0: self.paint_emblems(painter, rect, emblems) 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) dpr = cdata.devicePixelRatio() cw, ch = int(cdata.width() / dpr), int(cdata.height() / dpr) dx = max(0, int((rect.width() - cw)/2.0)) dy = max(0, rect.height() - ch) 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, is_stars = self.render_field(db, book_id) if is_stars: painter.setFont(self.rating_font) metrics = painter.fontMetrics() painter.setPen(self.highlight_color) painter.drawText(rect, Qt.AlignCenter|Qt.TextSingleLine, metrics.elidedText(title, Qt.ElideRight, rect.width())) if self.emblem_size > 0: return # We dont draw embossed emblems as the ondevice/marked emblems are drawn in the gutter if marked: try: p = self.marked_emblem except AttributeError: p = self.marked_emblem = m.marked_icon.pixmap(48, 48) self.paint_embossed_emblem(p, painter, orect, right_adjust) if on_device: try: p = self.on_device_emblem except AttributeError: p = self.on_device_emblem = QIcon(I('ok.png')).pixmap(48, 48) self.paint_embossed_emblem(p, painter, orect, right_adjust, left=False) finally: painter.restore() def paint_emblems(self, painter, rect, emblems): gutter = self.emblem_size + self.MARGIN grect = QRect(rect) gpos = self.gutter_position if gpos is self.TOP: grect.setBottom(grect.top() + gutter) rect.setTop(rect.top() + gutter) elif gpos is self.BOTTOM: grect.setTop(grect.bottom() - gutter + self.MARGIN) rect.setBottom(rect.bottom() - gutter) elif gpos is self.LEFT: grect.setRight(grect.left() + gutter) rect.setLeft(rect.left() + gutter) else: grect.setLeft(grect.right() - gutter + self.MARGIN) rect.setRight(rect.right() - gutter) horizontal = gpos in (self.TOP, self.BOTTOM) painter.save() painter.setClipRect(grect) try: for i, emblem in enumerate(emblems): delta = 0 if i == 0 else self.emblem_size + self.MARGIN grect.moveLeft(grect.left() + delta) if horizontal else grect.moveTop(grect.top() + delta) rect = QRect(grect) rect.setWidth(int(emblem.width() / emblem.devicePixelRatio())), rect.setHeight(int(emblem.height() / emblem.devicePixelRatio())) painter.drawPixmap(rect, emblem) finally: painter.restore() def paint_embossed_emblem(self, pixmap, painter, orect, right_adjust, left=True): drect = QRect(orect) pw = int(pixmap.width() / pixmap.devicePixelRatio()) ph = int(pixmap.height() / pixmap.devicePixelRatio()) if left: drect.setLeft(drect.left() + right_adjust) drect.setRight(drect.left() + pw) else: drect.setRight(drect.right() - right_adjust) drect.setLeft(drect.right() - pw + 1) drect.setBottom(drect.bottom() - self.title_height) drect.setTop(drect.bottom() - ph) painter.drawPixmap(drect, pixmap) @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 IconButton(QPushButton): def __init__(self, icon, title, parent, size=None, padding=None, color=None, hover_color=None): QPushButton.__init__(self, parent) self._theme_manager = ThemeManager.get(self) self._background_color = hover_color or self._theme_manager.get_color( 'button_background_hover') self.setToolTip(title) self._icon = Icon(self, icon, size, padding, color) self._iconname = icon self._layout = QHBoxLayout(self) self._layout.addWidget(self._icon) self._layout.setContentsMargins(0, 0, 0, 0) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._size = size self.setFlat(True) self._background_opacity = 0.0 self._background_opacity_stop = 0.0 self._update_style_sheet() self._animation = QPropertyAnimation(self, b'backgroundOpacity') self.clicked.connect(self._update_background_opacity_stop) def setColor(self, color=None): self._icon.setColor(color) def setIcon(self, icon): self._icon.loadIcon(icon) @pyqtProperty(float) def backgroundOpacity(self): return self._background_opacity @backgroundOpacity.setter def backgroundOpacity(self, value): self._background_opacity = value self._update_style_sheet() def sizeHint(self): return self._size or self._icon.sizeHint() def enterEvent(self, event): if self.isEnabled(): self._start_opacity_animation(350, 0, 1) return QPushButton.enterEvent(self, event) def leaveEvent(self, event): if self.isEnabled(): self._start_opacity_animation(350, 1, 0) return QPushButton.leaveEvent(self, event) def mousePressEvent(self, event): if self.isEnabled(): self._start_opacity_animation(50, 1, 0.6) return QPushButton.mousePressEvent(self, event) def mouseReleaseEvent(self, event): if self.isEnabled(): self._start_opacity_animation(50, self._background_opacity, 1) return QPushButton.mouseReleaseEvent(self, event) def _start_opacity_animation(self, duration, start, end): self._animation.stop() self._animation.setDuration(duration) self._animation.setStartValue(start) self._animation.setEndValue(end) self._animation.setEasingCurve(QEasingCurve.BezierSpline) self._animation.start() def setEnabled(self, value): QPushButton.setEnabled(self, value) self._icon.setEnabled(value) def _update_background_opacity_stop(self): self._background_opacity_stop = 0.0 if not self.isChecked() else 0.8 self._update_style_sheet() def setChecked(self, value): QPushButton.setChecked(self, value) self._update_background_opacity_stop() def _update_style_sheet(self): stylesheet = """ Icon {{ background-color: {color}; border-radius: 2px; }} QPushButton {{ background-color: transparent; border: none; }} """.format(color=format_color( self._background_color, ColorFormat.rgba_string_256, max(self._background_opacity, self._background_opacity_stop))) self.setStyleSheet(stylesheet)