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"