File indexing completed on 2024-05-05 04:19:17

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "imageopscontextmanageritem.h"
0023 
0024 // Qt
0025 #include <QAction>
0026 #include <QApplication>
0027 #include <QRect>
0028 
0029 // KF
0030 #include <KActionCategory>
0031 #include <KActionCollection>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 
0035 // Local
0036 #include "config-gwenview.h"
0037 #include "gwenview_app_debug.h"
0038 #include "mainwindow.h"
0039 #include "sidebar.h"
0040 #include "viewmainpage.h"
0041 #ifdef KIMAGEANNOTATOR_FOUND
0042 #include <lib/annotate/annotatedialog.h>
0043 #include <lib/annotate/annotateoperation.h>
0044 #endif
0045 #include <lib/bcg/bcgtool.h>
0046 #include <lib/contextmanager.h>
0047 #include <lib/crop/croptool.h>
0048 #include <lib/document/documentfactory.h>
0049 #include <lib/documentview/rasterimageview.h>
0050 #include <lib/eventwatcher.h>
0051 #include <lib/gwenviewconfig.h>
0052 #include <lib/redeyereduction/redeyereductiontool.h>
0053 #include <lib/resize/resizeimagedialog.h>
0054 #include <lib/resize/resizeimageoperation.h>
0055 #include <lib/transformimageoperation.h>
0056 
0057 namespace Gwenview
0058 {
0059 #undef ENABLE_LOG
0060 #undef LOG
0061 // #define ENABLE_LOG
0062 #ifdef ENABLE_LOG
0063 #define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
0064 #else
0065 #define LOG(x) ;
0066 #endif
0067 
0068 struct ImageOpsContextManagerItem::Private {
0069     ImageOpsContextManagerItem *q = nullptr;
0070     MainWindow *mMainWindow = nullptr;
0071     SideBarGroup *mGroup = nullptr;
0072     QRect *mCropStateRect = nullptr;
0073 
0074     QAction *mRotateLeftAction = nullptr;
0075     QAction *mRotateRightAction = nullptr;
0076     QAction *mMirrorAction = nullptr;
0077     QAction *mFlipAction = nullptr;
0078     QAction *mResizeAction = nullptr;
0079     QAction *mCropAction = nullptr;
0080     QAction *mBCGAction = nullptr;
0081     QAction *mRedEyeReductionAction = nullptr;
0082 #ifdef KIMAGEANNOTATOR_FOUND
0083     QAction *mAnnotateAction = nullptr;
0084 #endif
0085     QList<QAction *> mActionList;
0086 
0087     void setupActions()
0088     {
0089         KActionCollection *actionCollection = mMainWindow->actionCollection();
0090         auto edit = new KActionCategory(i18nc("@title actions category - means actions changing image", "Edit"), actionCollection);
0091 
0092         mRotateLeftAction = edit->addAction(QStringLiteral("rotate_left"), q, SLOT(rotateLeft()));
0093         mRotateLeftAction->setText(i18n("Rotate Left"));
0094         mRotateLeftAction->setToolTip(i18nc("@info:tooltip", "Rotate image to the left"));
0095         mRotateLeftAction->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-left")));
0096         actionCollection->setDefaultShortcut(mRotateLeftAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R);
0097 
0098         mRotateRightAction = edit->addAction(QStringLiteral("rotate_right"), q, SLOT(rotateRight()));
0099         mRotateRightAction->setText(i18n("Rotate Right"));
0100         mRotateRightAction->setToolTip(i18nc("@info:tooltip", "Rotate image to the right"));
0101         mRotateRightAction->setIcon(QIcon::fromTheme(QStringLiteral("object-rotate-right")));
0102         actionCollection->setDefaultShortcut(mRotateRightAction, Qt::CTRL | Qt::Key_R);
0103 
0104         mMirrorAction = edit->addAction(QStringLiteral("mirror"), q, SLOT(mirror()));
0105         mMirrorAction->setText(i18n("Mirror"));
0106         mMirrorAction->setIcon(QIcon::fromTheme(QStringLiteral("object-flip-horizontal")));
0107 
0108         mFlipAction = edit->addAction(QStringLiteral("flip"), q, SLOT(flip()));
0109         mFlipAction->setText(i18n("Flip"));
0110         mFlipAction->setIcon(QIcon::fromTheme(QStringLiteral("object-flip-vertical")));
0111 
0112         mResizeAction = edit->addAction(QStringLiteral("resize"), q, SLOT(resizeImage()));
0113         mResizeAction->setText(i18n("Resize"));
0114         mResizeAction->setIcon(QIcon::fromTheme(QStringLiteral("transform-scale")));
0115         actionCollection->setDefaultShortcut(mResizeAction, Qt::SHIFT | Qt::Key_R);
0116 
0117         mCropAction = edit->addAction(QStringLiteral("crop"), q, SLOT(crop()));
0118         mCropAction->setText(i18n("Crop"));
0119         mCropAction->setIcon(QIcon::fromTheme(QStringLiteral("transform-crop-and-resize")));
0120         actionCollection->setDefaultShortcut(mCropAction, Qt::SHIFT | Qt::Key_C);
0121 
0122         mBCGAction = edit->addAction(QStringLiteral("brightness_contrast_gamma"), q, SLOT(startBCG()));
0123         mBCGAction->setText(i18nc("@action:intoolbar", "Adjust Colors"));
0124         mBCGAction->setIcon(QIcon::fromTheme(QStringLiteral("contrast")));
0125         actionCollection->setDefaultShortcut(mBCGAction, Qt::SHIFT | Qt::Key_B);
0126 
0127         mRedEyeReductionAction = edit->addAction(QStringLiteral("red_eye_reduction"), q, SLOT(startRedEyeReduction()));
0128         mRedEyeReductionAction->setText(i18n("Reduce Red Eye"));
0129         mRedEyeReductionAction->setIcon(QIcon::fromTheme(QStringLiteral("redeyes")));
0130         actionCollection->setDefaultShortcut(mRedEyeReductionAction, Qt::SHIFT | Qt::Key_E);
0131 #ifdef KIMAGEANNOTATOR_FOUND
0132         mAnnotateAction = edit->addAction(QStringLiteral("annotate"));
0133         mAnnotateAction->setText(i18nc("@action:intoolbar", "Annotate"));
0134         mAnnotateAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-image"), QIcon::fromTheme(QStringLiteral("draw-brush"))));
0135         actionCollection->setDefaultShortcut(mAnnotateAction, Qt::SHIFT | Qt::Key_A);
0136         connect(mAnnotateAction, &QAction::triggered, q, [this]() {
0137             Document::Ptr doc = DocumentFactory::instance()->load(q->contextManager()->currentUrl());
0138             doc->waitUntilLoaded();
0139 
0140             AnnotateDialog dialog(mMainWindow);
0141             dialog.setWindowTitle(i18nc("@title:window Annotate [filename]", "Annotate %1", doc->url().fileName()));
0142             dialog.setImage(doc->image());
0143             if (dialog.exec() == QDialog::Accepted) {
0144                 q->applyImageOperation(new AnnotateOperation(dialog.getImage()));
0145             }
0146         });
0147 #endif
0148         mActionList << mRotateLeftAction << mRotateRightAction << mMirrorAction << mFlipAction << mResizeAction << mCropAction << mBCGAction
0149                     << mRedEyeReductionAction;
0150 #ifdef KIMAGEANNOTATOR_FOUND
0151         mActionList << mAnnotateAction;
0152 #endif
0153     }
0154 
0155     bool ensureEditable()
0156     {
0157         const QUrl url = q->contextManager()->currentUrl();
0158         Document::Ptr doc = DocumentFactory::instance()->load(url);
0159         doc->waitUntilLoaded();
0160         if (doc->isEditable()) {
0161             return true;
0162         }
0163 
0164         KMessageBox::error(QApplication::activeWindow(), i18nc("@info", "Gwenview cannot edit this kind of image."));
0165         return false;
0166     }
0167 };
0168 
0169 ImageOpsContextManagerItem::ImageOpsContextManagerItem(ContextManager *manager, MainWindow *mainWindow)
0170     : AbstractContextManagerItem(manager)
0171     , d(new Private)
0172 {
0173     d->q = this;
0174     d->mMainWindow = mainWindow;
0175     d->mGroup = new SideBarGroup(i18n("Image Operations"));
0176     setWidget(d->mGroup);
0177     EventWatcher::install(d->mGroup, QEvent::Show, this, SLOT(updateSideBarContent()));
0178     d->mCropStateRect = new QRect;
0179     d->setupActions();
0180     updateActions();
0181     connect(contextManager(), &ContextManager::selectionChanged, this, &ImageOpsContextManagerItem::updateActions);
0182     connect(mainWindow, &MainWindow::viewModeChanged, this, &ImageOpsContextManagerItem::updateActions);
0183     connect(mainWindow->viewMainPage(), &ViewMainPage::completed, this, &ImageOpsContextManagerItem::updateActions);
0184 }
0185 
0186 ImageOpsContextManagerItem::~ImageOpsContextManagerItem()
0187 {
0188     delete d->mCropStateRect;
0189     delete d;
0190 }
0191 
0192 void ImageOpsContextManagerItem::updateSideBarContent()
0193 {
0194     if (!d->mGroup->isVisible()) {
0195         return;
0196     }
0197 
0198     d->mGroup->clear();
0199     for (QAction *action : qAsConst(d->mActionList)) {
0200         if (action->isEnabled() && action->priority() != QAction::LowPriority) {
0201             d->mGroup->addAction(action);
0202         }
0203     }
0204 }
0205 
0206 void ImageOpsContextManagerItem::updateActions()
0207 {
0208     bool canModify = contextManager()->currentUrlIsRasterImage();
0209     bool viewMainPageIsVisible = d->mMainWindow->viewMainPage()->isVisible();
0210     if (!viewMainPageIsVisible) {
0211         // Since we only support image operations on one image for now,
0212         // disable actions if several images are selected and the document
0213         // view is not visible.
0214         if (contextManager()->selectedFileItemList().count() != 1) {
0215             canModify = false;
0216         }
0217     }
0218 
0219     d->mRotateLeftAction->setEnabled(canModify);
0220     d->mRotateRightAction->setEnabled(canModify);
0221     d->mMirrorAction->setEnabled(canModify);
0222     d->mFlipAction->setEnabled(canModify);
0223     d->mResizeAction->setEnabled(canModify);
0224     d->mCropAction->setEnabled(canModify && viewMainPageIsVisible);
0225     d->mBCGAction->setEnabled(canModify && viewMainPageIsVisible);
0226     d->mRedEyeReductionAction->setEnabled(canModify && viewMainPageIsVisible);
0227 #ifdef KIMAGEANNOTATOR_FOUND
0228     d->mAnnotateAction->setEnabled(canModify);
0229 #endif
0230     updateSideBarContent();
0231 }
0232 
0233 void ImageOpsContextManagerItem::rotateLeft()
0234 {
0235     auto op = new TransformImageOperation(ROT_270);
0236     applyImageOperation(op);
0237 }
0238 
0239 void ImageOpsContextManagerItem::rotateRight()
0240 {
0241     auto op = new TransformImageOperation(ROT_90);
0242     applyImageOperation(op);
0243 }
0244 
0245 void ImageOpsContextManagerItem::mirror()
0246 {
0247     auto op = new TransformImageOperation(HFLIP);
0248     applyImageOperation(op);
0249 }
0250 
0251 void ImageOpsContextManagerItem::flip()
0252 {
0253     auto op = new TransformImageOperation(VFLIP);
0254     applyImageOperation(op);
0255 }
0256 
0257 void ImageOpsContextManagerItem::resizeImage()
0258 {
0259     if (!d->ensureEditable()) {
0260         return;
0261     }
0262     Document::Ptr doc = DocumentFactory::instance()->load(contextManager()->currentUrl());
0263     doc->startLoadingFullImage();
0264 
0265     auto dialog = new ResizeImageDialog(d->mMainWindow);
0266     dialog->setAttribute(Qt::WA_DeleteOnClose);
0267     dialog->setModal(true);
0268     dialog->setOriginalSize(doc->size());
0269     dialog->setCurrentImageUrl(doc->url());
0270     connect(dialog, &QDialog::accepted, this, [this, dialog]() {
0271         applyImageOperation(new ResizeImageOperation(dialog->size()));
0272     });
0273 
0274     dialog->show();
0275 }
0276 
0277 void ImageOpsContextManagerItem::crop()
0278 {
0279     if (!d->ensureEditable()) {
0280         return;
0281     }
0282     RasterImageView *imageView = d->mMainWindow->viewMainPage()->imageView();
0283     if (!imageView) {
0284         qCCritical(GWENVIEW_APP_LOG) << "No ImageView available!";
0285         return;
0286     }
0287 
0288     auto tool = new CropTool(imageView);
0289     Document::Ptr doc = DocumentFactory::instance()->load(contextManager()->currentUrl());
0290     QSize size = doc->size();
0291     QRect sizeAsRect = QRect(0, 0, size.width(), size.height());
0292 
0293     if (!d->mCropStateRect->isNull() && sizeAsRect.contains(*d->mCropStateRect)) {
0294         tool->setRect(*d->mCropStateRect);
0295     }
0296 
0297     connect(tool, &CropTool::imageOperationRequested, this, &ImageOpsContextManagerItem::applyImageOperation);
0298     connect(tool, &CropTool::rectReset, this, [this]() {
0299         this->resetCropState();
0300     });
0301     connect(tool, &CropTool::done, this, [this, tool]() {
0302         this->d->mCropStateRect->setTopLeft(tool->rect().topLeft());
0303         this->d->mCropStateRect->setSize(tool->rect().size());
0304         this->restoreDefaultImageViewTool();
0305     });
0306 
0307     d->mMainWindow->setDistractionFreeMode(true);
0308     imageView->setCurrentTool(tool);
0309 }
0310 
0311 void ImageOpsContextManagerItem::resetCropState()
0312 {
0313     // Set the rect to null (see QRect::isNull())
0314     d->mCropStateRect->setRect(0, 0, -1, -1);
0315 }
0316 
0317 void ImageOpsContextManagerItem::startRedEyeReduction()
0318 {
0319     if (!d->ensureEditable()) {
0320         return;
0321     }
0322     RasterImageView *view = d->mMainWindow->viewMainPage()->imageView();
0323     if (!view) {
0324         qCCritical(GWENVIEW_APP_LOG) << "No RasterImageView available!";
0325         return;
0326     }
0327     auto tool = new RedEyeReductionTool(view);
0328     connect(tool, &RedEyeReductionTool::imageOperationRequested, this, &ImageOpsContextManagerItem::applyImageOperation);
0329     connect(tool, &RedEyeReductionTool::done, this, &ImageOpsContextManagerItem::restoreDefaultImageViewTool);
0330 
0331     d->mMainWindow->setDistractionFreeMode(true);
0332     view->setCurrentTool(tool);
0333 }
0334 
0335 void ImageOpsContextManagerItem::applyImageOperation(AbstractImageOperation *op)
0336 {
0337     // For now, we only support operations on one image
0338     QUrl url = contextManager()->currentUrl();
0339 
0340     Document::Ptr doc = DocumentFactory::instance()->load(url);
0341     op->applyToDocument(doc);
0342 }
0343 
0344 void ImageOpsContextManagerItem::restoreDefaultImageViewTool()
0345 {
0346     RasterImageView *imageView = d->mMainWindow->viewMainPage()->imageView();
0347     if (!imageView) {
0348         qCCritical(GWENVIEW_APP_LOG) << "No RasterImageView available!";
0349         return;
0350     }
0351 
0352     AbstractRasterImageViewTool *tool = imageView->currentTool();
0353     imageView->setCurrentTool(nullptr);
0354     tool->deleteLater();
0355     d->mMainWindow->setDistractionFreeMode(false);
0356 }
0357 
0358 void ImageOpsContextManagerItem::startBCG()
0359 {
0360     if (!d->ensureEditable()) {
0361         return;
0362     }
0363     RasterImageView *view = d->mMainWindow->viewMainPage()->imageView();
0364     if (!view) {
0365         qCCritical(GWENVIEW_APP_LOG) << "No RasterImageView available!";
0366         return;
0367     }
0368     auto tool = new BCGTool(view);
0369     connect(tool, &BCGTool::imageOperationRequested, this, &ImageOpsContextManagerItem::applyImageOperation);
0370     connect(tool, &BCGTool::done, this, &ImageOpsContextManagerItem::restoreDefaultImageViewTool);
0371 
0372     d->mMainWindow->setDistractionFreeMode(true);
0373     view->setCurrentTool(tool);
0374 }
0375 
0376 } // namespace
0377 
0378 #include "moc_imageopscontextmanageritem.cpp"