File indexing completed on 2024-04-28 04:18:53

0001 /*
0002 Gwenview: an image viewer
0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0004 
0005 This program is free software; you can redistribute it and/or
0006 modify it under the terms of the GNU General Public License
0007 as published by the Free Software Foundation; either version 2
0008 of the License, or (at your option) any later version.
0009 
0010 This program is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 GNU General Public License for more details.
0014 
0015 You should have received a copy of the GNU General Public License
0016 along with this program; if not, write to the Free Software
0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018 
0019 */
0020 #include "slideshow.h"
0021 
0022 // STL
0023 #include <algorithm>
0024 #include <ctime>
0025 #include <random>
0026 
0027 // Qt
0028 #include <QAction>
0029 #include <QTimer>
0030 
0031 // KF
0032 #include <KLocalizedString>
0033 
0034 // Local
0035 #include "gwenview_lib_debug.h"
0036 #include <gwenviewconfig.h>
0037 #include <lib/gvdebug.h>
0038 
0039 namespace Gwenview
0040 {
0041 #undef ENABLE_LOG
0042 #undef LOG
0043 // #define ENABLE_LOG
0044 #ifdef ENABLE_LOG
0045 #define LOG(x) qCDebug(GWENVIEW_LIB_LOG) << x
0046 #else
0047 #define LOG(x) ;
0048 #endif
0049 
0050 enum State {
0051     Paused,
0052     Started,
0053     WaitForEndOfUrl,
0054 };
0055 
0056 struct SlideShowPrivate {
0057     QTimer *mTimer = nullptr;
0058     State mState;
0059     QVector<QUrl> mUrls;
0060     QVector<QUrl> mShuffledUrls;
0061     QVector<QUrl>::ConstIterator mStartIt;
0062     QUrl mCurrentUrl;
0063     QUrl mLastShuffledUrl;
0064 
0065     QAction *mLoopAction = nullptr;
0066     QAction *mRandomAction = nullptr;
0067 
0068     QUrl findNextUrl()
0069     {
0070         if (GwenviewConfig::random()) {
0071             return findNextRandomUrl();
0072         } else {
0073             return findNextOrderedUrl();
0074         }
0075     }
0076 
0077     QUrl findNextOrderedUrl()
0078     {
0079         QVector<QUrl>::ConstIterator it = std::find(mUrls.constBegin(), mUrls.constEnd(), mCurrentUrl);
0080         GV_RETURN_VALUE_IF_FAIL2(it != mUrls.constEnd(), QUrl(), "Current url not found in list.");
0081 
0082         ++it;
0083         if (GwenviewConfig::loop()) {
0084             // Looping, if we reach the end, start again
0085             if (it == mUrls.constEnd()) {
0086                 it = mUrls.constBegin();
0087             }
0088         } else {
0089             // Not looping, have we reached the end?
0090             // FIXME: stopAtEnd
0091             if (/*(it==mUrls.end() && GwenviewConfig::stopAtEnd()) ||*/ it == mStartIt) {
0092                 it = mUrls.constEnd();
0093             }
0094         }
0095 
0096         if (it != mUrls.constEnd()) {
0097             return *it;
0098         } else {
0099             return {};
0100         }
0101     }
0102 
0103     void initShuffledUrls()
0104     {
0105         mShuffledUrls = mUrls;
0106         std::random_device rd;
0107         std::mt19937 generator(rd());
0108         std::shuffle(mShuffledUrls.begin(), mShuffledUrls.end(), generator);
0109         // Ensure the first url is different from the previous last one, so that
0110         // last url does not stay visible twice longer than usual
0111         if (mLastShuffledUrl == mShuffledUrls.first() && mShuffledUrls.count() > 1) {
0112             std::swap(mShuffledUrls[0], mShuffledUrls[1]);
0113         }
0114         mLastShuffledUrl = mShuffledUrls.last();
0115     }
0116 
0117     QUrl findNextRandomUrl()
0118     {
0119         if (mShuffledUrls.empty()) {
0120             if (GwenviewConfig::loop()) {
0121                 initShuffledUrls();
0122             } else {
0123                 return {};
0124             }
0125         }
0126 
0127         const QUrl url = mShuffledUrls.last();
0128         mShuffledUrls.pop_back();
0129 
0130         return url;
0131     }
0132 
0133     void updateTimerInterval()
0134     {
0135         mTimer->setInterval(int(GwenviewConfig::interval() * 1000));
0136     }
0137 
0138     void doStart()
0139     {
0140         if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) {
0141             LOG("mState = WaitForEndOfUrl");
0142             // Just in case
0143             mTimer->stop();
0144             mState = WaitForEndOfUrl;
0145         } else {
0146             LOG("mState = Started");
0147             mTimer->start();
0148             mState = Started;
0149         }
0150     }
0151 };
0152 
0153 SlideShow::SlideShow(QObject *parent)
0154     : QObject(parent)
0155     , d(new SlideShowPrivate)
0156 {
0157     d->mState = Paused;
0158 
0159     d->mTimer = new QTimer(this);
0160     connect(d->mTimer, &QTimer::timeout, this, &SlideShow::goToNextUrl);
0161 
0162     d->mLoopAction = new QAction(this);
0163     d->mLoopAction->setText(i18nc("@item:inmenu toggle loop in slideshow", "Loop"));
0164     d->mLoopAction->setCheckable(true);
0165     connect(d->mLoopAction, &QAction::triggered, this, &SlideShow::updateConfig);
0166 
0167     d->mRandomAction = new QAction(this);
0168     d->mRandomAction->setText(i18nc("@item:inmenu toggle random order in slideshow", "Random"));
0169     d->mRandomAction->setCheckable(true);
0170     connect(d->mRandomAction, &QAction::toggled, this, &SlideShow::slotRandomActionToggled);
0171     connect(d->mRandomAction, &QAction::triggered, this, &SlideShow::updateConfig);
0172 
0173     d->mLoopAction->setChecked(GwenviewConfig::loop());
0174     d->mRandomAction->setChecked(GwenviewConfig::random());
0175 }
0176 
0177 SlideShow::~SlideShow()
0178 {
0179     GwenviewConfig::self()->save();
0180     delete d;
0181 }
0182 
0183 QAction *SlideShow::loopAction() const
0184 {
0185     return d->mLoopAction;
0186 }
0187 
0188 QAction *SlideShow::randomAction() const
0189 {
0190     return d->mRandomAction;
0191 }
0192 
0193 void SlideShow::start(const QList<QUrl> &urls)
0194 {
0195     d->mUrls.resize(urls.size());
0196     std::copy(urls.begin(), urls.end(), d->mUrls.begin());
0197 
0198     d->mStartIt = std::find(d->mUrls.constBegin(), d->mUrls.constEnd(), d->mCurrentUrl);
0199     if (d->mStartIt == d->mUrls.constEnd()) {
0200         qCWarning(GWENVIEW_LIB_LOG) << "Current url not found in list, aborting.\n";
0201         return;
0202     }
0203 
0204     if (GwenviewConfig::random()) {
0205         d->initShuffledUrls();
0206     }
0207 
0208     d->updateTimerInterval();
0209     d->mTimer->setSingleShot(false);
0210     d->doStart();
0211     Q_EMIT stateChanged(true);
0212 }
0213 
0214 void SlideShow::setInterval(int intervalInSeconds)
0215 {
0216     GwenviewConfig::setInterval(double(intervalInSeconds));
0217     d->updateTimerInterval();
0218     Q_EMIT intervalChanged(intervalInSeconds);
0219 }
0220 
0221 int SlideShow::interval() const
0222 {
0223     return GwenviewConfig::interval();
0224 }
0225 
0226 int SlideShow::position() const
0227 {
0228     // TODO: also support videos
0229 
0230     // QTimer::remainingTime() returns -1 if inactive
0231     // and there are moments where mState == Started but timer already done but not yet next url reached
0232     // so handle that
0233     if (d->mState == Started) {
0234         if (d->mTimer->isActive()) {
0235             return interval() * 1000 - d->mTimer->remainingTime();
0236         }
0237         // already timeout reached, but not yet progressed to next url
0238         return interval();
0239     }
0240 
0241     return 0;
0242 }
0243 
0244 void SlideShow::pause()
0245 {
0246     LOG("Stopping timer");
0247     d->mTimer->stop();
0248     d->mState = Paused;
0249     Q_EMIT stateChanged(false);
0250 }
0251 
0252 void SlideShow::resumeAndGoToNextUrl()
0253 {
0254     LOG("");
0255     if (d->mState == WaitForEndOfUrl) {
0256         goToNextUrl();
0257     }
0258 }
0259 
0260 void SlideShow::goToNextUrl()
0261 {
0262     LOG("");
0263     const QUrl url = d->findNextUrl();
0264     LOG("url:" << url);
0265     if (!url.isValid()) {
0266         pause();
0267         return;
0268     }
0269     Q_EMIT goToUrl(url);
0270 }
0271 
0272 void SlideShow::setCurrentUrl(const QUrl &url)
0273 {
0274     LOG(url);
0275     if (d->mCurrentUrl == url) {
0276         return;
0277     }
0278     d->mCurrentUrl = url;
0279     // Restart timer to avoid showing new url for the remaining time of the old
0280     // url
0281     if (d->mState != Paused) {
0282         d->doStart();
0283     }
0284 }
0285 
0286 bool SlideShow::isRunning() const
0287 {
0288     return d->mState != Paused;
0289 }
0290 
0291 void SlideShow::updateConfig()
0292 {
0293     GwenviewConfig::setLoop(d->mLoopAction->isChecked());
0294     GwenviewConfig::setRandom(d->mRandomAction->isChecked());
0295 }
0296 
0297 void SlideShow::slotRandomActionToggled(bool on)
0298 {
0299     if (on && d->mState != Paused) {
0300         d->initShuffledUrls();
0301     }
0302 }
0303 
0304 } // namespace
0305 
0306 #include "moc_slideshow.cpp"