diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index b3dafd2aa8..91ebaa3c9d 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1066,6 +1066,7 @@ SET(View_CPP_SRCS MDIViewPy.cpp MDIViewPyWrap.cpp GraphvizView.cpp + ImageView.cpp ActiveObjectList.cpp ) SET(View_HPP_SRCS @@ -1073,6 +1074,7 @@ SET(View_HPP_SRCS MDIViewPy.h MDIViewPyWrap.h GraphvizView.h + ImageView.h ActiveObjectList.h ) SET(View_SRCS diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 89ef0a83c5..fdd5ed11e4 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -36,6 +36,7 @@ # include # include # include +# include # include # include # include @@ -62,6 +63,7 @@ #include "DlgSettingsImageImp.h" #include "Document.h" #include "FileDialog.h" +#include "ImageView.h" #include "Macro.h" #include "MainWindow.h" #include "NavigationStyle.h" @@ -1807,12 +1809,12 @@ StdViewScreenShot::StdViewScreenShot() : Command("Std_ViewScreenShot") { sGroup = "Standard-View"; - sMenuText = QT_TR_NOOP("Save picture..."); + sMenuText = QT_TR_NOOP("Save image..."); sToolTipText= QT_TR_NOOP("Creates a screenshot of the active view"); sWhatsThis = "Std_ViewScreenShot"; sStatusTip = QT_TR_NOOP("Creates a screenshot of the active view"); sPixmap = "camera-photo"; - eType = Alter3DView; + eType = Alter3DView; } void StdViewScreenShot::activated(int iMsg) @@ -1969,6 +1971,51 @@ bool StdViewScreenShot::isActive() return isViewOfType(Gui::View3DInventor::getClassTypeId()); } +//=========================================================================== +// Std_ViewLoadImage +//=========================================================================== +DEF_STD_CMD(StdViewLoadImage) + +StdViewLoadImage::StdViewLoadImage() + : Command("Std_ViewLoadImage") +{ + sGroup = "Standard-View"; + sMenuText = QT_TR_NOOP("Load image..."); + sToolTipText= QT_TR_NOOP("Loads a image"); + sWhatsThis = "Std_ViewLoadPicture"; + sStatusTip = QT_TR_NOOP("Loads a image"); + //sPixmap = "camera-photo"; + eType = 0; +} + +void StdViewLoadImage::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + // add all supported QImage formats + QStringList mimeTypeFilters; + QList supportedMimeTypes = QImageReader::supportedMimeTypes(); + for (const auto& mimeTypeName : supportedMimeTypes) { + mimeTypeFilters.append(QString::fromLatin1(mimeTypeName)); + } + + // Reading an image + QFileDialog dialog(Gui::getMainWindow()); + dialog.setWindowTitle(QObject::tr("Choose an image file to open")); + dialog.setMimeTypeFilters(mimeTypeFilters); + dialog.selectMimeTypeFilter(QString::fromLatin1("image/png")); + dialog.setDefaultSuffix(QString::fromLatin1("png")); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setOption(QFileDialog::DontUseNativeDialog); + + if (dialog.exec()) { + QString fileName = dialog.selectedFiles().constFirst(); + ImageView* view = new ImageView(Gui::getMainWindow()); + view->loadFile(fileName); + view->resize(400, 300); + Gui::getMainWindow()->addWindow(view); + } +} //=========================================================================== // Std_ViewCreate @@ -3775,6 +3822,7 @@ void CreateViewStdCommands() rcCmdMgr.addCommand(new StdCmdViewCreate()); rcCmdMgr.addCommand(new StdViewScreenShot()); + rcCmdMgr.addCommand(new StdViewLoadImage()); rcCmdMgr.addCommand(new StdMainFullscreen()); rcCmdMgr.addCommand(new StdViewDockUndockFullscreen()); rcCmdMgr.addCommand(new StdCmdSetAppearance()); diff --git a/src/Gui/ImageView.cpp b/src/Gui/ImageView.cpp new file mode 100644 index 0000000000..42227b450d --- /dev/null +++ b/src/Gui/ImageView.cpp @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2023 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include "ImageView.h" + +using namespace Gui; + +ImageView::ImageView(QWidget* parent) + : MDIView(nullptr, parent) + , imageLabel(new QLabel) + , scrollArea(new QScrollArea) + , scaleFactor{1.0} + , dragging{false} +{ + imageLabel->setBackgroundRole(QPalette::Base); + imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + imageLabel->setScaledContents(true); + + scrollArea->setBackgroundRole(QPalette::Dark); + scrollArea->setWidget(imageLabel); + scrollArea->setVisible(false); + setCentralWidget(scrollArea); + setAcceptDrops(true); +} + +bool ImageView::loadFile(const QString& fileName) +{ + QImageReader reader(fileName); + reader.setAutoTransform(true); + QImage image = reader.read(); + if (image.isNull()) { + QMessageBox::information(this, tr("Failed to load image file"), + tr("Cannot load file %1: %2") + .arg(fileName, reader.errorString())); + return false; + } + + setImage(image); + setWindowFilePath(fileName); + + return true; +} + +void ImageView::setImage(const QImage& image) +{ + rawImage = image; + imageLabel->setPixmap(QPixmap::fromImage(image)); + imageLabel->adjustSize(); + scrollArea->setVisible(true); + scaleFactor = 1.0; +} + +void ImageView::scaleImage(double factor) +{ + scaleFactor *= factor; +#if QT_VERSION >= QT_VERSION_CHECK(5,15,0) + imageLabel->resize(scaleFactor * imageLabel->pixmap(Qt::ReturnByValue).size()); +#else + imageLabel->resize(scaleFactor * imageLabel->pixmap()->size()); +#endif + + adjustScrollBar(scrollArea->horizontalScrollBar(), factor); + adjustScrollBar(scrollArea->verticalScrollBar(), factor); +} + +void ImageView::adjustScrollBar(QScrollBar *scrollBar, double factor) +{ + scrollBar->setValue(int(factor * scrollBar->value() + + ((factor - 1) * scrollBar->pageStep()/2))); +} + +bool ImageView::canZoomIn() const +{ + int maxWidth{10000}; + return !isFitToWindow() && imageLabel->width() < maxWidth; +} + +bool ImageView::canZoomOut() const +{ + int minWidth{200}; + return !isFitToWindow() && imageLabel->width() > minWidth; +} + +void ImageView::zoomIn() +{ + double scale{1.25}; + scaleImage(scale); +} + +void ImageView::zoomOut() +{ + double scale{0.8}; + scaleImage(scale); +} + +void ImageView::normalSize() +{ + imageLabel->adjustSize(); + scaleFactor = 1.0; +} + +void ImageView::fitToWindow(bool fitView) +{ + scrollArea->setWidgetResizable(fitView); + if (!fitView) { + normalSize(); + } +} + +bool ImageView::isFitToWindow() const +{ + return scrollArea->widgetResizable(); +} + +bool ImageView::canDrag() const +{ + return scrollArea->verticalScrollBar()->isVisible() || + scrollArea->horizontalScrollBar()->isVisible(); +} + +void ImageView::startDrag() +{ + dragging = true; +} + +void ImageView::stopDrag() +{ + dragging = false; +} + +bool ImageView::isDragging() const +{ + return dragging; +} + +void ImageView::contextMenuEvent(QContextMenuEvent* event) +{ + QMenu menu; + QAction* fitToWindowAct = menu.addAction(tr("Fit to window")); + fitToWindowAct->setCheckable(true); + fitToWindowAct->setChecked(isFitToWindow()); + connect(fitToWindowAct, &QAction::toggled, this, &ImageView::fitToWindow); + + QAction* zoomInAct = menu.addAction(tr("Zoom in"), this, &ImageView::zoomIn); + zoomInAct->setEnabled(canZoomIn()); + + QAction* zoomOutAct = menu.addAction(tr("Zoom out"), this, &ImageView::zoomOut); + zoomOutAct->setEnabled(canZoomOut()); + + menu.exec(event->globalPos()); +} + +void ImageView::mousePressEvent(QMouseEvent* event) +{ + if (event->buttons().testFlag(Qt::MiddleButton)) { + if (canDrag()) { + setCursor(QCursor(Qt::ClosedHandCursor)); + startDrag(); + dragPos = event->pos(); + } + } +} + +void ImageView::mouseReleaseEvent(QMouseEvent* event) +{ + if (!event->buttons().testFlag(Qt::MiddleButton)) { + if (isDragging()) { + stopDrag(); + unsetCursor(); + } + } +} + +void ImageView::mouseMoveEvent(QMouseEvent* event) +{ + if (isDragging()) { + QScrollBar* hBar = scrollArea->horizontalScrollBar(); + QScrollBar* vBar = scrollArea->verticalScrollBar(); + QPoint delta = event->pos() - dragPos; + hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x())); + vBar->setValue(vBar->value() - delta.y()); + dragPos = event->pos(); + } +} + +void ImageView::dropEvent(QDropEvent* event) +{ + const QMimeData* data = event->mimeData(); + if (data->hasUrls()) { + loadImageFromUrl(data->urls()); + } + else { + MDIView::dropEvent(event); + } +} + +void ImageView::dragEnterEvent(QDragEnterEvent* event) +{ + const QMimeData* data = event->mimeData(); + if (data->hasUrls()) { + event->accept(); + } + else { + event->ignore(); + } +} + +void ImageView::loadImageFromUrl(const QList& urls) +{ + if (urls.isEmpty()) { + return; + } + + const QUrl& url = urls.first(); + const QFileInfo info(url.toLocalFile()); + if (info.exists() && info.isFile()) { + loadFile(info.absoluteFilePath()); + } +} + +void ImageView::pasteImage() +{ + QImage image = imageFromClipboard(); + if (!image.isNull()) { + setImage(image); + } +} + +bool ImageView::canPasteImage() const +{ + return !imageFromClipboard().isNull(); +} + +QImage ImageView::imageFromClipboard() +{ + QImage image; + if (const QMimeData *mimeData = QApplication::clipboard()->mimeData()) { + if (mimeData->hasImage()) { + image = qvariant_cast(mimeData->imageData()); + } + } + + return image; +} + +void ImageView::print(QPrinter* printer) +{ + QPrintDialog dialog(printer, this); + if (dialog.exec()) { + QPainter painter(printer); + QPixmap pixmap = QPixmap::fromImage(rawImage); + QRect rect = painter.viewport(); + QSize 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); + } +} + +bool ImageView::onMsg(const char* pMsg,const char**) +{ + if (strcmp("ViewFit", pMsg) == 0) { + fitToWindow(true); + return true; + } + if (strcmp("ZoomIn", pMsg) == 0) { + zoomIn(); + return true; + } + if (strcmp("ZoomOut", pMsg) == 0) { + zoomOut(); + return true; + } + if (strcmp("Paste", pMsg) == 0) { + pasteImage(); + return true; + } + if (strcmp("Print", pMsg) == 0) { + print(); + return true; + } + if (strcmp("PrintPreview", pMsg) == 0) { + printPreview(); + return true; + } + if (strcmp("PrintPdf", pMsg) == 0) { + printPdf(); + return true; + } + + return false; +} + +bool ImageView::onHasMsg(const char* pMsg) const +{ + if (strcmp("ViewFit", pMsg) == 0) { + return true; + } + if (strcmp("ZoomIn", pMsg) == 0) { + return canZoomIn(); + } + if (strcmp("ZoomOut", pMsg) == 0) { + return canZoomOut(); + } + if (strcmp("Paste", pMsg) == 0) { + return canPasteImage(); + } + if (strcmp("Print", pMsg) == 0) { + return true; + } + if (strcmp("PrintPreview", pMsg) == 0) { + return true; + } + if (strcmp("PrintPdf", pMsg) == 0) { + return true; + } + + return false; +} + +#include "moc_ImageView.cpp" diff --git a/src/Gui/ImageView.h b/src/Gui/ImageView.h new file mode 100644 index 0000000000..ebada88f33 --- /dev/null +++ b/src/Gui/ImageView.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2023 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef GUI_IMAGE_VIEW_H +#define GUI_IMAGE_VIEW_H + +#include + +class QLabel; +class QScrollArea; +class QScrollBar; +class QUrl; + +namespace Gui { + +class GuiExport ImageView : public MDIView +{ + Q_OBJECT + +public: + explicit ImageView(QWidget* parent); + bool loadFile(const QString &); + + const char *getName() const override { + return "ImageView"; + } + + /// Message handler + bool onMsg(const char* pMsg, const char** ppReturn) override; + /// Message handler test + bool onHasMsg(const char* pMsg) const override; + + /** @name Printing */ + //@{ + using MDIView::print; + void print(QPrinter* printer) override; + //@} + +protected: + void contextMenuEvent(QContextMenuEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void dropEvent(QDropEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + +private: + void setImage(const QImage& image); + void scaleImage(double factor); + static void adjustScrollBar(QScrollBar *scrollBar, double factor); + bool canZoomIn() const; + bool canZoomOut() const; + void zoomIn(); + void zoomOut(); + void normalSize(); + void fitToWindow(bool fitView); + bool isFitToWindow() const; + bool canDrag() const; + void startDrag(); + void stopDrag(); + bool isDragging() const; + void pasteImage(); + bool canPasteImage() const; + static QImage imageFromClipboard(); + void loadImageFromUrl(const QList&); + +private: + QImage rawImage; + QLabel *imageLabel; + QScrollArea *scrollArea; + double scaleFactor; + bool dragging; + QPoint dragPos; +}; + +} + +#endif //GUI_IMAGE_VIEW_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index d7120a6eb5..961e9f4fa2 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -697,6 +697,7 @@ MenuItem* StdWorkbench::setupMenuBar() const *tool << "Std_DlgParameter" << "Separator" << "Std_ViewScreenShot" + << "Std_ViewLoadImage" << "Std_SceneInspector" << "Std_DependencyGraph" << "Std_ProjectUtil"