File indexing completed on 2024-05-12 04:19:39

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2011 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, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "documentviewcontainer.h"
0023 
0024 // Local
0025 #include "gwenview_lib_debug.h"
0026 #include <lib/graphicswidgetfloater.h>
0027 #include <lib/gvdebug.h>
0028 #include <lib/gwenviewconfig.h>
0029 
0030 // KF
0031 
0032 // Qt
0033 #include <QGraphicsScene>
0034 #include <QOpenGLWidget>
0035 #include <QPropertyAnimation>
0036 #include <QTimer>
0037 #include <QtMath>
0038 
0039 namespace Gwenview
0040 {
0041 using DocumentViewSet = QSet<DocumentView *>;
0042 using SetupForUrl = QHash<QUrl, DocumentView::Setup>;
0043 
0044 struct DocumentViewContainerPrivate {
0045     DocumentViewContainer *q = nullptr;
0046     QGraphicsScene *mScene = nullptr;
0047     SetupForUrl mSetupForUrl;
0048     DocumentViewSet mViews;
0049     DocumentViewSet mAddedViews;
0050     DocumentViewSet mRemovedViews;
0051     QTimer *mLayoutUpdateTimer = nullptr;
0052 
0053     void scheduleLayoutUpdate()
0054     {
0055         mLayoutUpdateTimer->start();
0056     }
0057 
0058     /**
0059      * Remove view from set, move it to mRemovedViews so that it is later
0060      * deleted.
0061      */
0062     bool removeFromSet(DocumentView *view, DocumentViewSet *set)
0063     {
0064         DocumentViewSet::Iterator it = set->find(view);
0065         if (it == set->end()) {
0066             return false;
0067         }
0068         set->erase(it);
0069         mRemovedViews << view;
0070         scheduleLayoutUpdate();
0071         return true;
0072     }
0073 
0074     void resetSet(DocumentViewSet *set)
0075     {
0076         qDeleteAll(*set);
0077         set->clear();
0078     }
0079 };
0080 
0081 DocumentViewContainer::DocumentViewContainer(QWidget *parent)
0082     : QGraphicsView(parent)
0083     , d(new DocumentViewContainerPrivate)
0084 {
0085     d->q = this;
0086     d->mScene = new QGraphicsScene(this);
0087 #ifndef QT_NO_OPENGL
0088     if (GwenviewConfig::animationMethod() == DocumentView::GLAnimation) {
0089         auto glWidget = new QOpenGLWidget;
0090         setViewport(glWidget);
0091     }
0092 #endif
0093     setScene(d->mScene);
0094     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0095 
0096     setFrameStyle(QFrame::NoFrame);
0097     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0098     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0099 
0100     d->mLayoutUpdateTimer = new QTimer(this);
0101     d->mLayoutUpdateTimer->setInterval(0);
0102     d->mLayoutUpdateTimer->setSingleShot(true);
0103     connect(d->mLayoutUpdateTimer, &QTimer::timeout, this, &DocumentViewContainer::updateLayout);
0104 
0105     connect(GwenviewConfig::self(), &GwenviewConfig::configChanged, this, &DocumentViewContainer::slotConfigChanged);
0106 }
0107 
0108 DocumentViewContainer::~DocumentViewContainer()
0109 {
0110     delete d;
0111 }
0112 
0113 DocumentView *DocumentViewContainer::createView()
0114 {
0115     auto view = new DocumentView(d->mScene);
0116     view->setPalette(palette());
0117     d->mAddedViews << view;
0118     view->show();
0119     connect(view, &DocumentView::fadeInFinished, this, &DocumentViewContainer::slotFadeInFinished);
0120     d->scheduleLayoutUpdate();
0121 
0122     if (GwenviewConfig::animationMethod() == DocumentView::NoAnimation) {
0123         setUpdatesEnabled(false);
0124         connect(view, &DocumentView::completed, this, [this]() {
0125             setUpdatesEnabled(true);
0126         });
0127         connect(view, &DocumentView::indicateLoadingToUser, this, [this]() {
0128             setUpdatesEnabled(true);
0129         });
0130     }
0131 
0132     return view;
0133 }
0134 
0135 void DocumentViewContainer::deleteView(DocumentView *view)
0136 {
0137     if (d->removeFromSet(view, &d->mViews)) {
0138         return;
0139     }
0140     d->removeFromSet(view, &d->mAddedViews);
0141 }
0142 
0143 DocumentView::Setup DocumentViewContainer::savedSetup(const QUrl &url) const
0144 {
0145     return d->mSetupForUrl.value(url);
0146 }
0147 
0148 void DocumentViewContainer::updateSetup(DocumentView *view)
0149 {
0150     d->mSetupForUrl[view->url()] = view->setup();
0151 }
0152 
0153 void DocumentViewContainer::reset()
0154 {
0155     d->resetSet(&d->mViews);
0156     d->resetSet(&d->mAddedViews);
0157     d->resetSet(&d->mRemovedViews);
0158 }
0159 
0160 void DocumentViewContainer::showEvent(QShowEvent *event)
0161 {
0162     QWidget::showEvent(event);
0163     updateLayout();
0164 }
0165 
0166 void DocumentViewContainer::resizeEvent(QResizeEvent *event)
0167 {
0168     QWidget::resizeEvent(event);
0169     d->mScene->setSceneRect(rect());
0170     updateLayout();
0171 }
0172 
0173 static bool viewLessThan(DocumentView *v1, DocumentView *v2)
0174 {
0175     return v1->sortKey() < v2->sortKey();
0176 }
0177 
0178 void DocumentViewContainer::updateLayout()
0179 {
0180     // Stop update timer: this is useful if updateLayout() is called directly
0181     // and not through scheduleLayoutUpdate()
0182     d->mLayoutUpdateTimer->stop();
0183     QList<DocumentView *> views = (d->mViews | d->mAddedViews).values();
0184     std::sort(views.begin(), views.end(), viewLessThan);
0185 
0186     bool animated = GwenviewConfig::animationMethod() != DocumentView::NoAnimation;
0187     bool crossFade = d->mAddedViews.count() == 1 && d->mRemovedViews.count() == 1;
0188 
0189     if (animated && crossFade) {
0190         DocumentView *oldView = *d->mRemovedViews.begin();
0191         DocumentView *newView = *d->mAddedViews.begin();
0192 
0193         newView->setGeometry(rect());
0194         QPropertyAnimation *anim = newView->fadeIn();
0195 
0196         oldView->setZValue(-1);
0197         connect(anim, &QPropertyAnimation::finished, oldView, &DocumentView::hideAndDeleteLater);
0198         d->mRemovedViews.clear();
0199 
0200         return;
0201     }
0202 
0203     if (!views.isEmpty()) {
0204         // Compute column count
0205         int colCount;
0206         switch (views.count()) {
0207         case 1:
0208             colCount = 1;
0209             break;
0210         case 2:
0211             colCount = 2;
0212             break;
0213         case 3:
0214             colCount = 3;
0215             break;
0216         case 4:
0217             colCount = 2;
0218             break;
0219         case 5:
0220             colCount = 3;
0221             break;
0222         case 6:
0223             colCount = 3;
0224             break;
0225         default:
0226             colCount = 3;
0227             break;
0228         }
0229 
0230         int rowCount = qCeil(views.count() / qreal(colCount));
0231         Q_ASSERT(rowCount > 0);
0232         int viewWidth = width() / colCount;
0233         int viewHeight = height() / rowCount;
0234 
0235         int col = 0;
0236         int row = 0;
0237 
0238         for (DocumentView *view : qAsConst(views)) {
0239             QRect rect;
0240             rect.setLeft(col * viewWidth);
0241             rect.setTop(row * viewHeight);
0242             rect.setWidth(viewWidth);
0243             rect.setHeight(viewHeight);
0244 
0245             if (animated) {
0246                 if (d->mViews.contains(view)) {
0247                     if (rect != view->geometry()) {
0248                         if (d->mAddedViews.isEmpty() && d->mRemovedViews.isEmpty()) {
0249                             // View moves because of a resize
0250                             view->moveTo(rect);
0251                         } else {
0252                             // View moves because the number of views changed,
0253                             // animate the change
0254                             view->moveToAnimated(rect);
0255                         }
0256                     }
0257                 } else {
0258                     view->setGeometry(rect);
0259                     view->fadeIn();
0260                 }
0261             } else {
0262                 // Not animated, set final geometry and opacity now
0263                 view->setGeometry(rect);
0264                 view->setGraphicsEffectOpacity(1);
0265             }
0266 
0267             ++col;
0268             if (col == colCount) {
0269                 col = 0;
0270                 ++row;
0271             }
0272         }
0273     }
0274 
0275     // Handle removed views
0276     if (animated) {
0277         for (DocumentView *view : qAsConst(d->mRemovedViews)) {
0278             view->fadeOut();
0279             QTimer::singleShot(DocumentView::AnimDuration, view, &QObject::deleteLater);
0280         }
0281     } else {
0282         for (DocumentView *view : qAsConst(d->mRemovedViews)) {
0283             view->deleteLater();
0284         }
0285         QMetaObject::invokeMethod(this, &DocumentViewContainer::pretendFadeInFinished, Qt::QueuedConnection);
0286     }
0287     d->mRemovedViews.clear();
0288 }
0289 
0290 void DocumentViewContainer::pretendFadeInFinished()
0291 {
0292     // Animations are disabled. Pretend all fade ins are finished so that added
0293     // views are moved to mViews, will modify d->mAddedViews
0294     const auto currentViews = d->mAddedViews;
0295     for (DocumentView *view : currentViews) {
0296         slotFadeInFinished(view);
0297     }
0298 }
0299 
0300 void DocumentViewContainer::slotFadeInFinished(DocumentView *view)
0301 {
0302     if (!d->mAddedViews.contains(view)) {
0303         // This can happen if user goes to next image then quickly goes to the
0304         // next one before the animation is finished.
0305         return;
0306     }
0307     d->mAddedViews.remove(view);
0308     d->mViews.insert(view);
0309 }
0310 
0311 void DocumentViewContainer::slotConfigChanged()
0312 {
0313 #ifndef QT_NO_OPENGL
0314     bool currentlyGL = qobject_cast<QOpenGLWidget *>(viewport());
0315     bool wantGL = GwenviewConfig::animationMethod() == DocumentView::GLAnimation;
0316     if (currentlyGL != wantGL) {
0317         setViewport(wantGL ? new QOpenGLWidget() : new QWidget());
0318     }
0319 #endif
0320 }
0321 
0322 void DocumentViewContainer::showMessageWidget(QGraphicsWidget *widget, Qt::Alignment align)
0323 {
0324     DocumentView *view = nullptr;
0325     if (d->mViews.isEmpty()) {
0326         GV_RETURN_IF_FAIL(!d->mAddedViews.isEmpty());
0327         view = *d->mAddedViews.begin();
0328     } else {
0329         view = *d->mViews.begin();
0330     }
0331     GV_RETURN_IF_FAIL(view);
0332 
0333     widget->setParentItem(view);
0334     auto floater = new GraphicsWidgetFloater(view);
0335     floater->setChildWidget(widget);
0336     floater->setAlignment(align);
0337     widget->show();
0338     widget->setZValue(1);
0339 }
0340 
0341 void DocumentViewContainer::applyPalette(const QPalette &palette)
0342 {
0343     setPalette(palette);
0344     for (DocumentView *view : d->mViews | d->mAddedViews) {
0345         view->setPalette(palette);
0346     }
0347 }
0348 
0349 } // namespace
0350 
0351 #include "moc_documentviewcontainer.cpp"