File indexing completed on 2024-10-13 05:04:15
0001 // clang-format off 0002 /* 0003 * KDiff3 - Text Diff And Merge Tool 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 0011 #include "progress.h" 0012 0013 #include "defmac.h" 0014 #include "difftextwindow.h" 0015 #include "TypeUtils.h" 0016 0017 #include <cmath> 0018 0019 #include <QApplication> 0020 #include <QEventLoop> 0021 #include <QLabel> 0022 #include <QPointer> 0023 #include <QProgressBar> 0024 #include <QPushButton> 0025 #include <QStatusBar> 0026 #include <QThread> 0027 #include <QTimer> 0028 #include <QTimerEvent> 0029 #include <QVBoxLayout> 0030 0031 #include <KIO/Job> 0032 #include <KLocalizedString> 0033 0034 namespace placeholders = boost::placeholders; 0035 0036 QPointer<ProgressDialog> g_pProgressDialog = nullptr; 0037 0038 ProgressDialog::ProgressDialog(QWidget* pParent, QStatusBar* pStatusBar) 0039 : QDialog(pParent), m_pStatusBar(pStatusBar) 0040 { 0041 dialogUi.setupUi(this); 0042 setModal(true); 0043 //Abort if verticalLayout is not the immediate child of the dialog. This interferes with re-sizing. 0044 assert(dialogUi.layout->parent() == this); 0045 0046 chk_connect_a(dialogUi.abortButton, &QPushButton::clicked, this, &ProgressDialog::slotAbort); 0047 if(m_pStatusBar != nullptr) 0048 { 0049 m_pStatusBarWidget = new QWidget; 0050 QHBoxLayout* pStatusBarLayout = new QHBoxLayout(m_pStatusBarWidget); 0051 pStatusBarLayout->setContentsMargins(0, 0, 0, 0); 0052 pStatusBarLayout->setSpacing(3); 0053 m_pStatusProgressBar = new QProgressBar; 0054 m_pStatusProgressBar->setRange(0, 1000); 0055 m_pStatusProgressBar->setTextVisible(false); 0056 m_pStatusAbortButton = new QPushButton(i18n("&Cancel")); 0057 chk_connect_a(m_pStatusAbortButton, &QPushButton::clicked, this, &ProgressDialog::slotAbort); 0058 pStatusBarLayout->addWidget(m_pStatusProgressBar); 0059 pStatusBarLayout->addWidget(m_pStatusAbortButton); 0060 m_pStatusBar->addPermanentWidget(m_pStatusBarWidget, 0); 0061 m_pStatusBarWidget->setFixedHeight(m_pStatusBar->height()); 0062 m_pStatusBarWidget->hide(); 0063 } 0064 0065 resize(400, 100); 0066 0067 m_t1.start(); 0068 m_t2.start(); 0069 0070 initConnections(); 0071 } 0072 0073 ProgressDialog::~ProgressDialog() 0074 { 0075 while(DiffTextWindow::maxThreads() > 0) {} //expected to be set by last helper thread. 0076 } 0077 0078 void ProgressDialog::initConnections() 0079 { 0080 connections.push_back(ProgressProxy::startBackgroundTask.connect(boost::bind(&ProgressDialog::beginBackgroundTask, this))); 0081 connections.push_back(ProgressProxy::endBackgroundTask.connect(boost::bind(&ProgressDialog::endBackgroundTask, this))); 0082 0083 connections.push_back(ProgressProxy::push.connect(boost::bind(&ProgressDialog::push, this))); 0084 connections.push_back(ProgressProxy::pop.connect(boost::bind(&ProgressDialog::pop, this, placeholders::_1))); 0085 connections.push_back(ProgressProxy::clear.connect(boost::bind(&ProgressDialog::clear, this))); 0086 0087 connections.push_back(ProgressProxy::enterEventLoop.connect(boost::bind(&ProgressDialog::enterEventLoop, this, placeholders::_1, placeholders::_2))); 0088 connections.push_back(ProgressProxy::exitEventLoop.connect(boost::bind(&ProgressDialog::exitEventLoop, this))); 0089 0090 connections.push_back(ProgressProxy::setCurrentSig.connect(boost::bind(&ProgressDialog::setCurrent, this, placeholders::_1, placeholders::_2))); 0091 connections.push_back(ProgressProxy::addNofSteps.connect(boost::bind(&ProgressDialog::addNofSteps, this, placeholders::_1))); 0092 connections.push_back(ProgressProxy::setMaxNofSteps.connect(boost::bind(&ProgressDialog::setMaxNofSteps, this, placeholders::_1))); 0093 connections.push_back(ProgressProxy::stepSig.connect(boost::bind(&ProgressDialog::step, this, placeholders::_1))); 0094 0095 connections.push_back(ProgressProxy::setRangeTransformation.connect(boost::bind(&ProgressDialog::setRangeTransformation, this, placeholders::_1, placeholders::_2))); 0096 connections.push_back(ProgressProxy::setSubRangeTransformation.connect(boost::bind(&ProgressDialog::setSubRangeTransformation, this, placeholders::_1, placeholders::_2))); 0097 0098 connections.push_back(ProgressProxy::wasCancelled.connect(boost::bind(&ProgressDialog::wasCancelled, this))); 0099 0100 connections.push_back(ProgressProxy::setInformationSig.connect(boost::bind( 0101 static_cast<void (ProgressDialog::*)(const QString&, bool)>(&ProgressDialog::setInformation), 0102 this, 0103 placeholders::_1, placeholders::_2))); 0104 } 0105 0106 void ProgressDialog::setStayHidden(bool bStayHidden) 0107 { 0108 if(m_bStayHidden != bStayHidden) 0109 { 0110 m_bStayHidden = bStayHidden; 0111 if(m_pStatusBarWidget != nullptr) 0112 { 0113 if(m_bStayHidden) 0114 { 0115 if(m_delayedHideStatusBarWidgetTimer) 0116 { 0117 killTimer(m_delayedHideStatusBarWidgetTimer); 0118 m_delayedHideStatusBarWidgetTimer = 0; 0119 } 0120 m_pStatusBarWidget->show(); 0121 } 0122 else 0123 hideStatusBarWidget(); // delayed 0124 } 0125 if(m_bStayHidden) 0126 hide(); // delayed hide 0127 } 0128 } 0129 0130 void ProgressDialog::push() 0131 { 0132 ProgressLevelData pld; 0133 if(!m_progressStack.empty()) 0134 { 0135 pld.m_dRangeMax = m_progressStack.back().m_dSubRangeMax; 0136 pld.m_dRangeMin = m_progressStack.back().m_dSubRangeMin; 0137 } 0138 else 0139 { 0140 m_bWasCancelled = false; 0141 0142 m_t1.restart(); 0143 m_t2.restart(); 0144 0145 if(!m_bStayHidden) 0146 show(); 0147 } 0148 0149 m_progressStack.push_back(pld); 0150 } 0151 0152 void ProgressDialog::beginBackgroundTask() 0153 { 0154 if(backgroundTaskCount > 0) 0155 { 0156 m_t1.restart(); 0157 m_t2.restart(); 0158 } 0159 backgroundTaskCount++; 0160 if(!m_bStayHidden) 0161 show(); 0162 } 0163 0164 void ProgressDialog::endBackgroundTask() 0165 { 0166 if(backgroundTaskCount > 0) 0167 { 0168 backgroundTaskCount--; 0169 if(backgroundTaskCount == 0) 0170 { 0171 hide(); 0172 } 0173 } 0174 } 0175 0176 void ProgressDialog::pop(bool bRedrawUpdate) 0177 { 0178 if(!m_progressStack.empty()) 0179 { 0180 m_progressStack.pop_back(); 0181 if(m_progressStack.empty()) 0182 { 0183 hide(); 0184 } 0185 else 0186 recalc(bRedrawUpdate); 0187 } 0188 } 0189 0190 void ProgressDialog::setInformation(const QString& info, qint32 current, bool bRedrawUpdate) 0191 { 0192 if(m_progressStack.empty()) 0193 return; 0194 0195 setCurrent(current, false); 0196 setInformationImp(info); 0197 recalc(bRedrawUpdate); 0198 } 0199 0200 void ProgressDialog::setInformation(const QString& info, bool bRedrawUpdate) 0201 { 0202 if(m_progressStack.empty()) 0203 return; 0204 0205 setInformationImp(info); 0206 recalc(bRedrawUpdate); 0207 } 0208 0209 void ProgressDialog::setMaxNofSteps(const quint64 maxNofSteps) 0210 { 0211 if(m_progressStack.empty() || maxNofSteps == 0) 0212 return; 0213 0214 ProgressLevelData& pld = m_progressStack.back(); 0215 pld.m_maxNofSteps = maxNofSteps; 0216 pld.m_current = 0; 0217 } 0218 0219 void ProgressDialog::setInformationImp(const QString& info) 0220 { 0221 assert(!m_progressStack.empty()); 0222 0223 size_t level = m_progressStack.size(); 0224 if(level == 1) 0225 { 0226 dialogUi.information->setText(info); 0227 dialogUi.subInformation->setText(""); 0228 if(m_pStatusBar && m_bStayHidden) 0229 m_pStatusBar->showMessage(info); 0230 } 0231 else if(level == 2) 0232 { 0233 dialogUi.subInformation->setText(info); 0234 } 0235 } 0236 0237 void ProgressDialog::addNofSteps(const quint64 nofSteps) 0238 { 0239 if(m_progressStack.empty()) 0240 return; 0241 0242 ProgressLevelData& pld = m_progressStack.back(); 0243 pld.m_maxNofSteps.fetchAndAddRelaxed(nofSteps); 0244 } 0245 0246 void ProgressDialog::step(bool bRedrawUpdate) 0247 { 0248 if(m_progressStack.empty()) 0249 return; 0250 0251 ProgressLevelData& pld = m_progressStack.back(); 0252 pld.m_current.fetchAndAddRelaxed(1); 0253 recalc(bRedrawUpdate); 0254 } 0255 0256 void ProgressDialog::setCurrent(quint64 subCurrent, bool bRedrawUpdate) 0257 { 0258 if(m_progressStack.empty()) 0259 return; 0260 0261 ProgressLevelData& pld = m_progressStack.back(); 0262 pld.m_current = subCurrent; 0263 0264 recalc(bRedrawUpdate); 0265 } 0266 0267 void ProgressDialog::clear() 0268 { 0269 if(m_progressStack.empty()) 0270 return; 0271 0272 ProgressLevelData& pld = m_progressStack.back(); 0273 setCurrent(pld.m_maxNofSteps); 0274 } 0275 0276 // The progressbar goes from 0 to 1 usually. 0277 // By supplying a subrange transformation the subCurrent-values 0278 // 0 to 1 will be transformed to dMin to dMax instead. 0279 // Requirement: 0 < dMin < dMax < 1 0280 void ProgressDialog::setRangeTransformation(double dMin, double dMax) 0281 { 0282 if(m_progressStack.empty()) 0283 return; 0284 0285 ProgressLevelData& pld = m_progressStack.back(); 0286 pld.m_dRangeMin = dMin; 0287 pld.m_dRangeMax = dMax; 0288 pld.m_current = 0; 0289 } 0290 0291 void ProgressDialog::setSubRangeTransformation(double dMin, double dMax) 0292 { 0293 if(m_progressStack.empty()) 0294 return; 0295 0296 ProgressLevelData& pld = m_progressStack.back(); 0297 pld.m_dSubRangeMin = dMin; 0298 pld.m_dSubRangeMax = dMax; 0299 } 0300 0301 void ProgressDialog::enterEventLoop(KJob* pJob, const QString& jobInfo) 0302 { 0303 m_pJob = pJob; 0304 m_currentJobInfo = jobInfo; 0305 dialogUi.slowJobInfo->setText(m_currentJobInfo); 0306 if(m_progressDelayTimer) 0307 killTimer(m_progressDelayTimer); 0308 m_progressDelayTimer = startTimer(3000); /* 3 s delay */ 0309 0310 // immediately show the progress dialog for KIO jobs, because some KIO jobs require password authentication, 0311 // but if the progress dialog pops up at a later moment, this might cover the login dialog and hide it from the user. 0312 if(m_pJob && !m_bStayHidden) 0313 show(); 0314 0315 // instead of using exec() the eventloop is entered and exited often without hiding/showing the window. 0316 if(m_eventLoop == nullptr) 0317 { 0318 m_eventLoop = QPointer<QEventLoop>(new QEventLoop(this)); 0319 m_eventLoop->exec(); // this function only returns after ProgressDialog::exitEventLoop() is called. 0320 m_eventLoop.clear(); 0321 } 0322 else 0323 { 0324 m_eventLoop->processEvents(QEventLoop::WaitForMoreEvents); 0325 } 0326 } 0327 0328 void ProgressDialog::exitEventLoop() 0329 { 0330 if(m_progressDelayTimer) 0331 killTimer(m_progressDelayTimer); 0332 m_progressDelayTimer = 0; 0333 m_pJob = nullptr; 0334 if(m_eventLoop != nullptr) 0335 m_eventLoop->exit(); 0336 } 0337 0338 void ProgressDialog::recalc(bool bUpdate) 0339 { 0340 if(!m_bWasCancelled) 0341 { 0342 if(QThread::currentThread() == m_pGuiThread) 0343 { 0344 if(m_progressDelayTimer) 0345 killTimer(m_progressDelayTimer); 0346 m_progressDelayTimer = 0; 0347 if(!m_bStayHidden) 0348 m_progressDelayTimer = startTimer(3000); /* 3 s delay */ 0349 0350 size_t level = m_progressStack.size(); 0351 if((bUpdate && level == 1) || m_t1.elapsed() > 200) 0352 { 0353 if(m_progressStack.empty()) 0354 { 0355 dialogUi.progressBar->setValue(0); 0356 dialogUi.subProgressBar->setValue(0); 0357 } 0358 else 0359 { 0360 std::list<ProgressLevelData>::iterator i = m_progressStack.begin(); 0361 qint32 value = qint32(1000.0 * (i->m_current.loadRelaxed() * (i->m_dRangeMax - i->m_dRangeMin) / i->m_maxNofSteps.loadRelaxed() + i->m_dRangeMin)); 0362 dialogUi.progressBar->setValue(value); 0363 if(m_bStayHidden && m_pStatusProgressBar) 0364 m_pStatusProgressBar->setValue(value); 0365 0366 ++i; 0367 if(i != m_progressStack.end()) 0368 dialogUi.subProgressBar->setValue((qint32)lround(1000.0 * (i->m_current.loadRelaxed() * (i->m_dRangeMax - i->m_dRangeMin) / i->m_maxNofSteps.loadRelaxed() + i->m_dRangeMin))); 0369 else 0370 dialogUi.subProgressBar->setValue((qint32)lround(1000.0 * m_progressStack.front().m_dSubRangeMin)); 0371 } 0372 0373 if(!m_bStayHidden) 0374 show(); 0375 qApp->processEvents(); 0376 m_t1.restart(); 0377 } 0378 } 0379 else 0380 { 0381 QMetaObject::invokeMethod(this, "recalc", Qt::QueuedConnection, Q_ARG(bool, bUpdate)); 0382 } 0383 } 0384 } 0385 0386 void ProgressDialog::show() 0387 { 0388 if(m_progressDelayTimer) 0389 killTimer(m_progressDelayTimer); 0390 if(m_delayedHideTimer) 0391 killTimer(m_delayedHideTimer); 0392 m_progressDelayTimer = 0; 0393 m_delayedHideTimer = 0; 0394 if(parentWidget() == nullptr || parentWidget()->isVisible()) 0395 { 0396 QDialog::show(); 0397 } 0398 } 0399 0400 void ProgressDialog::hide() 0401 { 0402 if(m_progressDelayTimer) 0403 killTimer(m_progressDelayTimer); 0404 m_progressDelayTimer = 0; 0405 // Calling QDialog::hide() directly doesn't always work. (?) 0406 if(m_delayedHideTimer) 0407 killTimer(m_delayedHideTimer); 0408 m_delayedHideTimer = startTimer(100); 0409 } 0410 0411 void ProgressDialog::delayedHide() 0412 { 0413 if(m_pJob != nullptr) 0414 { 0415 m_pJob->kill(KJob::Quietly); 0416 m_pJob = nullptr; 0417 } 0418 QDialog::hide(); 0419 dialogUi.information->setText(""); 0420 0421 //m_progressStack.clear(); 0422 0423 dialogUi.progressBar->setValue(0); 0424 dialogUi.subProgressBar->setValue(0); 0425 dialogUi.subInformation->setText(""); 0426 dialogUi.slowJobInfo->setText(""); 0427 } 0428 0429 void ProgressDialog::hideStatusBarWidget() 0430 { 0431 if(m_delayedHideStatusBarWidgetTimer) 0432 killTimer(m_delayedHideStatusBarWidgetTimer); 0433 m_delayedHideStatusBarWidgetTimer = startTimer(100); 0434 } 0435 0436 void ProgressDialog::delayedHideStatusBarWidget() 0437 { 0438 if(m_progressDelayTimer) 0439 killTimer(m_progressDelayTimer); 0440 m_progressDelayTimer = 0; 0441 if(m_pStatusBarWidget != nullptr) 0442 { 0443 m_pStatusBarWidget->hide(); 0444 m_pStatusProgressBar->setValue(0); 0445 m_pStatusBar->clearMessage(); 0446 } 0447 } 0448 0449 void ProgressDialog::reject() 0450 { 0451 cancel(eUserAbort); 0452 QDialog::reject(); 0453 } 0454 0455 void ProgressDialog::slotAbort() 0456 { 0457 reject(); 0458 } 0459 0460 bool ProgressDialog::wasCancelled() 0461 { 0462 if(QThread::currentThread() == m_pGuiThread) 0463 { 0464 if(m_t2.elapsed() > 100) 0465 { 0466 qApp->processEvents(); 0467 m_t2.restart(); 0468 } 0469 } 0470 return m_bWasCancelled; 0471 } 0472 0473 void ProgressDialog::clearCancelState() 0474 { 0475 m_bWasCancelled = false; 0476 } 0477 0478 void ProgressDialog::cancel(e_CancelReason eCancelReason) 0479 { 0480 if(!m_bWasCancelled) 0481 { 0482 m_bWasCancelled = true; 0483 m_eCancelReason = eCancelReason; 0484 if(m_eventLoop != nullptr) 0485 m_eventLoop->exit(1); 0486 } 0487 } 0488 0489 ProgressDialog::e_CancelReason ProgressDialog::cancelReason() 0490 { 0491 return m_eCancelReason; 0492 } 0493 0494 void ProgressDialog::timerEvent(QTimerEvent* te) 0495 { 0496 if(te->timerId() == m_progressDelayTimer) 0497 { 0498 if(!m_bStayHidden) 0499 { 0500 show(); 0501 } 0502 dialogUi.slowJobInfo->setText(m_currentJobInfo); 0503 } 0504 else if(te->timerId() == m_delayedHideTimer) 0505 { 0506 killTimer(m_delayedHideTimer); 0507 m_delayedHideTimer = 0; 0508 delayedHide(); 0509 } 0510 else if(te->timerId() == m_delayedHideStatusBarWidgetTimer) 0511 { 0512 killTimer(m_delayedHideStatusBarWidgetTimer); 0513 m_delayedHideStatusBarWidgetTimer = 0; 0514 delayedHideStatusBarWidget(); 0515 } 0516 }