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"