File indexing completed on 2024-05-12 16:02:30
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org> 0003 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 #include "KoProgressUpdater.h" 0008 0009 #include <QApplication> 0010 #include <QString> 0011 #include <QTimer> 0012 #include <QThread> 0013 #include <QMutex> 0014 #include <QMutexLocker> 0015 0016 #include "KoUpdaterPrivate_p.h" 0017 #include "KoUpdater.h" 0018 #include "KoProgressProxy.h" 0019 0020 #include "kis_signal_compressor.h" 0021 0022 #include <kis_debug.h> 0023 0024 class Q_DECL_HIDDEN KoProgressUpdater::Private 0025 { 0026 public: 0027 0028 Private(KoProgressUpdater *_q, KoProgressProxy *proxy, QPointer<KoUpdater> parentUpdater, Mode _mode) 0029 : q(_q) 0030 , parentProgressProxy(proxy) 0031 , parentUpdater(parentUpdater) 0032 , mode(_mode) 0033 , updateCompressor(new KisSignalCompressor(250, KisSignalCompressor::FIRST_ACTIVE, q)) 0034 , canceled(false) 0035 { 0036 } 0037 0038 KoProgressUpdater *q; 0039 0040 private: 0041 KoProgressProxy *parentProgressProxy; 0042 QPointer<KoUpdater> parentUpdater; 0043 0044 public: 0045 Mode mode; 0046 int currentProgress = 0; 0047 bool isUndefinedState = false; 0048 KisSignalCompressor *updateCompressor; 0049 QList<QPointer<KoUpdaterPrivate> > subtasks; 0050 bool canceled; 0051 int updateInterval = 250; // ms, 4 updates per second should be enough 0052 bool autoNestNames = false; 0053 QString taskName; 0054 int taskMax = 99; 0055 bool isStarted = false; 0056 0057 QMutex mutex; 0058 0059 void updateParentText(); 0060 void clearState(); 0061 0062 KoProgressProxy* progressProxy() { 0063 return parentUpdater ? parentUpdater : parentProgressProxy; 0064 } 0065 }; 0066 0067 // NOTE: do not make the KoProgressUpdater object part of the QObject 0068 // hierarchy. Do not make KoProgressProxy its parent (note that KoProgressProxy 0069 // is not necessarily castable to QObject ). This prevents proper functioning 0070 // of progress reporting in multi-threaded environments. 0071 KoProgressUpdater::KoProgressUpdater(KoProgressProxy *progressProxy, Mode mode) 0072 : d (new Private(this, progressProxy, 0, mode)) 0073 { 0074 KIS_ASSERT_RECOVER_RETURN(progressProxy); 0075 connect(d->updateCompressor, SIGNAL(timeout()), SLOT(updateUi())); 0076 connect(this, SIGNAL(triggerUpdateAsynchronously()), d->updateCompressor, SLOT(start())); 0077 Q_EMIT triggerUpdateAsynchronously(); 0078 } 0079 0080 KoProgressUpdater::KoProgressUpdater(QPointer<KoUpdater> updater) 0081 : d (new Private(this, 0, updater, Unthreaded)) 0082 { 0083 KIS_ASSERT_RECOVER_RETURN(updater); 0084 connect(d->updateCompressor, SIGNAL(timeout()), SLOT(updateUi())); 0085 connect(this, SIGNAL(triggerUpdateAsynchronously()), d->updateCompressor, SLOT(start())); 0086 Q_EMIT triggerUpdateAsynchronously(); 0087 } 0088 0089 KoProgressUpdater::~KoProgressUpdater() 0090 { 0091 if (d->progressProxy()) { 0092 d->progressProxy()->setRange(0, d->taskMax); 0093 d->progressProxy()->setValue(d->progressProxy()->maximum()); 0094 } 0095 0096 // make sure to stop the timer to avoid accessing 0097 // the data we are going to delete right now 0098 d->updateCompressor->stop(); 0099 0100 qDeleteAll(d->subtasks); 0101 d->subtasks.clear(); 0102 0103 delete d; 0104 } 0105 0106 void KoProgressUpdater::start(int range, const QString &text) 0107 { 0108 { 0109 QMutexLocker l(&d->mutex); 0110 d->clearState(); 0111 d->taskName = text; 0112 d->taskMax = range - 1; 0113 d->isStarted = true; 0114 d->currentProgress = 0; 0115 } 0116 0117 Q_EMIT triggerUpdateAsynchronously(); 0118 } 0119 0120 QPointer<KoUpdater> KoProgressUpdater::startSubtask(int weight, 0121 const QString &name, 0122 bool isPersistent) 0123 { 0124 if (!d->isStarted) { 0125 // lazy initialization for intermediate proxies 0126 start(); 0127 } 0128 0129 KoUpdaterPrivate *p = new KoUpdaterPrivate(this, weight, name, isPersistent); 0130 0131 { 0132 QMutexLocker l(&d->mutex); 0133 d->subtasks.append(p); 0134 } 0135 connect(p, SIGNAL(sigUpdated()), SLOT(update())); 0136 0137 QPointer<KoUpdater> updater = p->connectedUpdater(); 0138 0139 Q_EMIT triggerUpdateAsynchronously(); 0140 return updater; 0141 } 0142 0143 void KoProgressUpdater::removePersistentSubtask(QPointer<KoUpdater> updater) 0144 { 0145 { 0146 QMutexLocker l(&d->mutex); 0147 0148 for (auto it = d->subtasks.begin(); it != d->subtasks.end();) { 0149 if ((*it)->connectedUpdater() != updater) { 0150 ++it; 0151 } else { 0152 KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent()); 0153 (*it)->deleteLater(); 0154 it = d->subtasks.erase(it); 0155 break; 0156 } 0157 } 0158 } 0159 0160 Q_EMIT triggerUpdateAsynchronously(); 0161 } 0162 0163 void KoProgressUpdater::cancel() 0164 { 0165 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0166 0167 QList<QPointer<KoUpdaterPrivate> > subtasks; 0168 0169 { 0170 QMutexLocker l(&d->mutex); 0171 subtasks = d->subtasks; 0172 } 0173 0174 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) { 0175 if (!updater) continue; 0176 0177 updater->setProgress(100); 0178 updater->setInterrupted(true); 0179 } 0180 d->canceled = true; 0181 0182 Q_EMIT triggerUpdateAsynchronously(); 0183 } 0184 0185 void KoProgressUpdater::update() 0186 { 0187 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0188 0189 if (d->mode == Unthreaded) { 0190 qApp->processEvents(); 0191 } 0192 0193 d->updateCompressor->start(); 0194 } 0195 0196 void KoProgressUpdater::updateUi() 0197 { 0198 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0199 0200 // This function runs in the app main thread. All the progress 0201 // updates arrive at the KoUpdaterPrivate instances through 0202 // queued connections, so until we relinquish control to the 0203 // event loop, the progress values cannot change, and that 0204 // won't happen until we return from this function (which is 0205 // triggered by a timer) 0206 0207 { 0208 QMutexLocker l(&d->mutex); 0209 0210 if (!d->subtasks.isEmpty()) { 0211 int totalProgress = 0; 0212 int totalWeight = 0; 0213 d->isUndefinedState = false; 0214 0215 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, d->subtasks) { 0216 if (updater->interrupted()) { 0217 d->currentProgress = -1; 0218 break; 0219 } 0220 0221 if (!updater->hasValidRange()) { 0222 totalWeight = 0; 0223 totalProgress = 0; 0224 d->isUndefinedState = true; 0225 break; 0226 } 0227 0228 if (updater->isPersistent() && updater->isCompleted()) { 0229 continue; 0230 } 0231 0232 const int progress = qBound(0, updater->progress(), 100); 0233 totalProgress += progress * updater->weight(); 0234 totalWeight += updater->weight(); 0235 } 0236 0237 const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1; 0238 0239 d->currentProgress = 0240 d->taskMax == 99 ? 0241 progressPercent : 0242 qRound(qreal(progressPercent) * d->taskMax / 99.0); 0243 } 0244 0245 } 0246 0247 if (d->progressProxy()) { 0248 if (!d->isUndefinedState) { 0249 d->progressProxy()->setRange(0, d->taskMax); 0250 0251 if (d->currentProgress == -1) { 0252 d->currentProgress = d->progressProxy()->maximum(); 0253 } 0254 0255 if (d->currentProgress >= d->progressProxy()->maximum()) { 0256 { 0257 QMutexLocker l(&d->mutex); 0258 d->clearState(); 0259 } 0260 d->progressProxy()->setRange(0, d->taskMax); 0261 d->progressProxy()->setValue(d->progressProxy()->maximum()); 0262 } else { 0263 d->progressProxy()->setValue(d->currentProgress); 0264 } 0265 } else { 0266 d->progressProxy()->setRange(0,0); 0267 d->progressProxy()->setValue(0); 0268 } 0269 0270 d->updateParentText(); 0271 } 0272 } 0273 0274 void KoProgressUpdater::Private::updateParentText() 0275 { 0276 if (!progressProxy()) return; 0277 0278 QString actionName = taskName; 0279 0280 if (autoNestNames) { 0281 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) { 0282 0283 if (updater->isPersistent() && updater->isCompleted()) { 0284 continue; 0285 } 0286 0287 if (updater->progress() < 100) { 0288 const QString subTaskName = updater->mergedSubTaskName(); 0289 0290 if (!subTaskName.isEmpty()) { 0291 if (actionName.isEmpty()) { 0292 actionName = subTaskName; 0293 } else { 0294 actionName = QString("%1: %2").arg(actionName).arg(subTaskName); 0295 } 0296 } 0297 break; 0298 } 0299 } 0300 progressProxy()->setAutoNestedName(actionName); 0301 } else { 0302 progressProxy()->setFormat(actionName); 0303 } 0304 0305 } 0306 0307 void KoProgressUpdater::Private::clearState() 0308 { 0309 for (auto it = subtasks.begin(); it != subtasks.end();) { 0310 if (!(*it)->isPersistent()) { 0311 (*it)->deleteLater(); 0312 it = subtasks.erase(it); 0313 } else { 0314 if ((*it)->interrupted()) { 0315 (*it)->setInterrupted(false); 0316 } 0317 ++it; 0318 } 0319 } 0320 0321 canceled = false; 0322 } 0323 0324 bool KoProgressUpdater::interrupted() const 0325 { 0326 return d->canceled; 0327 } 0328 0329 void KoProgressUpdater::setUpdateInterval(int ms) 0330 { 0331 d->updateCompressor->setDelay(ms); 0332 } 0333 0334 int KoProgressUpdater::updateInterval() const 0335 { 0336 return d->updateCompressor->delay(); 0337 } 0338 0339 void KoProgressUpdater::setAutoNestNames(bool value) 0340 { 0341 d->autoNestNames = value; 0342 } 0343 0344 bool KoProgressUpdater::autoNestNames() const 0345 { 0346 return d->autoNestNames; 0347 }