aboutsummaryrefslogtreecommitdiffstats
path: root/examples/widgets
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-02-25 14:14:51 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-03-02 07:43:44 +0100
commit55126cb936f5861a20817d9e2f8a9ab6a9fe92ff (patch)
tree2008f0878a6c02ee5b18abbaf5b9d15edc7d1c72 /examples/widgets
parente4dd58289618132ffeb11a25aad6ae6aa171f1f9 (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.py313
-rw-r--r--examples/widgets/imageviewer/imageviewer.pyproject3
-rw-r--r--examples/widgets/imageviewer/main.py64
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_())