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"