Esempio n. 1
0
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.Type.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(QPalette.ColorRole.Button)
            rect.setLeft(rect.left() + int(rect.width() * self._animated_size))
            painter.setClipRect(rect)
            painter.fillRect(self.rect(), col)
Esempio n. 2
0
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.Type.OutExpo))
        self.animation.setDuration(1000)
        self.animation.setStartValue(QSize(0, 0))
        self.animation.valueChanged.connect(self.value_changed)

        self.setSizePolicy(
                QSizePolicy.Policy.Expanding if vertical else QSizePolicy.Policy.Minimum,
                QSizePolicy.Policy.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.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform)
        try:
            dpr = self.devicePixelRatioF()
        except AttributeError:
            dpr = self.devicePixelRatio()
        spmap = self.pixmap.scaled(target.size() * dpr, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.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 = '\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
            flags = Qt.AlignmentFlag.AlignBottom|Qt.AlignmentFlag.AlignRight|Qt.TextFlag.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'))
        save = cm.addAction(_('Save cover to disk'))
        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)
        save.triggered.connect(self.save_cover)
        create_open_cover_with_menu(self, cm)
        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(QClipboard.Mode.Selection)
        if not pmap.isNull():
            self.update_cover(pmap)

    def save_cover(self):
        from calibre.gui2.ui import get_gui
        book_id = self.data.get('id')
        db = get_gui().current_db.new_api
        path = choose_save_file(
            self, 'save-cover-from-book-details', _('Choose cover save location'),
            filters=[(_('JPEG images'), ['jpg', 'jpeg'])], all_files=False,
            initial_filename='{}.jpeg'.format(sanitize_file_name(db.field_for('title', book_id, default_value='cover')))
        )
        if path:
            db.copy_cover_to(book_id, path)

    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 None:
            return
        from calibre.gui2.ui import get_gui
        mi = get_gui().current_db.new_api.get_metadata(book_id)
        if not mi.has_cover or confirm(
                _('Are you sure you want to replace the cover? The existing cover will be permanently lost.'), 'book_details_generate_cover'):
            from calibre.ebooks.covers import generate_cover
            cdata = generate_cover(mi)
            self.update_cover(cdata=cdata)

    def remove_cover(self):
        if not confirm_delete(
            _('Are you sure you want to delete the cover permanently?'),
                'book-details-confirm-cover-remove', parent=self):
            return
        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())
        )
Esempio n. 3
0
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, b"geometry", self)
        self.animation.setDuration(750)
        self.animation.setLoopCount(2)
        self.animation.setEasingCurve(QEasingCurve.Type.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.ColorGroup.Active,
                                 QPalette.ColorRole.WindowText)
        self.color = QColor(c)
        self.color.setAlpha(100)
        self.brush = QBrush(self.color, Qt.BrushStyle.SolidPattern)

        # from qt.core 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(QAbstractAnimation.Direction.Backward)
        num_keys = 100
        for i in range(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(QPainter.RenderHint.Antialiasing)
        p.setBrush(self.brush)
        p.setPen(Qt.PenStyle.NoPen)
        p.drawPath(self.arrow_path)
        p.end()