Building a "complex" progress widget



  • Hey

    I've been stuck on this for few days now... I'm trying to build a widget that can receive calls from main + other threads to display progress/allow user to make some decisions while keeping remaining part of the app "frozen"...

    I initially started with qApp()->installEvent() and gave it my own class that does filtering. That class has a setLocK() flag which enables checking for mouse/keyboard input events and then ignoring them while its locked.

    The class below is part of larger app, so I sadly it won't work as a standalone test... in any case it shows how far I got so far...

    The problem I have is that when I push updates from other threads, they never get delivered. The idea was that when large process starts, the lock becomes enabled and while lock is enabled I fire a update every 50ms to redraw/processEvents(), but that appear to never work and any queued connections don't get processed while main loop is working. I used pragma omp parallel for to do some loops and from each of these loops I fire a signal to increment value(progress) but they never display update...

    In any case here is the code...

    
    #ifndef NOTIFICATIONMANAGER_H
    #define NOTIFICATIONMANAGER_H
    
    #include "QObject"
    
    class QDialog;
    class QProgressBar;
    class QLabel;
    class QTextBrowser;
    class QTime;
    class QTimer;
    class QGraphicsOpacityEffect;
    class QPropertyAnimation;
    class NotificationManagerWorker;
    
    #include <ctime>
    #include <ratio>
    #include <chrono>
    
    namespace NotificationManagerEnums {
        enum notificationType {
            header,
            primary,
            secondary,
            tethary,
            list,
    
        };
    }
    /*!
    * \class NotificationManager
    *
    * \brief NotificationManager class
    *
    */
    class NotificationManager : public QObject {
    Q_OBJECT
        NotificationManager();
        QWidget *mMainDialog;
        QProgressBar *mProgressItem;
        QLabel *mInfoA;
        QLabel *mInfoB;
        QLabel *mInfoC;
        QTextBrowser *mLog;
        QTimer *mTime;
        std::atomic<int> mValue;
        QGraphicsOpacityEffect *mEffectOpacity;
        QPropertyAnimation *mEffectAnimation;
        NotificationManagerWorker* mWorkerPtr;
        bool mWorker;
        std::chrono::steady_clock::time_point t1;
        std::chrono::steady_clock::time_point t2;
        void checkUpdate();
        void worker();
    
    private Q_SLOTS:
        void setRan(int min, int max);
        void setMin(int val);
        void setMax(int val);
        void setCur(int val);
        void setMsg(const QString &msg, int type);
        void showBox(const QString &title, int min = 0, int max = 100, int val = 0);
        void incVal();
    
    public:
        static NotificationManager *NM();
        ~icNotificationManager();
    
        void showManager();
        void hideDialog();
    
        void update();
        void incrementValThread();
    
    Q_SIGNALS:
        void setRange(int min, int max);
        void setMinimum(int val);
        void setMaximum(int val);
        void setValue(int val);
        void setMessageTex(const QString &msg, int type = 0);
        void showNotificationBox(const QString &title, int min = 0, int max = 100, int val = 0);
        void incrementVal();
        void incrementValT();
    
    };
    
    class NotificationManagerWorker : public QObject {
    Q_OBJECT
        friend class NotificationManager;
        bool mWorker;
        QThread *t;
        NotificationManager*mManager;
    
    public:
        NotificationManagerWorker(icNotificationManager*manager);
        ~icNotificationManagerWorker();
        void runWorker();
    };
    
    #endif //NOTIFICATIONMANAGER_H
    
    #include <QtWidgets/QGridLayout>
    #include <Lib/BaseSystems/InputMonitor.h>
    #include <QtGui/qguiapplication.h>
    #include <QtConcurrent/QtConcurrent>
    #include "NotificationManager.h"
    #include "QDialog"
    #include "QProgressBar"
    #include "QLabel"
    #include "QTextBrowser"
    #include "QTime"
    #include "QTimer"
    #include "QDebug"
    #include "QGraphicsOpacityEffect"
    #include "QPropertyAnimation"
    #include "QThread"
    
    using namespace std::chrono;
    
    NotificationManager::NotificationManager() {
        mValue = 0;
        mMainDialog = new QWidget();
        mMainDialog->setWindowFlags(Qt::Tool);// | Qt::WindowStaysOnTopHint | Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
        mProgressItem = new QProgressBar();
        mProgressItem->setTextVisible(true);
        mProgressItem->setAlignment(Qt::AlignCenter);
        mProgressItem->setFormat("%v / %m %p%");
        mLog = new QTextBrowser();
        QGridLayout *lay_main = new QGridLayout();
        mMainDialog->setLayout(lay_main);
        mInfoA = new QLabel("");
        mInfoB = new QLabel("");
        mInfoC = new QLabel("");
    
        lay_main->addWidget(mInfoA);
        lay_main->addWidget(mInfoB);
        lay_main->addWidget(mProgressItem);
        lay_main->addWidget(mInfoC);
        lay_main->addWidget(mLog);
    
        connect(this, &NotificationManager::setRange, this, &NotificationManager::setRan, Qt::DirectConnection);
        connect(this, &NotificationManager::setMinimum, this, &NotificationManager::setMin, Qt::DirectConnection);
        connect(this, &NotificationManager::setMaximum, this, &NotificationManager::setMax, Qt::DirectConnection);
        connect(this, &NotificationManager::setValue, this, &NotificationManager::setCur, Qt::DirectConnection);
        connect(this, &NotificationManager::setMessageTex, this, &NotificationManager::setMsg, Qt::DirectConnection);
        connect(this, &NotificationManager::showNotificationBox, this, &NotificationManager::showBox, Qt::DirectConnection);
        connect(this, &NotificationManager::incrementVal, this, &NotificationManager::incVal, Qt::DirectConnection);
        connect(this, &NotificationManager::incrementValT, this, &NotificationManager::incVal, Qt::QueuedConnection);
    
        mTime = new QTimer(mMainDialog); // failed did not work.
        //connect(mTime, &QTimer::timeout, []() {
        //    qDebug() << "timer kicking?";
        //    if (icInputMonitor::IM()->isLock()) {
        //        qDebug() << "timer kick";
        //        qApp->processEvents();
        //    }
        //});//SIGNAL(timeout()), myWidget, SLOT(showGPS()));
        //mTime->start(250); //time specified in ms
    
        mEffectOpacity = new QGraphicsOpacityEffect(this);
        //mMainDialog->setGraphicsEffect(mEffectOpacity);
        mEffectAnimation = new QPropertyAnimation(mEffectOpacity, "opacity");
        mEffectAnimation->setDuration(10000);
        mEffectAnimation->setStartValue(1);
        mEffectAnimation->setEndValue(0);
        mEffectAnimation->setEasingCurve(QEasingCurve::OutBack);
        //connect(mEffectAnimation, &QPropertyAnimation::finished, mMainDialog, &QWidget::hide);
        t1 = steady_clock::now();
        mWorker = true;
        //QtConcurrent::run(this, &NotificationManager::worker); /// failed did not work.
        mWorkerPtr = new NotificationManagerWorker(this);
    }
    
    NotificationManager *NotificationManager::NM() {
        static NotificationManager nm;
        return &nm;
    }
    
    NotificationManager::~NotificationManager() {
    
    }
    void NotificationManager::showManager() {
        //mMainDialog->exec();
    }
    void NotificationManager::hideDialog() {
        InputMonitor::IM()->setLock(false);
        //mMainDialog->hide();
        mEffectAnimation->setStartValue(1);
        //mEffectAnimation->start(QPropertyAnimation::KeepWhenStopped);
    // now implement a slot called hideThisWidget() to do
    // things like hide any background dimmer, etc.
        update();
        qDebug() << "Closed the fucking window?";
    }
    
    
    void NotificationManager::setRan(int min, int max) {
        qDebug() << "Range set :" << min << " " << max;
        mValue = 0;
        mProgressItem->setRange(min, max);
    }
    
    void NotificationManager::setMin(int val) {
        mProgressItem->setMinimum(val);
    }
    
    void NotificationManager::setMax(int val) {
        mProgressItem->setMaximum(val);
    }
    
    void NotificationManager::setCur(int val) {
        mProgressItem->setValue(val);
    }
    
    void NotificationManager::incVal() {
        mValue++;
        qDebug() << mValue;
        if (mValue >= mProgressItem->value())mProgressItem->setValue(mValue);
        t2 = steady_clock::now();
        duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
        if (time_span.count() > 500) {
            t1 = t2;
            mProgressItem->setValue(mValue);
        }
    }
    
    void NotificationManager::setMsg(const QString &msg, int type) {
        switch (type) {
            case NotificationManagerEnums::header: {
                mMainDialog->setWindowIconText(msg);
                break;
            }
            case NotificationManagerEnums::primary: {
                mInfoA->setText(msg);
                break;
            }
            case NotificationManagerEnums::secondary: {
                mInfoB->setText(msg);
                break;
            }
            case NotificationManagerEnums::tethary: {
                mInfoC->setText(msg);
                break;
            }
            case NotificationManagerEnums::list: {
                mLog->append(QTime::currentTime().toString() + "  -  " + msg);
                break;
            }
        }
        update();
    }
    void NotificationManager::showBox(const QString &title, int min, int max, int val) {
        InputMonitor::IM()->setLock(true);
        mEffectOpacity->setOpacity(1.0);
        mValue = val;
        mMainDialog->setWindowTitle("Processing : " + title);
        mInfoA->setText(title);
        mProgressItem->setMinimum(min);
        mProgressItem->setMaximum(max);
        mProgressItem->setValue(val);
        mMainDialog->show();
    }
    void NotificationManager::update() {
        mMainDialog->repaint();
        qApp->processEvents();
    }
    void NotificationManager::checkUpdate() {
    }
    void NotificationManager::incrementValThread() {
        Q_EMIT incrementValT();
    }
    void NotificationManager::worker() {
        auto *controller = InputMonitor::IM();
        while (mWorker) {
            QThread::msleep(10);
            if (controller->isLock()) {
                qDebug() << "I update!";
                qApp->processEvents();
            }
        }
    }
    NotificationManagerWorker::NotificationManagerWorker(NotificationManager *manager) : mWorker(true), mManager(manager) {
        t = new QThread();
        moveToThread(t);
        connect(t, &QThread::started, this, &NotificationManagerWorker::runWorker);
        t->start();
    }
    NotificationManagerWorker::~NotificationManagerWorker() {
    
    }
    void NotificationManagerWorker::runWorker() {
        auto *controller = InputMonitor::IM();
        while (mWorker) {
            QThread::msleep(50);
            if (controller->isLock()) {
                mManager->update();
                qDebug() << "I update!";
            }
        }
    }
    

    Any help would be amazing. I'm lost with qt system... :- (


    A rough example of code I would run with it :

    void manager::processData(vec<data> &data):
    
    for (auto &x:data){
         x.doStuff();
         NotificationManager::NM()->msg(x.status());
         NotificationManager::NM()->incrementVal()
    }
    
    #pragma omp parallel for
    for(int x=0;x<data.size();x++){
         data[x].adjust(x);
         NotificationManager::NM()->msg(x.status());
         NotificationManager::NM()->incrementValT(); // T for threaded
    }
    
    }
    

    The 1st option I think it "might" work... but second threaded option never works... sigh. What todo here?

    As far as I can tell. The main thread is busy, because it waits for its sub threads made from pragma gets processed. Now each of those pragma threads that are processing data can also call on the messageManager and send more processing updates there... so its all threaded down the line from here.... with main thread being locked... and if I read correctly... processEvents() would not process threaded events as this only gets called by thread that calls it which is not the pragma thread but my generic worker thread that fires update every 50ms... sigh!


  • Lifetime Qt Champion

    Hi,

    From what you described, it seems that this manager should be built on top of Qt Model/View framework.

    You can have a model that stores whatever data is being pushed and then the view on top of it would update when needed.

    This would also allow you to easily show several progress in parallel.



  • @sgaist Yeah long term that is the plan. But for now I'm struggling to actually display/update widgets while I process data.

    I know its my "design" problem as I lock my main thread, thus I can't update widget from my subThreads that the main spawns using pragma/other threads.... So as the example shows at the end of the topic... I'm kinda stuck :- ( Is there any way to "steal" main thread somehow to allow for Qt to redraw widget?

    The problem I have now is that I branch out to multiple sub threads and then go back to single thread as I process data. The branched out threads cant update my progress widget.


  • Lifetime Qt Champion

    Don't try to steal anything. Use signals and slots to communicate from your secondary threads back to your main thread.



  • @sgaist said in Building a "complex" progress widget:

    Don't try to steal anything. Use signals and slots to communicate from your secondary threads back to your main thread.

    Yes, but as far as I can tell the main thread is "working" (or user has took control over it) as it spawned the secondary threads and is waiting for them to finish processing before moving on.


    Edit
    Ok so I've now run QMetaObject::invokeMethod(this,={callToLongFunction},Qt::QueuedConnection);
    This appear to work, however any loop that was being

    #pragma omp parallel for
    for (){}

    No longer threads... so qt somehow manages to break the omp multithreading ? -- well standalone teste indicates that no... all works there but my app stopped threading... sweet.


    Ok I'm a nub, its official! Everything works. It threads & runs job in qt internal event loop thus I don't have any issues. Wow. Ok I need to learn more on qt threading event loop :D I need to fork one for myself and have it process all my work stuff now :- ))


    EDIT...

    Ok seems like I did a full loop. I went from processing in user thread to processing in qt event loop - fine - but pragma still wont update the widget at the correct time... sigh. Since now we are in event loop, the processing of function is more important than processing of omp thread update signal... I need some kind of priority attached to signal to tell qt that hey... I know u have ur loop but process this signal now.
    Right now with Qt::DirectConnection while trying to update val/etc I get this >

    ######
    			WARNING: QWidget::repaint: Recursive repaint detected
     ((null):0, (null))
    ######
    
    
    ######
    			WARNING: QBackingStore::endPaint() called with active painter on backingstore paint device
     ((null):0, (null))
    ######
    

    And aparently by putting this inside omp seems to "work"...

            if (omp_get_thread_num() == 0) {
                qApp->processEvents();
            }
    ```... ahhhh multithreading <3

 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.