File indexing completed on 2024-05-19 04:29:54
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(weight, name, isPersistent); 0130 0131 { 0132 QMutexLocker l(&d->mutex); 0133 d->subtasks.append(p); 0134 } 0135 connect(p, SIGNAL(sigUpdated()), SLOT(update())); 0136 connect(p, SIGNAL(sigCancelled()), SLOT(cancel())); 0137 0138 QPointer<KoUpdater> updater = p->connectedUpdater(); 0139 0140 Q_EMIT triggerUpdateAsynchronously(); 0141 return updater; 0142 } 0143 0144 void KoProgressUpdater::removePersistentSubtask(QPointer<KoUpdater> updater) 0145 { 0146 { 0147 QMutexLocker l(&d->mutex); 0148 0149 for (auto it = d->subtasks.begin(); it != d->subtasks.end();) { 0150 if ((*it)->connectedUpdater() != updater) { 0151 ++it; 0152 } else { 0153 KIS_SAFE_ASSERT_RECOVER_NOOP((*it)->isPersistent()); 0154 (*it)->deleteLater(); 0155 it = d->subtasks.erase(it); 0156 break; 0157 } 0158 } 0159 } 0160 0161 Q_EMIT triggerUpdateAsynchronously(); 0162 } 0163 0164 void KoProgressUpdater::cancel() 0165 { 0166 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0167 0168 QList<QPointer<KoUpdaterPrivate> > subtasks; 0169 0170 { 0171 QMutexLocker l(&d->mutex); 0172 subtasks = d->subtasks; 0173 } 0174 0175 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) { 0176 if (!updater) continue; 0177 0178 updater->setProgress(100); 0179 updater->setInterrupted(true); 0180 } 0181 d->canceled = true; 0182 0183 Q_EMIT triggerUpdateAsynchronously(); 0184 } 0185 0186 void KoProgressUpdater::update() 0187 { 0188 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0189 0190 if (d->mode == Unthreaded) { 0191 qApp->processEvents(); 0192 } 0193 0194 d->updateCompressor->start(); 0195 } 0196 0197 void KoProgressUpdater::updateUi() 0198 { 0199 KIS_SAFE_ASSERT_RECOVER_RETURN(QThread::currentThread() == this->thread()); 0200 0201 // This function runs in the app main thread. All the progress 0202 // updates arrive at the KoUpdaterPrivate instances through 0203 // queued connections, so until we relinquish control to the 0204 // event loop, the progress values cannot change, and that 0205 // won't happen until we return from this function (which is 0206 // triggered by a timer) 0207 0208 { 0209 QMutexLocker l(&d->mutex); 0210 0211 if (!d->subtasks.isEmpty()) { 0212 int totalProgress = 0; 0213 int totalWeight = 0; 0214 d->isUndefinedState = false; 0215 0216 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, d->subtasks) { 0217 if (updater->interrupted()) { 0218 d->currentProgress = -1; 0219 break; 0220 } 0221 0222 if (!updater->hasValidRange()) { 0223 totalWeight = 0; 0224 totalProgress = 0; 0225 d->isUndefinedState = true; 0226 break; 0227 } 0228 0229 if (updater->isPersistent() && updater->isCompleted()) { 0230 continue; 0231 } 0232 0233 const int progress = qBound(0, updater->progress(), 100); 0234 totalProgress += progress * updater->weight(); 0235 totalWeight += updater->weight(); 0236 } 0237 0238 const int progressPercent = totalWeight > 0 ? totalProgress / totalWeight : -1; 0239 0240 d->currentProgress = 0241 d->taskMax == 99 ? 0242 progressPercent : 0243 qRound(qreal(progressPercent) * d->taskMax / 99.0); 0244 } 0245 0246 } 0247 0248 if (d->progressProxy()) { 0249 if (!d->isUndefinedState) { 0250 d->progressProxy()->setRange(0, d->taskMax); 0251 0252 if (d->currentProgress == -1) { 0253 d->currentProgress = d->progressProxy()->maximum(); 0254 } 0255 0256 if (d->currentProgress >= d->progressProxy()->maximum()) { 0257 { 0258 QMutexLocker l(&d->mutex); 0259 d->clearState(); 0260 } 0261 d->progressProxy()->setRange(0, d->taskMax); 0262 d->progressProxy()->setValue(d->progressProxy()->maximum()); 0263 } else { 0264 d->progressProxy()->setValue(d->currentProgress); 0265 } 0266 } else { 0267 d->progressProxy()->setRange(0,0); 0268 d->progressProxy()->setValue(0); 0269 } 0270 0271 d->updateParentText(); 0272 } 0273 } 0274 0275 void KoProgressUpdater::Private::updateParentText() 0276 { 0277 if (!progressProxy()) return; 0278 0279 QString actionName = taskName; 0280 0281 if (autoNestNames) { 0282 Q_FOREACH (QPointer<KoUpdaterPrivate> updater, subtasks) { 0283 0284 if (updater->isPersistent() && updater->isCompleted()) { 0285 continue; 0286 } 0287 0288 if (updater->progress() < 100) { 0289 const QString subTaskName = updater->mergedSubTaskName(); 0290 0291 if (!subTaskName.isEmpty()) { 0292 if (actionName.isEmpty()) { 0293 actionName = subTaskName; 0294 } else { 0295 actionName = QString("%1: %2").arg(actionName).arg(subTaskName); 0296 } 0297 } 0298 break; 0299 } 0300 } 0301 progressProxy()->setAutoNestedName(actionName); 0302 } else { 0303 progressProxy()->setFormat(actionName); 0304 } 0305 0306 } 0307 0308 void KoProgressUpdater::Private::clearState() 0309 { 0310 for (auto it = subtasks.begin(); it != subtasks.end();) { 0311 if (!(*it)->isPersistent()) { 0312 (*it)->deleteLater(); 0313 it = subtasks.erase(it); 0314 } else { 0315 if ((*it)->interrupted()) { 0316 (*it)->setInterrupted(false); 0317 } 0318 ++it; 0319 } 0320 } 0321 0322 canceled = false; 0323 } 0324 0325 bool KoProgressUpdater::interrupted() const 0326 { 0327 return d->canceled; 0328 } 0329 0330 void KoProgressUpdater::setUpdateInterval(int ms) 0331 { 0332 d->updateCompressor->setDelay(ms); 0333 } 0334 0335 int KoProgressUpdater::updateInterval() const 0336 { 0337 return d->updateCompressor->delay(); 0338 } 0339 0340 void KoProgressUpdater::setAutoNestNames(bool value) 0341 { 0342 d->autoNestNames = value; 0343 } 0344 0345 bool KoProgressUpdater::autoNestNames() const 0346 { 0347 return d->autoNestNames; 0348 }