diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-02-25 14:14:51 +0100 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2021-03-02 07:43:44 +0100 |
commit | 55126cb936f5861a20817d9e2f8a9ab6a9fe92ff (patch) | |
tree | 2008f0878a6c02ee5b18abbaf5b9d15edc7d1c72 /examples/widgets | |
parent | e4dd58289618132ffeb11a25aad6ae6aa171f1f9 (diff) |
Port the imageviewer example
Task-number: PYSIDE-841
Change-Id: Icb833db3bf18e6943b0579a3a3a064024d57e084
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Diffstat (limited to 'examples/widgets')
-rw-r--r-- | examples/widgets/imageviewer/imageviewer.py | 313 | ||||
-rw-r--r-- | examples/widgets/imageviewer/imageviewer.pyproject | 3 | ||||
-rw-r--r-- | examples/widgets/imageviewer/main.py | 64 |
3 files changed, 380 insertions, 0 deletions
diff --git a/examples/widgets/imageviewer/imageviewer.py b/examples/widgets/imageviewer/imageviewer.py new file mode 100644 index 000000000..b18c365af --- /dev/null +++ b/examples/widgets/imageviewer/imageviewer.py @@ -0,0 +1,313 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +from PySide6.QtPrintSupport import QPrintDialog, QPrinter +from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog, QLabel, + QMainWindow, QMenuBar, QMessageBox, QScrollArea, + QScrollBar, QSizePolicy, QStatusBar) +from PySide6.QtGui import (QAction, QClipboard, QColorSpace, QGuiApplication, + QImage, QImageReader, QImageWriter, QKeySequence, + QPalette, QPainter, QPixmap, QScreen) +from PySide6.QtCore import QDir, QMimeData, QStandardPaths, Qt, Slot + + +ABOUT = """<p>The <b>Image Viewer</b> example shows how to combine QLabel +and QScrollArea to display an image. QLabel is typically used +for displaying a text, but it can also display an image. +QScrollArea provides a scrolling view around another widget. +If the child widget exceeds the size of the frame, QScrollArea +automatically provides scroll bars. </p><p>The example +demonstrates how QLabel's ability to scale its contents +(QLabel.scaledContents), and QScrollArea's ability to +automatically resize its contents +(QScrollArea.widgetResizable), can be used to implement +zooming and scaling features. </p><p>In addition the example +shows how to use QPainter to print an image.</p> +""" + + +class ImageViewer(QMainWindow): + def __init__(self, parent=None): + super(ImageViewer, self).__init__(parent) + self._scale_factor = 1.0 + self._first_file_dialog = True + self._image_label = QLabel() + self._image_label.setBackgroundRole(QPalette.Base) + self._image_label.setSizePolicy(QSizePolicy.Ignored, + QSizePolicy.Ignored) + self._image_label.setScaledContents(True) + + self._scroll_area = QScrollArea() + self._scroll_area.setBackgroundRole(QPalette.Dark) + self._scroll_area.setWidget(self._image_label) + self._scroll_area.setVisible(False) + self.setCentralWidget(self._scroll_area) + + self._create_actions() + + self.resize(QGuiApplication.primaryScreen().availableSize() * 3 / 5) + + def load_file(self, fileName): + reader = QImageReader(fileName) + reader.setAutoTransform(True) + new_image = reader.read() + native_filename = QDir.toNativeSeparators(fileName) + if new_image.isNull(): + error = reader.errorString() + QMessageBox.information(self, QGuiApplication.applicationDisplayName(), + f"Cannot load {native_filename}: {error}") + return False + self._set_image(new_image) + self.setWindowFilePath(fileName) + + w = self._image.width() + h = self._image.height() + d = self._image.depth() + message = f'Opened "{native_filename}", {w}x{h}, Depth: {d}' + self.statusBar().showMessage(message) + return True + + def _set_image(self, new_image): + self._image = new_image + if self._image.colorSpace().isValid(): + self._image.convertToColorSpace(QColorSpace.SRgb) + self._image_label.setPixmap(QPixmap.fromImage(self._image)) + self._scale_factor = 1.0 + + self._scroll_area.setVisible(True) + self._print_act.setEnabled(True) + self._fit_to_window_act.setEnabled(True) + self._update_actions() + + if not self._fit_to_window_act.isChecked(): + self._image_label.adjustSize() + + def _save_file(self, fileName): + writer = QImageWriter(fileName) + + native_filename = QDir.toNativeSeparators(fileName) + if not writer.write(self._image): + error = writer.errorString() + message = f"Cannot write {native_filename}: {error}" + QMessageBox.information(self, QGuiApplication.applicationDisplayName(), + message) + return False + self.statusBar().showMessage(f'Wrote "{native_filename}"') + return True + + @Slot() + def _open(self): + dialog = QFileDialog(self, "Open File") + self._initialize_image_filedialog(dialog, QFileDialog.AcceptOpen) + while (dialog.exec_() == QDialog.Accepted + and not self.load_file(dialog.selectedFiles()[0])): + pass + + @Slot() + def _save_as(self): + dialog = QFileDialog(self, "Save File As") + self._initialize_image_filedialog(dialog, QFileDialog.AcceptSave) + while (dialog.exec_() == QDialog.Accepted + and not self._save_file(dialog.selectedFiles()[0])): + pass + + @Slot() + def _print_(self): + printer = QPrinter() + dialog = QPrintDialog(printer, self) + if dialog.exec_() == QDialog.Accepted: + painter = QPainter(printer) + pixmap = self._image_label.pixmap() + rect = painter.viewport() + size = pixmap.size() + size.scale(rect.size(), Qt.KeepAspectRatio) + painter.setViewport(rect.x(), rect.y(), size.width(), size.height()) + painter.setWindow(pixmap.rect()) + painter.drawPixmap(0, 0, pixmap) + painter.end() + + @Slot() + def _copy(self): + QGuiApplication.clipboard().setImage(self._image) + + @Slot() + def _paste(self): + new_image = QGuiApplication.clipboard().image() + if new_image.isNull(): + self.statusBar().showMessage("No image in clipboard") + else: + self._set_image(new_image) + self.setWindowFilePath('') + w = new_image.width() + h = new_image.height() + d = new_image.depth() + message = f"Obtained image from clipboard, {w}x{h}, Depth: {d}" + self.statusBar().showMessage(message) + + @Slot() + def _zoom_in(self): + self._scale_image(1.25) + + @Slot() + def _zoom_out(self): + self._scale_image(0.8) + + @Slot() + def _normal_size(self): + self._image_label.adjustSize() + self._scale_factor = 1.0 + + @Slot() + def _fit_to_window(self): + fit_to_window = self._fit_to_window_act.isChecked() + self._scroll_area.setWidgetResizable(fit_to_window) + if not fit_to_window: + self._normal_size() + self._update_actions() + + @Slot() + def _about(self): + QMessageBox.about(self, "About Image Viewer", ABOUT) + + def _create_actions(self): + file_menu = self.menuBar().addMenu("&File") + + self._open_act = file_menu.addAction("&Open...") + self._open_act.triggered.connect(self._open) + self._open_act.setShortcut(QKeySequence.Open) + + self._save_as_act = file_menu.addAction("&Save As...") + self._save_as_act.triggered.connect(self._save_as) + self._save_as_act.setEnabled(False) + + self._print_act = file_menu.addAction("&Print...") + self._print_act.triggered.connect(self._print_) + self._print_act.setShortcut(QKeySequence.Print) + self._print_act.setEnabled(False) + + file_menu.addSeparator() + + self._exit_act = file_menu.addAction("E&xit") + self._exit_act.triggered.connect(self.close) + self._exit_act.setShortcut("Ctrl+Q") + + edit_menu = self.menuBar().addMenu("&Edit") + + self._copy_act = edit_menu.addAction("&Copy") + self._copy_act.triggered.connect(self._copy) + self._copy_act.setShortcut(QKeySequence.Copy) + self._copy_act.setEnabled(False) + + self._paste_act = edit_menu.addAction("&Paste") + self._paste_act.triggered.connect(self._paste) + self._paste_act.setShortcut(QKeySequence.Paste) + + view_menu = self.menuBar().addMenu("&View") + + self._zoom_in_act = view_menu.addAction("Zoom &In (25%)") + self._zoom_in_act.setShortcut(QKeySequence.ZoomIn) + self._zoom_in_act.triggered.connect(self._zoom_in) + self._zoom_in_act.setEnabled(False) + + self._zoom_out_act = view_menu.addAction("Zoom &Out (25%)") + self._zoom_out_act.triggered.connect(self._zoom_out) + self._zoom_out_act.setShortcut(QKeySequence.ZoomOut) + self._zoom_out_act.setEnabled(False) + + self._normal_size_act = view_menu.addAction("&Normal Size") + self._normal_size_act.triggered.connect(self._normal_size) + self._normal_size_act.setShortcut("Ctrl+S") + self._normal_size_act.setEnabled(False) + + view_menu.addSeparator() + + self._fit_to_window_act = view_menu.addAction("&Fit to Window") + self._fit_to_window_act.triggered.connect(self._fit_to_window) + self._fit_to_window_act.setEnabled(False) + self._fit_to_window_act.setCheckable(True) + self._fit_to_window_act.setShortcut("Ctrl+F") + + help_menu = self.menuBar().addMenu("&Help") + + about_act = help_menu.addAction("&About") + about_act.triggered.connect(self._about) + about_qt_act = help_menu.addAction("About &Qt") + about_qt_act.triggered.connect(QApplication.aboutQt) + + def _update_actions(self): + has_image = not self._image.isNull() + self._save_as_act.setEnabled(has_image) + self._copy_act.setEnabled(has_image) + enable_zoom = not self._fit_to_window_act.isChecked() + self._zoom_in_act.setEnabled(enable_zoom) + self._zoom_out_act.setEnabled(enable_zoom) + self._normal_size_act.setEnabled(enable_zoom) + + def _scale_image(self, factor): + self._scale_factor *= factor + new_size = self._scale_factor * self._image_label.pixmap().size() + self._image_label.resize(new_size) + + self._adjust_scrollbar(self._scroll_area.horizontalScrollBar(), factor) + self._adjust_scrollbar(self._scroll_area.verticalScrollBar(), factor) + + self._zoom_in_act.setEnabled(self._scale_factor < 3.0) + self._zoom_out_act.setEnabled(self._scale_factor > 0.333) + + def _adjust_scrollbar(self, scrollBar, factor): + pos = int(factor * scrollBar.value() + + ((factor - 1) * scrollBar.pageStep() / 2)) + scrollBar.setValue(pos) + + def _initialize_image_filedialog(self, dialog, acceptMode): + if self._first_file_dialog: + self._first_file_dialog = False + locations = QStandardPaths.standardLocations(QStandardPaths.PicturesLocation) + directory = locations[-1] if locations else QDir.currentPath() + dialog.setDirectory(directory) + + mime_types = [m.data().decode('utf-8') for m in QImageWriter.supportedMimeTypes()] + mime_types.sort() + + dialog.setMimeTypeFilters(mime_types) + dialog.selectMimeTypeFilter("image/jpeg") + dialog.setAcceptMode(acceptMode) + if acceptMode == QFileDialog.AcceptSave: + dialog.setDefaultSuffix("jpg") diff --git a/examples/widgets/imageviewer/imageviewer.pyproject b/examples/widgets/imageviewer/imageviewer.pyproject new file mode 100644 index 000000000..d2db1ffbb --- /dev/null +++ b/examples/widgets/imageviewer/imageviewer.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "imageviewer.py"] +} diff --git a/examples/widgets/imageviewer/main.py b/examples/widgets/imageviewer/main.py new file mode 100644 index 000000000..3fc729ddb --- /dev/null +++ b/examples/widgets/imageviewer/main.py @@ -0,0 +1,64 @@ +############################################################################# +## +## Copyright (C) 2021 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Qt for Python examples of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:BSD$ +## You may use this file under the terms of the BSD license as follows: +## +## "Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are +## met: +## * Redistributions of source code must retain the above copyright +## notice, this list of conditions and the following disclaimer. +## * Redistributions in binary form must reproduce the above copyright +## notice, this list of conditions and the following disclaimer in +## the documentation and/or other materials provided with the +## distribution. +## * Neither the name of The Qt Company Ltd nor the names of its +## contributors may be used to endorse or promote products derived +## from this software without specific prior written permission. +## +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +## +## $QT_END_LICENSE$ +## +############################################################################# + +"""PySide6 port of the widgets/imageviewer example from Qt v6.0""" + +from argparse import ArgumentParser, RawTextHelpFormatter +import sys + +from PySide6.QtWidgets import (QApplication) + +from imageviewer import ImageViewer + + +if __name__ == '__main__': + arg_parser = ArgumentParser(description="Image Viewer", + formatter_class=RawTextHelpFormatter) + arg_parser.add_argument('file', type=str, nargs='?', help='Image file') + args = arg_parser.parse_args() + + app = QApplication(sys.argv) + image_viewer = ImageViewer() + + if args.file and not image_viewer.load_file(args.file): + sys.exit(-1) + + image_viewer.show() + sys.exit(app.exec_()) |