File indexing completed on 2024-05-19 16:47:31

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 }