diff --git a/build.mk b/build.mk
index f9c3929c404cb33f7f810a365b4eae03510256a1..407d92775b978912199cace3b08730f48894d289 100644
--- a/build.mk
+++ b/build.mk
@@ -1,5 +1,5 @@
core := weboob/tools/application/qt5
-applications := qboobmsg qhavedate qwebcontentedit qflatboob qcineoob qcookboob qhandjoob qbooblyrics
+applications := qboobmsg qhavedate qwebcontentedit qflatboob qcineoob qcookboob qhandjoob qbooblyrics qgalleroob
ifeq ($(WIN32),)
applications += qvideoob
endif
diff --git a/desktop/qgalleroob.desktop b/desktop/qgalleroob.desktop
new file mode 100644
index 0000000000000000000000000000000000000000..e419be574e904658d651c32cf678788251ac9099
--- /dev/null
+++ b/desktop/qgalleroob.desktop
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=QGalleroob
+Comment=Search for galleries and images on many websites, and get info about them
+Exec=qgalleroob
+Icon=qgalleroob
+Terminal=false
+Type=Application
+StartupNotify=true
+Categories=Graphics;Viewer;Qt;
+Keywords=image;gallery;view;
diff --git a/setup.py b/setup.py
index 64b8dee055285bfe2209093ad9d28ef151a52f21..b7a2ebf35c5a0d922dd97679af44a7a2db17bca4 100755
--- a/setup.py
+++ b/setup.py
@@ -66,6 +66,7 @@ def install_weboob():
hildon_scripts = set(('masstransit',))
qt_scripts = set(('qboobmsg',
'qhavedate',
+ 'qgalleroob',
'qvideoob',
'weboob-config-qt',
'qwebcontentedit',
@@ -105,7 +106,9 @@ def install_weboob():
'weboob.applications.qwebcontentedit',
'weboob.applications.qwebcontentedit.ui'
'weboob.applications.qflatboob',
- 'weboob.applications.qflatboob.ui'
+ 'weboob.applications.qflatboob.ui',
+ 'weboob.applications.qgalleroob',
+ 'weboob.applications.qgalleroob.ui',
))
if not options.hildon:
diff --git a/weboob/applications/qgalleroob/__init__.py b/weboob/applications/qgalleroob/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8fdddbd257008e5391beb176abab7f4637fca3e6
--- /dev/null
+++ b/weboob/applications/qgalleroob/__init__.py
@@ -0,0 +1,3 @@
+from .qgalleroob import QGalleroob
+
+__all__ = ['QGalleroob']
diff --git a/weboob/applications/qgalleroob/main_window.py b/weboob/applications/qgalleroob/main_window.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a085a7e0326e304fce19f300abd763024354b6d
--- /dev/null
+++ b/weboob/applications/qgalleroob/main_window.py
@@ -0,0 +1,220 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2016 Vincent A
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+import os
+
+from PyQt5.QtCore import QModelIndex, pyqtSlot as Slot
+
+from weboob.tools.application.qt5 import QtMainWindow
+from weboob.tools.application.qt5.backendcfg import BackendCfg
+from weboob.tools.application.qt5.models import BackendListModel, ResultModel, FilterTypeModel
+from weboob.capabilities.collection import BaseCollection, CapCollection
+from weboob.capabilities.gallery import CapGallery, BaseGallery
+from weboob.capabilities.image import CapImage, BaseImage
+
+from .ui.mainwindow_ui import Ui_MainWindow
+from .viewer import Viewer
+
+
+def size_format(n):
+ UNITS = [
+ (1 << 40, 'TiB'),
+ (1 << 30, 'GiB'),
+ (1 << 20, 'MiB'),
+ (1 << 10, 'KiB'),
+ (0, 'B')
+ ]
+ for f, u in UNITS:
+ if n > f:
+ return '%.2f %s' % (n / float(f), u)
+
+
+class MainWindow(QtMainWindow):
+ def __init__(self, config, storage, weboob, parent=None):
+ super(MainWindow, self).__init__(parent)
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+ self.mdl = ResultModel(weboob)
+ self.mdl.setColumnFields([['name', 'title'],['url']])
+ self.proxy_mdl = FilterTypeModel()
+ self.proxy_mdl.setAcceptedTypes([BaseCollection])
+ self.proxy_mdl.setSourceModel(self.mdl)
+
+ self.ui.collectionTree.setModel(self.proxy_mdl)
+ self.ui.collectionTree.selectionModel().currentChanged.connect(self.showCollection)
+
+ self.config = config
+ self.storage = storage
+ self.weboob = weboob
+
+ self.ui.browseButton.clicked.connect(self.startBrowse)
+ self.ui.searchEdit.returnPressed.connect(self.startSearch)
+ self.ui.searchButton.clicked.connect(self.startSearch)
+ self.ui.galleryList.setModel(self.mdl)
+ self.ui.galleryList.selectionModel().currentChanged.connect(self.showGallery)
+ self.ui.galleryList.hide()
+
+ self.ui.imageList.setModel(self.mdl)
+ self.ui.imageList.selectionModel().currentChanged.connect(self.showImageInfo)
+ self.ui.imageList.activated.connect(self.openImage)
+
+ # backendEdit choice
+
+ self.fillBackends()
+ self.ui.backendEdit.currentIndexChanged.connect(self.changeBackend)
+
+ if self.weboob.count_backends() == 0:
+ self.backendsConfig()
+ self.ui.actionBackends.triggered.connect(self.backendsConfig)
+
+ self.lastSaveDir = os.path.expanduser('~')
+
+ @Slot()
+ def backendsConfig(self):
+ cfg = BackendCfg(self.weboob, (CapImage,), self)
+ if cfg.run():
+ self.fillBackends()
+
+ def fillBackends(self):
+ model = BackendListModel(self.weboob)
+ model.addBackends(CapGallery, entry_title=True)
+ model.addBackends(CapImage, entry_title=True)
+ self.ui.backendEdit.setModel(model)
+
+ def selectedBackend(self):
+ cap = self.ui.backendEdit.currentData(BackendListModel.RoleCapability)
+ backend = self.ui.backendEdit.currentData(BackendListModel.RoleBackendName)
+ return cap, backend
+
+ def _collectionBackends(self):
+ cap, backend = self.selectedBackend()
+ if backend is not None:
+ return [backend]
+
+ backends = self.weboob.iter_backends(caps=cap)
+ return [b for b in backends if b.has_caps(CapCollection)]
+
+ @Slot()
+ def changeBackend(self):
+ cap, backend = self.selectedBackend()
+
+ if cap is CapImage:
+ res_class = BaseImage
+ else:
+ res_class = BaseGallery
+ self.mdl.setResourceClasses([res_class])
+
+ @Slot(QModelIndex)
+ def showCollection(self, qidx):
+ cap, _ = self.selectedBackend()
+
+ qidx = self.proxy_mdl.mapToSource(qidx)
+ qidx = qidx.sibling(qidx.row(), 0)
+ if cap is CapImage:
+ self.ui.galleryList.hide()
+ self.ui.imageList.setRootIndex(qidx)
+ self.ui.imageList.setEnabled(True)
+ self.ui.imageList.show()
+ else:
+ self.ui.galleryList.show()
+ self.ui.galleryList.setRootIndex(qidx)
+ self.ui.galleryList.setEnabled(True)
+
+ @Slot(QModelIndex)
+ def showGallery(self, qidx):
+ self.ui.imageList.setEnabled(True)
+ self.ui.imageList.show()
+ qidx = qidx.sibling(qidx.row(), 0)
+ self.ui.imageList.setRootIndex(qidx)
+
+ def showNoneItem(self):
+ self.ui.labelTitle.setText('-')
+ self.ui.labelDescription.setText('-')
+ self.ui.labelAuthor.setText('-')
+ self.ui.labelDate.setText('-')
+ self.ui.labelLink.setText('-')
+ self.ui.labelSize.setText('-')
+ self.ui.labelRating.setText('-')
+
+ @Slot(QModelIndex)
+ def showImageInfo(self, qidx):
+ image = qidx.data(self.mdl.RoleObject)
+ if image is None:
+ self.showNoneItem()
+ return
+
+ self.ui.labelTitle.setText(image.title or '')
+ self.ui.labelDescription.setText(image.description or '')
+ self.ui.labelAuthor.setText(image.author or '')
+ if image.size:
+ self.ui.labelSize.setText(size_format(image.size) or '')
+ else:
+ self.ui.labelSize.setText('-')
+ if image.url:
+ self.ui.labelLink.setText('Link' % image.url)
+ else:
+ self.ui.labelLink.setText('')
+
+ @Slot(QModelIndex)
+ def openImage(self, qidx):
+ viewer = Viewer(self.weboob, self)
+ viewer.setData(self.mdl, qidx)
+ viewer.show()
+
+ @Slot()
+ def startSearch(self):
+ pattern = self.ui.searchEdit.text()
+ if not pattern:
+ return
+
+ self.mdl.clear()
+ cap, backend = self.selectedBackend()
+ if cap is CapImage:
+ self.ui.galleryList.hide()
+ self.ui.imageList.setRootIndex(QModelIndex())
+ self.mdl.addRootDo('search_image', pattern, backends=backend)
+ self.ui.imageList.setEnabled(True)
+ self.ui.imageList.show()
+ elif cap is CapGallery:
+ self.ui.imageList.hide()
+ self.ui.galleryList.setRootIndex(QModelIndex())
+ self.mdl.addRootDo('search_galleries', pattern, backends=backend)
+ self.ui.galleryList.setEnabled(True)
+ self.ui.galleryList.show()
+
+ @Slot()
+ def startBrowse(self):
+ self.ui.collectionTree.setEnabled(True)
+ self.ui.galleryList.setEnabled(False)
+ self.ui.imageList.setEnabled(False)
+
+ self.mdl.clear()
+
+ cap, backend = self.selectedBackend()
+ if cap is CapImage:
+ self.ui.galleryList.hide()
+ res_class = BaseImage
+ else:
+ self.ui.galleryList.show()
+ res_class = BaseGallery
+ self.mdl.setResourceClasses([res_class])
+
+ backends = self._collectionBackends()
+ self.mdl.addRootDo('iter_resources', [res_class], [], backends=backends)
diff --git a/weboob/applications/qgalleroob/qgalleroob.py b/weboob/applications/qgalleroob/qgalleroob.py
new file mode 100644
index 0000000000000000000000000000000000000000..512d979ae934103b5d86b995b523d5f15ee24392
--- /dev/null
+++ b/weboob/applications/qgalleroob/qgalleroob.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2016 Vincent A
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.capabilities.gallery import CapGallery
+from weboob.capabilities.image import CapImage
+from weboob.tools.application.qt5 import QtApplication
+from weboob.tools.config.yamlconfig import YamlConfig
+
+from .main_window import MainWindow
+
+
+class QGalleroob(QtApplication):
+ APPNAME = 'qgalleroob'
+ VERSION = '1.1'
+ COPYRIGHT = u'Copyright(C) 2016 Vincent A'
+ DESCRIPTION = "Qt application to view image galleries."
+ SHORT_DESCRIPTION = "search for images"
+ #~ CONFIG = {'queries': {}}
+ #~ STORAGE = {'bookmarks': [], 'read': [], 'notes': {}}
+
+ def main(self, argv):
+ self.load_backends(CapGallery)
+ self.load_backends(CapImage)
+ self.create_storage()
+ self.load_config(klass=YamlConfig)
+
+ main_window = MainWindow(self.config, self.storage, self.weboob)
+ main_window.show()
+ return self.weboob.loop()
diff --git a/weboob/applications/qgalleroob/ui/Makefile b/weboob/applications/qgalleroob/ui/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..85940ca51919cc00f42b9369b58e58d0d9e6fa72
--- /dev/null
+++ b/weboob/applications/qgalleroob/ui/Makefile
@@ -0,0 +1,13 @@
+UI_FILES = $(wildcard *.ui)
+UI_PY_FILES = $(UI_FILES:%.ui=%_ui.py)
+PYUIC = pyuic5
+
+all: $(UI_PY_FILES)
+
+%_ui.py: %.ui
+ $(PYUIC) -o $@ $^
+
+clean:
+ rm -f *.pyc
+ rm -f $(UI_PY_FILES)
+
diff --git a/weboob/applications/qgalleroob/ui/__init__.py b/weboob/applications/qgalleroob/ui/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/weboob/applications/qgalleroob/ui/mainwindow.ui b/weboob/applications/qgalleroob/ui/mainwindow.ui
new file mode 100644
index 0000000000000000000000000000000000000000..d1b30a881add1da31f8d027d523068cdd8cf6c94
--- /dev/null
+++ b/weboob/applications/qgalleroob/ui/mainwindow.ui
@@ -0,0 +1,491 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ QGalleroob
+
+
+
+ -
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 464
+ 472
+
+
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ Qt::ActionsContextMenu
+
+
+ QListView::Static
+
+
+ QListView::LeftToRight
+
+
+ true
+
+
+ QListView::Adjust
+
+
+
+ 256
+ 256
+
+
+
+ QListView::IconMode
+
+
+ true
+
+
+
+
+ QListView::Static
+
+
+ QListView::LeftToRight
+
+
+ true
+
+
+ QListView::Adjust
+
+
+
+ 256
+ 256
+
+
+
+ QListView::IconMode
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ToolBar
+
+
+ false
+
+
+ false
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+ QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable
+
+
+ Info
+
+
+ 2
+
+
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Title
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Description
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Author
+
+
+
+ -
+
+
+ -
+
+
+ Qt::PlainText
+
+
+
+ -
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+ Qt::PlainText
+
+
+
+ -
+
+
+ -
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Date
+
+
+
+ -
+
+
+ -
+
+
+ Qt::PlainText
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Size
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Rating
+
+
+
+ -
+
+
+ -
+
+
+ Qt::PlainText
+
+
+
+ -
+
+
+ -
+
+
+ Qt::PlainText
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ QDockWidget::NoDockWidgetFeatures
+
+
+ Qt::TopDockWidgetArea
+
+
+ 4
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+ Find
+
+
+ 1
+
+
+
+ -
+
+
+ 0
+
+
+
+
+ 0
+ 0
+ 167
+ 399
+
+
+
+ Search
+
+
+
-
+
+
-
+
+
+ Text to search...
+
+
+
+ -
+
+
+ Go
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 196
+ 118
+
+
+
+ Browse
+
+
+ -
+
+
-
+
+
+ Start browsing collections
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+ Backends
+
+
+
+
+ Download gallery...
+
+
+
+
+ Save image...
+
+
+
+
+ Download to CBZ...
+
+
+
+
+
+
diff --git a/weboob/applications/qgalleroob/ui/viewer.ui b/weboob/applications/qgalleroob/ui/viewer.ui
new file mode 100644
index 0000000000000000000000000000000000000000..74ddb76aa82c13657d437c6202016d73cda78930
--- /dev/null
+++ b/weboob/applications/qgalleroob/ui/viewer.ui
@@ -0,0 +1,231 @@
+
+
+ Viewer
+
+
+
+ 0
+ 0
+ 800
+ 600
+
+
+
+ QGalleroob viewer
+
+
+
+ -
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+
+
+ 0
+ 0
+ 780
+ 503
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ |<
+
+
+
+
+
+
+ -
+
+
+ <
+
+
+
+
+
+
+ -
+
+
+ 1/N
+
+
+
+ -
+
+
+ >
+
+
+
+
+
+
+ -
+
+
+ >|
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+ toolBar
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+
+ Save image...
+
+
+
+
+
+
+
+ Original size
+
+
+ G
+
+
+
+
+
+
+
+ Fit to window
+
+
+ F
+
+
+
+
+
+
+
+ Zoom out
+
+
+ -
+
+
+
+
+
+
+
+ Zoom in
+
+
+ +
+
+
+
+
+
+
diff --git a/weboob/applications/qgalleroob/viewer.py b/weboob/applications/qgalleroob/viewer.py
new file mode 100644
index 0000000000000000000000000000000000000000..2824374d2098fc63ebf51aec729cfc083290e222
--- /dev/null
+++ b/weboob/applications/qgalleroob/viewer.py
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2016 Vincent A
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+import os
+import re
+
+from PyQt5.QtWidgets import QFileDialog, QMessageBox
+from PyQt5.QtGui import QPixmap, QImage, QKeySequence
+from PyQt5.QtCore import Qt, pyqtSlot as Slot
+
+from weboob.tools.application.qt5 import QtMainWindow, QtDo
+from weboob.tools.application.qt5.models import ResultModel
+from weboob.capabilities.base import NotLoaded
+
+from .ui.viewer_ui import Ui_Viewer
+
+
+ZOOM_FACTOR, ZOOM_FIT = range(2)
+
+
+class Viewer(QtMainWindow):
+ def __init__(self, weboob, parent=None):
+ super(Viewer, self).__init__(parent)
+ self.ui = Ui_Viewer()
+ self.ui.setupUi(self)
+ self.ui.prevButton.clicked.connect(self.prev)
+ self.ui.nextButton.clicked.connect(self.next)
+ self.ui.firstButton.clicked.connect(self.first)
+ self.ui.lastButton.clicked.connect(self.last)
+ self.ui.actionZoomIn.triggered.connect(self.zoomIn)
+ self.ui.actionZoomOut.triggered.connect(self.zoomOut)
+ self.ui.actionFullSize.triggered.connect(self.zoomFullSize)
+ self.ui.actionFitWindow.triggered.connect(self.zoomFit)
+
+ self.ui.actionSaveImage.setShortcut(QKeySequence.Save)
+ self.ui.actionSaveImage.triggered.connect(self.saveImage)
+ self.ui.actionClose.setShortcut(QKeySequence.Close)
+ self.ui.actionClose.triggered.connect(self.close)
+
+ self.model = None
+ self.current = None
+ self.total = 0
+ self.zoomFactor = 1
+ self.zoomMode = ZOOM_FACTOR
+ self.weboob = weboob
+
+ def setData(self, model, qidx):
+ self.model = model
+ self.current = qidx
+
+ self.model.rowsInserted.connect(self.updatePos)
+ self.model.rowsRemoved.connect(self.updatePos)
+ self.model.rowsInserted.connect(self.updateNavButtons)
+ self.model.rowsRemoved.connect(self.updateNavButtons)
+
+ self.updateImage()
+
+ def updateNavButtons(self):
+ prev = self.current.row() > 0
+ self.ui.prevButton.setEnabled(prev)
+ self.ui.firstButton.setEnabled(prev)
+ next = self.current.row() < self.total - 1
+ self.ui.nextButton.setEnabled(next)
+ self.ui.lastButton.setEnabled(next)
+
+ def updatePos(self):
+ self.total = self.model.rowCount(self.current.parent())
+ self.ui.posLabel.setText('%d / %d' % (self.current.row() + 1, self.total))
+
+ def _gotData(self, obj, qidx):
+ if self.current == qidx:
+ self.pixmap = QPixmap(QImage.fromData(obj.data))
+ self._rebuildImage()
+
+ def updateImage(self):
+ self.updatePos()
+ self.updateNavButtons()
+
+ obj = self.current.data(ResultModel.RoleObject)
+ pixmap = QPixmap()
+
+ if obj.data is NotLoaded:
+ qidx = self.current
+ process = QtDo(self.weboob, lambda r: self._gotData(r, qidx))
+ process.do('fillobj', obj, ['data'])
+ elif obj.data:
+ pixmap = QPixmap(QImage.fromData(obj.data))
+ self.pixmap = pixmap
+
+ self._rebuildImage()
+
+ @Slot()
+ def next(self):
+ new = self.current.sibling(self.current.row() + 1, 0)
+ if not new.isValid():
+ return
+ self.current = new
+ self.updateImage()
+
+ @Slot()
+ def prev(self):
+ if self.current.row() == 0:
+ return
+ self.current = self.current.sibling(self.current.row() - 1, 0)
+ self.updateImage()
+
+ @Slot()
+ def first(self):
+ self.current = self.current.sibling(0, 0)
+ self.updateImage()
+
+ @Slot()
+ def last(self):
+ self.current = self.current.sibling(self.total - 1, 0)
+ self.updateImage()
+
+ @Slot()
+ def zoomIn(self):
+ self.zoomFactor *= 1.25
+ self.zoomMode = ZOOM_FACTOR
+ self._rebuildImage()
+
+ @Slot()
+ def zoomOut(self):
+ self.zoomFactor *= 0.75
+ self.zoomMode = ZOOM_FACTOR
+ self._rebuildImage()
+
+ @Slot()
+ def zoomFullSize(self):
+ self.zoomFactor = 1
+ self.zoomMode = ZOOM_FACTOR
+ self._rebuildImage()
+
+ @Slot()
+ def zoomFit(self):
+ self.zoomMode = ZOOM_FIT
+ self._rebuildImage()
+
+ def resizeEvent(self, ev):
+ super(Viewer, self).resizeEvent(ev)
+ if self.zoomMode == ZOOM_FIT:
+ self._rebuildImage()
+
+ def _rebuildZoom(self):
+ if self.zoomMode == ZOOM_FACTOR:
+ new_width = int(self.pixmap.width() * self.zoomFactor)
+ pixmap = self.pixmap.scaledToWidth(new_width, Qt.SmoothTransformation)
+ else:
+ new_size = self.ui.scrollArea.viewport().size()
+ pixmap = self.pixmap.scaled(new_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
+ self.zoomFactor = pixmap.width() / float(self.pixmap.width())
+ return pixmap
+
+ def _rebuildImage(self):
+ if self.pixmap.isNull():
+ pixmap = self.pixmap
+ else:
+ pixmap = self._rebuildZoom()
+
+ self.ui.view.setPixmap(pixmap)
+
+ @Slot()
+ def saveImage(self):
+ def ext_for_filter(s):
+ return re.match(r'(?:[A-Z]+) \(\*\.([a-z]+)\)$', s).group(1)
+
+ if not self.pixmap:
+ return
+
+ filters = ['PNG (*.png)', 'JPEG (*.jpg)', 'GIF (*.gif)']
+
+ obj = self.current.data(ResultModel.RoleObject)
+ name = '%s.%s' % (obj.title or obj.id or u'', obj.ext or 'png')
+ default = filters[0]
+ for f in filters:
+ if name.endswith(ext_for_filter(f)):
+ default = f
+ filters = ';;'.join(filters)
+
+ target = os.path.join(self.parent().lastSaveDir, name)
+ out, filter = QFileDialog.getSaveFileName(self, 'Save image', target, filters, default)
+ if not out:
+ return
+
+ ext = ext_for_filter(filter)
+
+ self.parent().lastSaveDir = os.path.dirname(out)
+ if not os.path.splitext(out)[1]:
+ out = '%s.%s' % (out, ext)
+
+ if os.path.exists(out):
+ q = self.tr('%s already exists, are you sure you want to replace it?') % out
+ reply = QMessageBox.question(self, self.tr('Overwrite?'), q)
+ if reply == QMessageBox.No:
+ return self.saveImage()
+
+ self.pixmap.save(out, ext.upper())