File indexing completed on 2025-03-09 03:55:26

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-01-13
0007  * Description : progress manager
0008  *
0009  * SPDX-FileCopyrightText: 2007-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2004      by Till Adam <adam at kde dot org>
0011  * SPDX-FileCopyrightText: 2004      by David Faure <faure at kde dot org>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "statusbarprogresswidget.h"
0018 
0019 // Qt includes
0020 
0021 #include <QEvent>
0022 #include <QHBoxLayout>
0023 #include <QLabel>
0024 #include <QMouseEvent>
0025 #include <QProgressBar>
0026 #include <QPushButton>
0027 #include <QStackedWidget>
0028 #include <QTimer>
0029 #include <QApplication>
0030 #include <QStyle>
0031 #include <QIcon>
0032 
0033 // KDE includes
0034 
0035 #include <klocalizedstring.h>
0036 
0037 // Local includes
0038 
0039 #include "digikam_debug.h"
0040 #include "progressview.h"
0041 #include "progressmanager.h"
0042 
0043 namespace Digikam
0044 {
0045 
0046 class Q_DECL_HIDDEN StatusbarProgressWidget::Private
0047 {
0048 public:
0049 
0050     enum Mode
0051     {
0052         None,
0053         Progress
0054     };
0055 
0056 public:
0057 
0058     explicit Private()
0059       : mode(None),
0060         bShowButton(true),
0061         pProgressBar(nullptr),
0062         pLabel(nullptr),
0063         pButton(nullptr),
0064         box(nullptr),
0065         stack(nullptr),
0066         currentItem(nullptr),
0067         progressView(nullptr),
0068         delayTimer(nullptr),
0069         busyTimer(nullptr),
0070         cleanTimer(nullptr)
0071     {
0072     }
0073 
0074     uint            mode;
0075     bool            bShowButton;
0076 
0077     QProgressBar*   pProgressBar;
0078     QLabel*         pLabel;
0079     QPushButton*    pButton;
0080 
0081     QBoxLayout*     box;
0082     QStackedWidget* stack;
0083     ProgressItem*   currentItem;
0084     ProgressView*   progressView;
0085     QTimer*         delayTimer;
0086     QTimer*         busyTimer;
0087     QTimer*         cleanTimer;
0088 };
0089 
0090 StatusbarProgressWidget::StatusbarProgressWidget(ProgressView* const progressView, QWidget* const parent, bool button)
0091     : QFrame(parent),
0092       d(new Private)
0093 {
0094     d->progressView = progressView;
0095     d->bShowButton  = button;
0096 
0097     int w           = fontMetrics().horizontalAdvance(QLatin1String(" 999.9 kB/s 00:00:01 ")) + 8;
0098 
0099     d->box          = new QHBoxLayout(this);
0100     d->box->setContentsMargins(QMargins());
0101     d->box->setSpacing(0);
0102     d->stack        = new QStackedWidget(this);
0103 
0104     d->pButton      = new QPushButton(this);
0105     d->pButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
0106     d->pButton->setIcon(QIcon::fromTheme(QLatin1String("go-up")));
0107     QSize iconSize  = d->pButton->iconSize();
0108 
0109     d->pProgressBar = new QProgressBar(this);
0110     d->pProgressBar->installEventFilter(this);
0111     d->pProgressBar->setMinimumWidth(w);
0112     d->pProgressBar->setAttribute(Qt::WA_LayoutUsesWidgetRect, true);
0113 
0114     // Determine maximumHeight from the progressbar's height and scale the icon.
0115     // This operation is style specific and cannot infer the style in use
0116     // from Q_OS_??? because users can have started us using the -style option
0117     // (or even be using an unexpected QPA).
0118     // In most cases, maximumHeight should be set to fontMetrics().height() + 2
0119     // (Breeze, Oxygen, Fusion, Windows, QtCurve etc.); this corresponds to the actual
0120     // progressbar height plus a 1 pixel margin above and below.
0121 
0122     int maximumHeight         = d->pProgressBar->fontMetrics().height() + 2;
0123     const bool macWidgetStyle = QApplication::style()->objectName() == QLatin1String("macintosh");
0124 
0125     if (macWidgetStyle)
0126     {
0127         // QProgressBar height is fixed with the macintosh native widget style
0128         // and alignment with d->pButton is tricky. Sizing the icon to maximumHeight
0129         // gives a button that is slightly too high and not perfectly
0130         // aligned. Annoyingly that doesn't improve by calling setMaximumHeight()
0131         // which even causes the button to change shape. So we use a "flat" button,
0132         // an invisible outline which is more in line with platform practices anyway.
0133 
0134         maximumHeight = d->pProgressBar->sizeHint().height();
0135         iconSize.scale(maximumHeight, maximumHeight, Qt::KeepAspectRatio);
0136         d->pButton->setFlat(true);
0137         d->pButton->setMaximumWidth(d->pButton->iconSize().width() + 4);
0138     }
0139     else
0140     {
0141         // The icon is scaled to maximumHeight but with 1 pixel margins on each side
0142         // because it will be in a visible button.
0143 
0144         iconSize.scale(maximumHeight - 2, maximumHeight - 2, Qt::KeepAspectRatio);
0145 
0146         // additional adjustments:
0147 
0148         d->pButton->setAttribute(Qt::WA_LayoutUsesWidgetRect, true);
0149         d->stack->setMaximumHeight(maximumHeight);
0150     }
0151 
0152     d->pButton->setIconSize(iconSize);
0153     d->box->addWidget(d->pButton);
0154 
0155     d->pButton->setToolTip(i18n("Open detailed progress dialog"));
0156 
0157     d->box->addWidget(d->stack);
0158 
0159     d->stack->insertWidget(1, d->pProgressBar);
0160 
0161     d->pLabel       = new QLabel(i18n("No active process"), this);
0162     d->pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0163     d->pLabel->installEventFilter(this);
0164     d->pLabel->setMinimumWidth(w);
0165     d->pLabel->setMaximumHeight(maximumHeight);
0166     d->stack->insertWidget(2, d->pLabel);
0167     d->pButton->setMaximumHeight(maximumHeight);
0168     setMinimumWidth(minimumSizeHint().width());
0169 
0170     setMode();
0171 
0172     // -------------------------------------------------------------------------------------------------
0173 
0174     connect(d->pButton, SIGNAL(clicked()),
0175             progressView, SLOT(slotToggleVisibility()));
0176 
0177     connect(ProgressManager::instance(), SIGNAL(progressItemAdded(ProgressItem*)),
0178             this, SLOT(slotProgressItemAdded(ProgressItem*)));
0179 
0180     connect(ProgressManager::instance(), SIGNAL(progressItemCompleted(ProgressItem*)),
0181             this, SLOT(slotProgressItemCompleted(ProgressItem*)));
0182 
0183     connect(ProgressManager::instance(), SIGNAL(progressItemUsesBusyIndicator(ProgressItem*,bool)),
0184             this, SLOT(updateBusyMode()));
0185 
0186     connect(progressView, SIGNAL(visibilityChanged(bool)),
0187             this, SLOT(slotProgressViewVisible(bool)));
0188 
0189     d->delayTimer = new QTimer(this);
0190     d->delayTimer->setSingleShot(true);
0191 
0192     connect(d->delayTimer, SIGNAL(timeout()),
0193             this, SLOT(slotShowItemDelayed()));
0194 
0195     d->cleanTimer = new QTimer(this);
0196     d->cleanTimer->setSingleShot(true);
0197 
0198     connect(d->cleanTimer, SIGNAL(timeout()),
0199             this, SLOT(slotClean()));
0200 }
0201 
0202 StatusbarProgressWidget::~StatusbarProgressWidget()
0203 {
0204     delete d;
0205 }
0206 
0207 // There are three cases: no progressitem, one progressitem (connect to it directly),
0208 // or many progressitems (display busy indicator). Let's call them 0,1,N.
0209 // In slot..Added we can only end up in 1 or N.
0210 // In slot..Removed we can end up in 0, 1, or we can stay in N if we were already.
0211 
0212 void StatusbarProgressWidget::updateBusyMode()
0213 {
0214     connectSingleItem(); // if going to 1 item
0215 
0216     if (d->currentItem)
0217     {
0218         // Exactly one item
0219 
0220         delete d->busyTimer;
0221         d->busyTimer = nullptr;
0222         d->delayTimer->start( 1000 );
0223     }
0224     else
0225     {
0226         // N items
0227 
0228         if (!d->busyTimer)
0229         {
0230             d->busyTimer = new QTimer(this);
0231 
0232             connect(d->busyTimer, SIGNAL(timeout()),
0233                     this, SLOT(slotBusyIndicator()));
0234 
0235             d->delayTimer->start(1000);
0236         }
0237     }
0238 }
0239 
0240 void StatusbarProgressWidget::slotProgressItemAdded(ProgressItem* item)
0241 {
0242     if (item->parent())
0243     {
0244         return; // we are only interested in top level items
0245     }
0246 
0247     updateBusyMode();
0248 }
0249 
0250 void StatusbarProgressWidget::slotProgressItemCompleted(ProgressItem* item)
0251 {
0252     if (item && item->parent())
0253     {
0254         return; // we are only interested in top level items
0255     }
0256 
0257     connectSingleItem(); // if going back to 1 item
0258 
0259     if (ProgressManager::instance()->isEmpty())
0260     {
0261         // No item
0262         // Done. In 5s the progress-widget will close, then we can clean up the statusbar
0263 
0264         d->cleanTimer->start(5000);
0265     }
0266     else if (d->currentItem)
0267     {
0268         // Exactly one item
0269 
0270         delete d->busyTimer;
0271         d->busyTimer = nullptr;
0272         activateSingleItemMode();
0273     }
0274 }
0275 
0276 void StatusbarProgressWidget::connectSingleItem()
0277 {
0278     if (d->currentItem)
0279     {
0280         disconnect(d->currentItem, SIGNAL(progressItemProgress(ProgressItem*,uint)),
0281                    this, SLOT(slotProgressItemProgress(ProgressItem*,uint)));
0282 
0283         d->currentItem = nullptr;
0284     }
0285 
0286     d->currentItem = ProgressManager::instance()->singleItem();
0287 
0288     if (d->currentItem)
0289     {
0290         connect(d->currentItem, SIGNAL(progressItemProgress(ProgressItem*,uint)),
0291                 this, SLOT(slotProgressItemProgress(ProgressItem*,uint)));
0292     }
0293 }
0294 
0295 void StatusbarProgressWidget::activateSingleItemMode()
0296 {
0297     d->pProgressBar->setMaximum(100);
0298     d->pProgressBar->setValue(d->currentItem->progress());
0299     d->pProgressBar->setTextVisible(true);
0300 }
0301 
0302 void StatusbarProgressWidget::slotShowItemDelayed()
0303 {
0304     bool noItems = ProgressManager::instance()->isEmpty();
0305 
0306     if      (d->currentItem)
0307     {
0308         activateSingleItemMode();
0309     }
0310     else if (!noItems)
0311     {
0312         // N items
0313 
0314         d->pProgressBar->setMaximum(0);
0315         d->pProgressBar->setTextVisible(false);
0316 
0317         if (d->busyTimer)
0318         {
0319             d->busyTimer->start(100);
0320         }
0321     }
0322 
0323     if (!noItems && (d->mode == Private::None))
0324     {
0325         d->mode = Private::Progress;
0326         setMode();
0327     }
0328 }
0329 
0330 void StatusbarProgressWidget::slotBusyIndicator()
0331 {
0332     int p = d->pProgressBar->value();
0333     d->pProgressBar->setValue(p + 10);
0334 }
0335 
0336 void StatusbarProgressWidget::slotProgressItemProgress(ProgressItem* item, unsigned int value)
0337 {
0338     if (item != d->currentItem) // single item mode; discard others
0339     {
0340         return;
0341     }
0342 
0343     d->pProgressBar->setValue(value);
0344 }
0345 
0346 void StatusbarProgressWidget::setMode()
0347 {
0348     switch (d->mode)
0349     {
0350         case Private::None:
0351         {
0352             if (d->bShowButton)
0353             {
0354                 d->pButton->hide();
0355             }
0356 
0357             d->stack->show();
0358             d->stack->setCurrentWidget(d->pLabel);
0359 
0360             break;
0361         }
0362 
0363         case Private::Progress:
0364         {
0365             d->stack->show();
0366             d->stack->setCurrentWidget(d->pProgressBar);
0367 
0368             if (d->bShowButton)
0369             {
0370                 d->pButton->show();
0371             }
0372 
0373             break;
0374         }
0375     }
0376 }
0377 
0378 void StatusbarProgressWidget::slotClean()
0379 {
0380     // check if a new item showed up since we started the timer. If not, clear
0381 
0382     if (ProgressManager::instance()->isEmpty())
0383     {
0384         d->pProgressBar->setValue(0);
0385         //d->pLabel->clear();
0386         d->mode = Private::None;
0387         setMode();
0388     }
0389 }
0390 
0391 bool StatusbarProgressWidget::eventFilter(QObject*, QEvent* ev)
0392 {
0393     if (ev->type() == QEvent::MouseButtonPress)
0394     {
0395         QMouseEvent* const e = (QMouseEvent*)ev;
0396 
0397         if ((e->button() == Qt::LeftButton) && (d->mode != Private::None))
0398         {
0399             // toggle view on left mouse button
0400             // Consensus seems to be that we should show/hide the fancy dialog when the user
0401             // clicks anywhere in the small one.
0402 
0403             d->progressView->slotToggleVisibility();
0404 
0405             return true;
0406         }
0407     }
0408 
0409     return false;
0410 }
0411 
0412 void StatusbarProgressWidget::slotProgressViewVisible(bool b)
0413 {
0414     // Update the hide/show button when the detailed one is shown/hidden
0415 
0416     if (b)
0417     {
0418         d->pButton->setIcon(QIcon::fromTheme(QLatin1String("go-down")));
0419         d->pButton->setToolTip(i18n("Hide detailed progress window"));
0420         setMode();
0421     }
0422     else
0423     {
0424         d->pButton->setIcon(QIcon::fromTheme(QLatin1String("go-up")));
0425         d->pButton->setToolTip(i18n("Show detailed progress window"));
0426     }
0427 }
0428 
0429 } // namespace Digikam
0430 
0431 #include "moc_statusbarprogresswidget.cpp"