From 9c537340c5f2e3314a5fee96c72f0f5cd562ed37 Mon Sep 17 00:00:00 2001 From: Uwe Kindler Date: Fri, 4 Nov 2022 20:14:04 +0100 Subject: [PATCH] Added a small image viewer to the demo application --- demo/ImageViewer.cpp | 281 +++++++++++++++++++++++++++++++++++ demo/ImageViewer.h | 88 +++++++++++ demo/MainWindow.cpp | 21 +++ demo/RenderWidget.h | 111 ++++++++++++++ demo/demo.pro | 17 ++- demo/demo.qrc | 7 + demo/images/ads_icon.svg | 77 ++++++++++ demo/images/ads_logo.svg | 88 +++++++++++ demo/images/find_in_page.svg | 6 + demo/images/perm_media.svg | 6 + demo/images/zoom_in.svg | 6 + demo/images/zoom_out.svg | 6 + demo/images/zoom_out_map.svg | 6 + 13 files changed, 714 insertions(+), 6 deletions(-) create mode 100644 demo/ImageViewer.cpp create mode 100644 demo/ImageViewer.h create mode 100644 demo/RenderWidget.h create mode 100644 demo/images/ads_icon.svg create mode 100644 demo/images/ads_logo.svg create mode 100644 demo/images/find_in_page.svg create mode 100644 demo/images/perm_media.svg create mode 100644 demo/images/zoom_in.svg create mode 100644 demo/images/zoom_out.svg create mode 100644 demo/images/zoom_out_map.svg diff --git a/demo/ImageViewer.cpp b/demo/ImageViewer.cpp new file mode 100644 index 0000000..43c7d15 --- /dev/null +++ b/demo/ImageViewer.cpp @@ -0,0 +1,281 @@ +//============================================================================ +/// \file ImageViewer.cpp +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Implementation of CImageViewer +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ImageViewer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RenderWidget.h" + +/** + * Private image viewer data + */ +struct ImageViewerPrivate +{ + CImageViewer* _this; + CRenderWidget* RenderWidget;///< renders the image to screen + bool AutoFit;///< automatically fit image to window size on resize events + QSize ImageSize;///< stores the image size to detect image size changes + QPoint MouseMoveStartPos;///< for calculation of mouse move vector + QLabel* ScalingLabel;///< label displays scaling factor + QList OverlayTools;///< list of tool widget to overlay + + ImageViewerPrivate(CImageViewer* _public) : _this(_public) {} +}; + + + +//============================================================================ +CImageViewer::CImageViewer(QWidget *parent) + : Super(parent), + d(new ImageViewerPrivate(this)) +{ + d->AutoFit = true; + d->RenderWidget = new CRenderWidget(this); + + this->setBackgroundRole(QPalette::Light); + this->setAlignment(Qt::AlignCenter); + this->setWidget(d->RenderWidget); + this->createActions(); + this->setMouseTracking(false); // only produce mouse move events if mouse button pressed +} + + +//============================================================================ +CImageViewer::~CImageViewer() +{ + delete d; +} + + +//============================================================================ +bool CImageViewer::loadFile(const QString& fileName) +{ + QImageReader reader(fileName); + reader.setAutoTransform(true); + const QImage newImage = reader.read(); + if (newImage.isNull()) + { + QMessageBox::information(this, QGuiApplication::applicationDisplayName(), + tr("Cannot load %1: %2") + .arg(QDir::toNativeSeparators(fileName), reader.errorString())); + return false; + } + + setImage(newImage); + setWindowFilePath(fileName); + return true; +} + + +//=========================================================================== +void CImageViewer::setImage(const QImage &newImage) +{ + d->RenderWidget->showImage(newImage); + this->adjustDisplaySize(newImage); +} + + +//============================================================================ +void CImageViewer::adjustDisplaySize(const QImage& Image) +{ + if (d->ImageSize == Image.size()) + { + return; + } + d->ImageSize = Image.size(); + if (d->AutoFit) + { + this->fitToWindow(); + } +} + + +//=========================================================================== +static void initializeImageFileDialog(QFileDialog &dialog, QFileDialog::AcceptMode acceptMode) +{ + static bool firstDialog = true; + + if (firstDialog) { + firstDialog = false; + const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + dialog.setDirectory(picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last()); + } + + QStringList mimeTypeFilters; + const QByteArrayList supportedMimeTypes = acceptMode == QFileDialog::AcceptOpen + ? QImageReader::supportedMimeTypes() : QImageWriter::supportedMimeTypes(); + for (const QByteArray &mimeTypeName : supportedMimeTypes) + mimeTypeFilters.append(mimeTypeName); + mimeTypeFilters.sort(); + dialog.setMimeTypeFilters(mimeTypeFilters); + dialog.selectMimeTypeFilter("image/jpeg"); + if (acceptMode == QFileDialog::AcceptSave) + dialog.setDefaultSuffix("jpg"); +} + + +//=========================================================================== +void CImageViewer::open() +{ + QFileDialog dialog(this, tr("Open File")); + initializeImageFileDialog(dialog, QFileDialog::AcceptOpen); + + while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {} +} + + +//=========================================================================== +void CImageViewer::createActions() +{ + QAction* a; + a = new QAction(tr("&Open...")); + a->setIcon(QIcon(":/adsdemo/images/perm_media.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::open); + a->setShortcut(QKeySequence::Open);; + this->addAction(a); + + a = new QAction(tr("Fit on Screen")); + a->setIcon(QIcon(":/adsdemo/images/zoom_out_map.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::fitToWindow); + this->addAction(a); + + a = new QAction(tr("Actual Pixels")); + a->setIcon(QIcon(":/adsdemo/images/find_in_page.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::normalSize); + this->addAction(a); + + a = new QAction(this); + a->setSeparator(true); + this->addAction(a); + + a = new QAction(tr("Zoom In (25%)")); + a->setIcon(QIcon(":/adsdemo/images/zoom_in.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::zoomIn); + this->addAction(a); + + a = new QAction(tr("Zoom Out (25%)")); + a->setIcon(QIcon(":/adsdemo/images/zoom_out.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::zoomOut); + this->addAction(a); + + this->setContextMenuPolicy(Qt::ActionsContextMenu); +} + + +//=========================================================================== +void CImageViewer::zoomIn() +{ + d->AutoFit = false; + d->RenderWidget->zoomIn(); +} + + +//=========================================================================== +void CImageViewer::zoomOut() +{ + d->AutoFit = false; + d->RenderWidget->zoomOut(); +} + + +//=========================================================================== +void CImageViewer::normalSize() +{ + d->AutoFit = false; + d->RenderWidget->normalSize(); +} + + +//=========================================================================== +void CImageViewer::fitToWindow() +{ + d->AutoFit = true; + d->RenderWidget->scaleToSize(this->maximumViewportSize()); +} + + +//============================================================================ +void CImageViewer::resizeEvent(QResizeEvent* ResizeEvent) +{ + Super::resizeEvent(ResizeEvent); + if (d->AutoFit) + { + this->fitToWindow(); + } +} + + +//============================================================================ +void CImageViewer::mousePressEvent(QMouseEvent* Event) +{ + d->RenderWidget->setCursor(Qt::ClosedHandCursor); + d->MouseMoveStartPos = Event->pos(); + Super::mousePressEvent(Event); +} + + +//============================================================================ +void CImageViewer::mouseReleaseEvent(QMouseEvent* Event) +{ + d->RenderWidget->setCursor(Qt::OpenHandCursor); + Super::mouseReleaseEvent(Event); +} + + +//============================================================================ +void CImageViewer::mouseMoveEvent(QMouseEvent* Event) +{ + QPoint MoveVector = Event->pos() - d->MouseMoveStartPos; + d->MouseMoveStartPos = Event->pos(); + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + - MoveVector.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() - MoveVector.y()); +} + + +//============================================================================ +void CImageViewer::wheelEvent(QWheelEvent* Event) +{ + double numDegrees = Event->delta() / 8; + double numSteps = numDegrees / 15; + d->AutoFit = false; + double Zoom; + if (numSteps < 0) + { + Zoom = pow(0.9, 0 - numSteps); + } + else + { + Zoom = pow(1.10, numSteps); + } + d->RenderWidget->zoomByValue(Zoom); +} + +#include "moc_ImageViewer.cpp" +//--------------------------------------------------------------------------- +// EOF ImageViewer.cpp diff --git a/demo/ImageViewer.h b/demo/ImageViewer.h new file mode 100644 index 0000000..37bc8cb --- /dev/null +++ b/demo/ImageViewer.h @@ -0,0 +1,88 @@ +#ifndef ImageViewerH +#define ImageViewerH +//============================================================================ +/// \file ImageViewer.h +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Declaration of CImageViewer +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +QT_BEGIN_NAMESPACE +class QLabel; +QT_END_NAMESPACE + + +struct ImageViewerPrivate; + +/** + * Tiny simple image viewer for showing images in demo + */ +class CImageViewer : public QScrollArea +{ + Q_OBJECT +public: + using Super = QScrollArea; + + explicit CImageViewer(QWidget *parent = nullptr); + virtual ~CImageViewer(); + + bool loadFile(const QString& Filename); + void setImage(const QImage &newImage); + +public Q_SLOTS: + void open(); + void zoomIn(); + void zoomOut(); + void normalSize(); + void fitToWindow(); + +protected: + /** + * @brief Reimplemented from QScrollArea to adjust image scaling if m_AutoFit is + * true. + */ + virtual void resizeEvent(QResizeEvent* ResizeEvent); + + /** + * @brief Handle mouse press events. + */ + virtual void mousePressEvent(QMouseEvent* Event); + + /** + * @brief Handles mouse release events. + */ + virtual void mouseReleaseEvent(QMouseEvent* Event); + + /** + * @brief Handle mouse move events. + */ + virtual void mouseMoveEvent(QMouseEvent* Event); + + /** + * @brief Use mouse wheel to change scaling of the image. + */ + virtual void wheelEvent(QWheelEvent* Event); + +private: + /** + * @brief Create the wiget actions. + */ + void createActions(); + + /** + * @brief Adjust size of render widget in case of image size change. + * @param[in] Image The new image that may have a different image size. + */ + void adjustDisplaySize(const QImage& Image); + + ImageViewerPrivate* d; + friend ImageViewerPrivate; +}; + +//--------------------------------------------------------------------------- +#endif // ImageViewerH diff --git a/demo/MainWindow.cpp b/demo/MainWindow.cpp index 0daed57..bf8a133 100644 --- a/demo/MainWindow.cpp +++ b/demo/MainWindow.cpp @@ -80,6 +80,7 @@ #include "DockComponentsFactory.h" #include "StatusDialog.h" #include "DockSplitter.h" +#include "ImageViewer.h" /** @@ -317,6 +318,22 @@ struct MainWindowPrivate return DockWidget; } + /** + * Creates a simply image viewr + */ + ads::CDockWidget* createImageViewer() + { + static int ImageViewerCount = 0; + auto w = new CImageViewer(); + auto Result = w->loadFile(":adsdemo/images/ads_logo.svg"); + qDebug() << "loadFile result: " << Result; + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Image Viewer %1").arg(ImageViewerCount++)); + DockWidget->setWidget(w,ads:: CDockWidget::ForceNoScrollArea); + auto ToolBar = DockWidget->createDefaultToolBar(); + ToolBar->addActions(w->actions()); + return DockWidget; + } + /** * Create a table widget */ @@ -510,6 +527,10 @@ void MainWindowPrivate::createContent() _this->connect(DockWidget, SIGNAL(viewToggled(bool)), SLOT(onViewToggled(bool))); _this->connect(DockWidget, SIGNAL(visibilityChanged(bool)), SLOT(onViewVisibilityChanged(bool))); } + + // Create image viewer + DockWidget = createImageViewer(); + DockManager->addDockWidget(ads::LeftDockWidgetArea, DockWidget); } diff --git a/demo/RenderWidget.h b/demo/RenderWidget.h new file mode 100644 index 0000000..81fedf1 --- /dev/null +++ b/demo/RenderWidget.h @@ -0,0 +1,111 @@ +#ifndef RenderWidgetH +#define RenderWidgetH +//============================================================================ +/// \file RenderWidget.h +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Declaration of CRenderWidget +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include + +//============================================================================ +// FORWARD DECLARATIONS +//============================================================================ +class QImage; + + +/** + * @brief Widget for fast display of images (i.e. for video capture devices) + */ +class CRenderWidget : public QWidget +{ + Q_OBJECT +private: + QPixmap m_Image; + double m_ScaleFactor; + +protected: + /** + * @brief Reimplemented paint event method showing actual image. + */ + void paintEvent(QPaintEvent* PaintEvent); + + /** + * @brief Change scale factor + */ + void scaleImage(double ScaleFactor); + + /** + * @brief Adjust widget size to size of image. + */ + void adjustWidgetSize(); + +public: + /** + * Constructor + * @param[in] Parent Parent widget. + */ + CRenderWidget(QWidget* Parent); + + /** + * Destructor + */ + virtual ~CRenderWidget(); + +signals: + /** + * @brief Signalize change of captured image size. + * @param ImageSize New image size. + */ + void imageSizeChanged(const QSize& ImageSize); + +public slots: + /** + * @brief Show new image in render widget. + */ + void showImage(const QImage& Image); + + /** + * @brief Zoom into the scene. + * This function decreases the scaling factor by setting it to the previous + * value in internal scaling list. + * @brief Steps The number of steps to zoom in. One step is 25%. + */ + void zoomIn(); + + /** + * @brief Zoom out of the scene. + * This function decreases the scaling factor by setting it to the next + * value in internal scaling list. + * @brief Steps The number of steps to zoom out. One step is 25%. + */ + void zoomOut(); + + /** + * @brief Change zoom by zoom value. + * @param[in] ZoomValue This is the zoom value to apply. A value of 1 + * means no change a value > 1 increases the image (i.e. 1.25 would increase + * the image by 25%) and a value of < 1 decreases the image size (i.e. + * a value of 0.8 would decrease the image size by 25%). + */ + void zoomByValue(double ZoomValue); + + /** + * @brief Resets the actual scaling to 1 and display the image with its + * actual pixel size. + */ + void normalSize(); + + /** + * @brief Scales the wiget and its content image to the given TargetSize + */ + void scaleToSize(const QSize& TargetSize); +}; // class CRenderWidget + +//--------------------------------------------------------------------------- +#endif // RenderWidgetH diff --git a/demo/demo.pro b/demo/demo.pro index 80ad866..b330fd9 100644 --- a/demo/demo.pro +++ b/demo/demo.pro @@ -2,7 +2,7 @@ ADS_OUT_ROOT = $${OUT_PWD}/.. TARGET = AdvancedDockingSystemDemo DESTDIR = $${ADS_OUT_ROOT}/lib -QT += core gui widgets +QT += core gui widgets svg include(../ads.pri) @@ -20,14 +20,19 @@ adsBuildStatic { DEFINES += ADS_STATIC } -SOURCES += \ - main.cpp \ - MainWindow.cpp \ - StatusDialog.cpp HEADERS += \ MainWindow.h \ - StatusDialog.h + StatusDialog.h \ + ImageViewer.h \ + RenderWidget.h + +SOURCES += \ + main.cpp \ + MainWindow.cpp \ + StatusDialog.cpp \ + ImageViewer.cpp \ + RenderWidget.cpp FORMS += \ mainwindow.ui \ diff --git a/demo/demo.qrc b/demo/demo.qrc index 7dc097b..3139b20 100644 --- a/demo/demo.qrc +++ b/demo/demo.qrc @@ -19,5 +19,12 @@ images/docked_editor.svg res/visual_studio_light.css images/color_lens.svg + images/ads_icon.svg + images/ads_logo.svg + images/find_in_page.svg + images/perm_media.svg + images/zoom_in.svg + images/zoom_out.svg + images/zoom_out_map.svg diff --git a/demo/images/ads_icon.svg b/demo/images/ads_icon.svg new file mode 100644 index 0000000..4231be6 --- /dev/null +++ b/demo/images/ads_icon.svg @@ -0,0 +1,77 @@ + + + + + electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH + + + + + + + + + diff --git a/demo/images/ads_logo.svg b/demo/images/ads_logo.svg new file mode 100644 index 0000000..1072540 --- /dev/null +++ b/demo/images/ads_logo.svg @@ -0,0 +1,88 @@ + + + + + electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH + + Qt Advanced Docking + + + + + + + + diff --git a/demo/images/find_in_page.svg b/demo/images/find_in_page.svg new file mode 100644 index 0000000..ff4e346 --- /dev/null +++ b/demo/images/find_in_page.svg @@ -0,0 +1,6 @@ + + find_in_page icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/perm_media.svg b/demo/images/perm_media.svg new file mode 100644 index 0000000..2dac12f --- /dev/null +++ b/demo/images/perm_media.svg @@ -0,0 +1,6 @@ + + perm_media icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_in.svg b/demo/images/zoom_in.svg new file mode 100644 index 0000000..b65ca3c --- /dev/null +++ b/demo/images/zoom_in.svg @@ -0,0 +1,6 @@ + + zoom_in icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_out.svg b/demo/images/zoom_out.svg new file mode 100644 index 0000000..d291238 --- /dev/null +++ b/demo/images/zoom_out.svg @@ -0,0 +1,6 @@ + + zoom_out icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_out_map.svg b/demo/images/zoom_out_map.svg new file mode 100644 index 0000000..dedc2d5 --- /dev/null +++ b/demo/images/zoom_out_map.svg @@ -0,0 +1,6 @@ + + zoom_out_map icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file