%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/ |
| Current File : //lib/calibre/calibre/gui2/image_popup.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from qt.core import (
QApplication, QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QImage,
QLabel, QPainter, QPalette, QPixmap, QScrollArea, QSize, QSizePolicy,
QSvgRenderer, Qt, QTransform, QUrl, QVBoxLayout, pyqtSignal
)
from calibre import fit_image
from calibre.gui2 import (
NO_URL_FORMATTING, choose_save_file, gprefs, max_available_height
)
def render_svg(widget, path):
img = QPixmap()
rend = QSvgRenderer()
if rend.load(path):
dpr = getattr(widget, 'devicePixelRatioF', widget.devicePixelRatio)()
sz = rend.defaultSize()
h = (max_available_height() - 50)
w = int(h * sz.height() / float(sz.width()))
pd = QImage(w * dpr, h * dpr, QImage.Format.Format_RGB32)
pd.fill(Qt.GlobalColor.white)
p = QPainter(pd)
rend.render(p)
p.end()
img = QPixmap.fromImage(pd)
img.setDevicePixelRatio(dpr)
return img
class Label(QLabel):
toggle_fit = pyqtSignal()
def __init__(self, scrollarea):
super().__init__(scrollarea)
self.setBackgroundRole(QPalette.ColorRole.Text if QApplication.instance().is_dark_theme else QPalette.ColorRole.Base)
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
self.setScaledContents(True)
self.default_cursor = self.cursor()
self.in_drag = False
self.prev_drag_position = None
self.scrollarea = scrollarea
@property
def is_pannable(self):
return self.scrollarea.verticalScrollBar().isVisible() or self.scrollarea.horizontalScrollBar().isVisible()
def mousePressEvent(self, ev):
if ev.button() == Qt.MouseButton.LeftButton and self.is_pannable:
self.setCursor(Qt.CursorShape.ClosedHandCursor)
self.in_drag = True
self.prev_drag_position = ev.globalPos()
return super().mousePressEvent(ev)
def mouseReleaseEvent(self, ev):
if ev.button() == Qt.MouseButton.LeftButton and self.in_drag:
self.setCursor(self.default_cursor)
self.in_drag = False
self.prev_drag_position = None
return super().mousePressEvent(ev)
def mouseMoveEvent(self, ev):
if self.prev_drag_position is not None:
p = self.prev_drag_position
self.prev_drag_position = pos = ev.globalPos()
self.dragged(pos.x() - p.x(), pos.y() - p.y())
return super().mouseMoveEvent(ev)
def dragged(self, dx, dy):
h = self.scrollarea.horizontalScrollBar()
if h.isVisible():
h.setValue(h.value() - dx)
v = self.scrollarea.verticalScrollBar()
if v.isVisible():
v.setValue(v.value() - dy)
class ScrollArea(QScrollArea):
toggle_fit = pyqtSignal()
def mouseDoubleClickEvent(self, ev):
if ev.button() == Qt.MouseButton.LeftButton:
self.toggle_fit.emit()
class ImageView(QDialog):
def __init__(self, parent, current_img, current_url, geom_name='viewer_image_popup_geometry', prefs=gprefs):
QDialog.__init__(self)
self.prefs = prefs
self.current_image_name = ''
self.maximized_at_last_fullscreen = False
self.setWindowFlag(Qt.WindowType.WindowMinimizeButtonHint)
self.setWindowFlag(Qt.WindowType.WindowMaximizeButtonHint)
self.avail_geom = self.screen().availableGeometry()
self.current_img = current_img
self.current_url = current_url
self.factor = 1.0
self.geom_name = geom_name
self.scrollarea = sa = ScrollArea()
sa.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
sa.setBackgroundRole(QPalette.ColorRole.Dark)
self.label = l = Label(sa)
sa.toggle_fit.connect(self.toggle_fit)
sa.setWidget(l)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.zi_button = zi = bb.addButton(_('Zoom &in'), QDialogButtonBox.ButtonRole.ActionRole)
self.zo_button = zo = bb.addButton(_('Zoom &out'), QDialogButtonBox.ButtonRole.ActionRole)
self.save_button = so = bb.addButton(_('&Save as'), QDialogButtonBox.ButtonRole.ActionRole)
self.rotate_button = ro = bb.addButton(_('&Rotate'), QDialogButtonBox.ButtonRole.ActionRole)
self.fullscreen_button = fo = bb.addButton(_('&Full screen'), QDialogButtonBox.ButtonRole.ActionRole)
zi.setIcon(QIcon(I('plus.png')))
zo.setIcon(QIcon(I('minus.png')))
so.setIcon(QIcon(I('save.png')))
ro.setIcon(QIcon(I('rotate-right.png')))
fo.setIcon(QIcon(I('page.png')))
zi.clicked.connect(self.zoom_in)
zo.clicked.connect(self.zoom_out)
so.clicked.connect(self.save_image)
ro.clicked.connect(self.rotate_image)
fo.setCheckable(True)
self.l = l = QVBoxLayout(self)
l.addWidget(sa)
self.h = h = QHBoxLayout()
h.setContentsMargins(0, 0, 0, 0)
l.addLayout(h)
self.fit_image = i = QCheckBox(_('&Fit image'))
i.setToolTip(_('Fit image inside the available space'))
i.setChecked(bool(self.prefs.get('image_popup_fit_image')))
i.stateChanged.connect(self.fit_changed)
h.addWidget(i), h.addStretch(), h.addWidget(bb)
if self.fit_image.isChecked():
self.set_to_viewport_size()
geom = self.prefs.get(self.geom_name)
if geom is not None:
self.restoreGeometry(geom)
fo.setChecked(self.isFullScreen())
fo.toggled.connect(self.toggle_fullscreen)
def set_to_viewport_size(self):
page_size = self.scrollarea.size()
pw, ph = page_size.width() - 2, page_size.height() - 2
img_size = self.current_img.size()
iw, ih = img_size.width(), img_size.height()
scaled, nw, nh = fit_image(iw, ih, pw, ph)
if scaled:
self.factor = min(nw/iw, nh/ih)
img_size.setWidth(nw), img_size.setHeight(nh)
self.label.resize(img_size)
def resizeEvent(self, ev):
if self.fit_image.isChecked():
self.set_to_viewport_size()
def factor_from_fit(self):
scaled_height = self.label.size().height()
actual_height = self.current_img.size().height()
return scaled_height / actual_height
def zoom_in(self):
if self.fit_image.isChecked():
factor = self.factor_from_fit()
self.fit_image.setChecked(False)
self.factor = factor
self.factor *= 1.25
self.adjust_image(1.25)
def zoom_out(self):
if self.fit_image.isChecked():
factor = self.factor_from_fit()
self.fit_image.setChecked(False)
self.factor = factor
self.factor *= 0.8
self.adjust_image(0.8)
def save_image(self):
filters=[('Images', ['png', 'jpeg', 'jpg'])]
f = choose_save_file(self, 'viewer image view save dialog',
_('Choose a file to save to'), filters=filters,
all_files=False, initial_filename=self.current_image_name or None)
if f:
from calibre.utils.img import save_image
save_image(self.current_img.toImage(), f)
def fit_changed(self):
fitted = bool(self.fit_image.isChecked())
self.prefs.set('image_popup_fit_image', fitted)
if self.fit_image.isChecked():
self.set_to_viewport_size()
else:
self.factor = 1
self.adjust_image(1)
def toggle_fit(self):
self.fit_image.toggle()
def adjust_image(self, factor):
if self.fit_image.isChecked():
self.set_to_viewport_size()
return
self.label.resize(self.factor * self.current_img.size())
self.zi_button.setEnabled(self.factor <= 3)
self.zo_button.setEnabled(self.factor >= 0.3333)
self.adjust_scrollbars(factor)
def adjust_scrollbars(self, factor):
for sb in (self.scrollarea.horizontalScrollBar(),
self.scrollarea.verticalScrollBar()):
sb.setValue(int(factor*sb.value()) + int((factor - 1) * sb.pageStep()/2))
def rotate_image(self):
pm = self.label.pixmap()
t = QTransform()
t.rotate(90)
pm = self.current_img = pm.transformed(t)
self.label.setPixmap(pm)
self.label.adjustSize()
if self.fit_image.isChecked():
self.set_to_viewport_size()
else:
self.factor = 1
for sb in (self.scrollarea.horizontalScrollBar(),
self.scrollarea.verticalScrollBar()):
sb.setValue(0)
def __call__(self, use_exec=False):
geom = self.avail_geom
self.label.setPixmap(self.current_img)
self.label.adjustSize()
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
geom = self.prefs.get(self.geom_name, None)
if geom is not None:
QApplication.instance().safe_restore_geometry(self, geom)
try:
self.current_image_name = str(self.current_url.toString(NO_URL_FORMATTING)).rpartition('/')[-1]
except AttributeError:
self.current_image_name = self.current_url
reso = ''
if self.current_img and not self.current_img.isNull():
reso = f'[{self.current_img.width()}x{self.current_img.height()}]'
title = _('Image: {name} {resolution}').format(name=self.current_image_name, resolution=reso)
self.setWindowTitle(title)
if use_exec:
self.exec()
else:
self.show()
def done(self, e):
self.prefs[self.geom_name] = bytearray(self.saveGeometry())
return QDialog.done(self, e)
def toggle_fullscreen(self):
on = not self.isFullScreen()
if on:
self.maximized_at_last_fullscreen = self.isMaximized()
self.showFullScreen()
else:
if self.maximized_at_last_fullscreen:
self.showMaximized()
else:
self.showNormal()
def wheelEvent(self, event):
d = event.angleDelta().y()
if abs(d) > 0 and not self.scrollarea.verticalScrollBar().isVisible():
event.accept()
(self.zoom_out if d < 0 else self.zoom_in)()
class ImagePopup:
def __init__(self, parent, prefs=gprefs):
self.current_img = QPixmap()
self.current_url = QUrl()
self.parent = parent
self.dialogs = []
self.prefs = prefs
def __call__(self):
if self.current_img.isNull():
return
d = ImageView(self.parent, self.current_img, self.current_url, prefs=self.prefs)
self.dialogs.append(d)
d.finished.connect(self.cleanup, type=Qt.ConnectionType.QueuedConnection)
d()
def cleanup(self):
for d in tuple(self.dialogs):
if not d.isVisible():
self.dialogs.remove(d)
if __name__ == '__main__':
import sys
from calibre.gui2 import Application
app = Application([])
p = QPixmap()
p.load(sys.argv[-1])
u = QUrl.fromLocalFile(sys.argv[-1])
d = ImageView(None, p, u)
d()
app.exec()