File indexing completed on 2024-05-12 04:38:18
0001 /* 0002 SPDX-FileCopyrightText: 2004 Till Adam <adam@kde.org> 0003 SPDX-FileCopyrightText: 2004 Don Sanders 0004 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org> 0005 0006 Includes StatusbarProgressWidget which is based on KIOLittleProgressDlg 0007 SPDX-FileCopyrightText: Matt Koss <koss@miesto.sk> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "statusbarprogresswidget.h" 0013 #include "progressdialog.h" 0014 #include "progressmanager.h" 0015 #ifdef Q_OS_OSX 0016 #include "../macdockprogressview.h" 0017 #endif 0018 0019 #include <KLocalizedString> 0020 0021 #include <QEvent> 0022 #include <QFrame> 0023 #include <QHBoxLayout> 0024 #include <QLabel> 0025 #include <QMouseEvent> 0026 #include <QProgressBar> 0027 #include <QPushButton> 0028 #include <QStackedWidget> 0029 #include <QTimer> 0030 #include <QToolButton> 0031 #include <QApplication> 0032 #include <QStyle> 0033 0034 using namespace KDevelop; 0035 0036 //----------------------------------------------------------------------------- 0037 StatusbarProgressWidget::StatusbarProgressWidget( ProgressDialog* progressDialog, QWidget* parent, bool button ) 0038 : QFrame( parent ), mCurrentItem( nullptr ), mProgressDialog( progressDialog ), 0039 mDelayTimer( nullptr ), mCleanTimer( nullptr ) 0040 { 0041 m_bShowButton = button; 0042 int w = fontMetrics().horizontalAdvance(QStringLiteral(" 999.9 kB/s 00:00:01 ")) + 8; 0043 box = new QHBoxLayout( this ); 0044 box->setContentsMargins(0, 0, 0, 0); 0045 box->setSpacing(0); 0046 stack = new QStackedWidget( this ); 0047 0048 m_pButton = new QToolButton( this ); 0049 m_pButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, 0050 QSizePolicy::Fixed ) ); 0051 QIcon smallIcon = QIcon::fromTheme( QStringLiteral("go-up") ); 0052 if ( smallIcon.isNull() ) { 0053 // this can happen everywhere but in particular with a standard build on OS X. 0054 // QToolButtons won't be visible without an icon, so fall back to showing a Qt::UpArrow. 0055 m_pButton->setArrowType( Qt::UpArrow ); 0056 } else { 0057 m_pButton->setIcon( smallIcon ); 0058 } 0059 m_pButton->setAutoRaise(true); 0060 QSize iconSize = m_pButton->iconSize(); 0061 0062 m_pProgressBar = new QProgressBar( this ); 0063 m_pProgressBar->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, 0064 QSizePolicy::Fixed ) ); 0065 m_pProgressBar->installEventFilter( this ); 0066 m_pProgressBar->setMinimumWidth( w ); 0067 m_pProgressBar->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); 0068 0069 // Determine maximumHeight from the progressbar's height and scale the icon. 0070 // This operation is style specific and cannot infer the style in use 0071 // from Q_OS_??? because users can have started us using the -style option 0072 // (or even be using an unexpected QPA). 0073 // In most cases, maximumHeight should be set to fontMetrics().height() + 2 0074 // (Breeze, Oxygen, Fusion, Windows, QtCurve etc.); this corresponds to the actual 0075 // progressbar height plus a 1 pixel margin above and below. 0076 int maximumHeight = m_pProgressBar->fontMetrics().height() + 2; 0077 const bool isMacWidgetStyle = QApplication::style()->objectName() == QLatin1String( "macintosh" ); 0078 0079 if ( isMacWidgetStyle && !smallIcon.isNull() ) { 0080 // QProgressBar height is fixed with the macintosh native widget style 0081 // and alignment with m_pButton is tricky. Sizing the icon to maximumHeight 0082 // gives a button that is slightly too high and not perfectly 0083 // aligned. Annoyingly that doesn't improve by calling setMaximumHeight() 0084 // which even causes the button to change shape. So we use a "flat" button, 0085 // an invisible outline which is more in line with platform practices anyway. 0086 maximumHeight = m_pProgressBar->sizeHint().height(); 0087 iconSize.scale( maximumHeight, maximumHeight, Qt::KeepAspectRatio ); 0088 } else { 0089 // The icon is scaled to maximumHeight but with 1 pixel margins on each side 0090 // because it will be in a visible button. 0091 iconSize.scale( maximumHeight - 2, maximumHeight - 2, Qt::KeepAspectRatio ); 0092 // additional adjustments: 0093 m_pButton->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); 0094 } 0095 stack->setMaximumHeight( maximumHeight ); 0096 m_pButton->setIconSize( iconSize ); 0097 box->addWidget( m_pButton ); 0098 0099 m_pButton->setToolTip( i18nc("@info:tooltip", "Open detailed progress dialog") ); 0100 0101 box->addWidget( stack ); 0102 0103 stack->insertWidget( 1, m_pProgressBar ); 0104 0105 if (m_bShowButton) { 0106 // create an empty, inactive QToolButton that's as high as m_pButton but only 1 pixel wide 0107 // this will act as a placeholder when the widget is invisible. 0108 m_pPlaceHolder.button = new QToolButton(this); 0109 m_pPlaceHolder.button->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, 0110 QSizePolicy::Fixed ) ); 0111 m_pPlaceHolder.button->setMinimumHeight(m_pButton->minimumSizeHint().height()); 0112 m_pPlaceHolder.button->setMaximumWidth(1); 0113 m_pPlaceHolder.button->setAutoRaise(true); 0114 m_pPlaceHolder.button->setAttribute( Qt::WA_LayoutUsesWidgetRect, true ); 0115 m_pPlaceHolder.button->setEnabled(false); 0116 m_pPlaceHolder.button->installEventFilter( this ); 0117 // the placeholder button should not go into the stack to avoid misalignment 0118 box->addWidget( m_pPlaceHolder.button ); 0119 m_pPlaceHolder.button->hide(); 0120 } else { 0121 // when the widget doesn't show m_pButton we can use a QLabel as the placeholder. 0122 m_pPlaceHolder.label = new QLabel( QString(), this ); 0123 m_pPlaceHolder.label->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, 0124 QSizePolicy::Fixed ) ); 0125 m_pPlaceHolder.label->setAlignment( Qt::AlignHCenter ); 0126 m_pPlaceHolder.label->installEventFilter( this ); 0127 m_pPlaceHolder.label->setMinimumWidth( w ); 0128 m_pPlaceHolder.label->setMaximumHeight( maximumHeight ); 0129 stack->insertWidget( 2, m_pPlaceHolder.label ); 0130 } 0131 0132 setMinimumWidth( minimumSizeHint().width() ); 0133 0134 mode = None; 0135 setMode(); 0136 0137 connect( m_pButton, &QPushButton::clicked, 0138 progressDialog, &ProgressDialog::slotToggleVisibility ); 0139 0140 connect ( ProgressManager::instance(), &ProgressManager::progressItemAdded, 0141 this, &StatusbarProgressWidget::slotProgressItemAdded ); 0142 connect ( ProgressManager::instance(), &ProgressManager::progressItemCompleted, 0143 this, &StatusbarProgressWidget::slotProgressItemCompleted ); 0144 connect ( ProgressManager::instance(), &ProgressManager::progressItemUsesBusyIndicator, 0145 this, &StatusbarProgressWidget::updateBusyMode ); 0146 0147 connect ( progressDialog, &ProgressDialog::visibilityChanged, 0148 this, &StatusbarProgressWidget::slotProgressDialogVisible ); 0149 0150 mDelayTimer = new QTimer( this ); 0151 mDelayTimer->setSingleShot( true ); 0152 mDelayTimer->setInterval(1000); 0153 connect ( mDelayTimer, &QTimer::timeout, 0154 this, &StatusbarProgressWidget::slotShowItemDelayed ); 0155 0156 mCleanTimer = new QTimer( this ); 0157 mCleanTimer->setSingleShot( true ); 0158 mCleanTimer->setInterval(5000); 0159 connect ( mCleanTimer, &QTimer::timeout, 0160 this, &StatusbarProgressWidget::slotClean ); 0161 } 0162 0163 // There are three cases: no progressitem, one progressitem (connect to it directly), 0164 // or many progressitems (display busy indicator). Let's call them 0,1,N. 0165 // In slot..Added we can only end up in 1 or N. 0166 // In slot..Removed we can end up in 0, 1, or we can stay in N if we were already. 0167 0168 void StatusbarProgressWidget::updateBusyMode() 0169 { 0170 connectSingleItem(); // if going to 1 item 0171 if (!mDelayTimer->isActive()) 0172 mDelayTimer->start(); 0173 } 0174 0175 void StatusbarProgressWidget::slotProgressItemAdded( ProgressItem *item ) 0176 { 0177 if ( item->parent() ) 0178 return; // we are only interested in top level items 0179 0180 updateBusyMode(); 0181 } 0182 0183 void StatusbarProgressWidget::slotProgressItemCompleted( ProgressItem *item ) 0184 { 0185 if ( item->parent() ) { 0186 item->deleteLater(); 0187 item = nullptr; 0188 return; // we are only interested in top level items 0189 } 0190 0191 item->deleteLater(); 0192 item = nullptr; 0193 0194 connectSingleItem(); // if going back to 1 item 0195 if ( ProgressManager::instance()->isEmpty() ) { // No item 0196 // If the progress manager doesn't have other progress items, set the progress to 100% 0197 // to indicate completion. Otherwise, if @p item uses a busy indicator, or had been running 0198 // for less than a second and was preceded by a different item that uses a busy indicator, 0199 // we could be showing a busy indicator for 5 seconds without any task in progress. 0200 activateSingleItemMode( 100 ); 0201 // Done. In 5s the progress-widget will close, then we can clean up the statusbar 0202 mCleanTimer->start(); 0203 } else if ( mCurrentItem ) { // Exactly one item 0204 activateSingleItemMode(); 0205 } 0206 } 0207 0208 void StatusbarProgressWidget::connectSingleItem() 0209 { 0210 auto* const singleItem = ProgressManager::instance()->singleItem(); 0211 if ( singleItem == mCurrentItem ) { 0212 return; // No need to waste time reconnecting the same signal/slot pair. 0213 } 0214 0215 if ( mCurrentItem ) { 0216 disconnect ( mCurrentItem, &ProgressItem::progressItemProgress, 0217 this, &StatusbarProgressWidget::slotProgressItemProgress ); 0218 mCurrentItem = nullptr; 0219 } 0220 mCurrentItem = singleItem; 0221 if ( mCurrentItem ) { 0222 connect ( mCurrentItem, &ProgressItem::progressItemProgress, 0223 this, &StatusbarProgressWidget::slotProgressItemProgress ); 0224 } 0225 } 0226 0227 void StatusbarProgressWidget::activateSingleItemMode() 0228 { 0229 activateSingleItemMode( mCurrentItem->progress() ); 0230 } 0231 0232 void StatusbarProgressWidget::activateSingleItemMode( unsigned int progress ) 0233 { 0234 m_pProgressBar->setMaximum( 100 ); 0235 m_pProgressBar->setValue( progress ); 0236 m_pProgressBar->setTextVisible( true ); 0237 #ifdef Q_OS_OSX 0238 MacDockProgressView::setRange( 0, 100 ); 0239 MacDockProgressView::setProgress( progress ); 0240 #endif 0241 } 0242 0243 void StatusbarProgressWidget::slotShowItemDelayed() 0244 { 0245 bool noItems = ProgressManager::instance()->isEmpty(); 0246 if ( mCurrentItem ) { 0247 activateSingleItemMode(); 0248 } else if ( !noItems ) { // N items 0249 m_pProgressBar->setMaximum( 0 ); 0250 m_pProgressBar->setTextVisible( false ); 0251 #ifdef Q_OS_OSX 0252 MacDockProgressView::setRange( 0, 0 ); 0253 MacDockProgressView::setProgress( 0 ); 0254 #endif 0255 } 0256 0257 if ( !noItems && mode == None ) { 0258 mode = Progress; 0259 setMode(); 0260 } 0261 } 0262 0263 void StatusbarProgressWidget::slotProgressItemProgress( ProgressItem *item, unsigned int value ) 0264 { 0265 Q_ASSERT( item == mCurrentItem); // the only one we should be connected to 0266 Q_UNUSED( item ); 0267 m_pProgressBar->setValue( value ); 0268 #ifdef Q_OS_OSX 0269 MacDockProgressView::setProgress( value ); 0270 #endif 0271 } 0272 0273 void StatusbarProgressWidget::setMode() { 0274 switch ( mode ) { 0275 case None: 0276 m_pButton->hide(); 0277 if ( m_bShowButton ) { 0278 // show the empty button in order to make the status bar look better 0279 m_pPlaceHolder.button->show(); 0280 } else { 0281 // show the empty label in order to make the status bar look better 0282 stack->setCurrentWidget( m_pPlaceHolder.label ); 0283 } 0284 m_pProgressBar->hide(); 0285 stack->show(); 0286 #ifdef Q_OS_OSX 0287 MacDockProgressView::setProgressVisible( false ); 0288 #endif 0289 break; 0290 0291 case Progress: 0292 stack->show(); 0293 m_pProgressBar->show(); 0294 stack->setCurrentWidget( m_pProgressBar ); 0295 if ( m_bShowButton ) { 0296 m_pButton->show(); 0297 m_pPlaceHolder.button->hide(); 0298 } 0299 #ifdef Q_OS_OSX 0300 MacDockProgressView::setProgressVisible( true ); 0301 #endif 0302 break; 0303 } 0304 } 0305 0306 void StatusbarProgressWidget::slotClean() 0307 { 0308 // check if a new item showed up since we started the timer. If not, clear 0309 if ( ProgressManager::instance()->isEmpty() ) { 0310 m_pProgressBar->setValue( 0 ); 0311 //m_pPlaceHolder.label->clear(); 0312 mode = None; 0313 setMode(); 0314 } 0315 } 0316 0317 bool StatusbarProgressWidget::eventFilter(QObject* object, QEvent* ev) 0318 { 0319 if ( ev->type() == QEvent::MouseButtonPress ) { 0320 auto* e = static_cast<QMouseEvent*>(ev); 0321 0322 if ( e->button() == Qt::LeftButton && mode != None ) { // toggle view on left mouse button 0323 // Consensus seems to be that we should show/hide the fancy dialog when the user 0324 // clicks anywhere in the small one. 0325 mProgressDialog->slotToggleVisibility(); 0326 return true; 0327 } 0328 } 0329 return QFrame::eventFilter(object, ev); 0330 } 0331 0332 void StatusbarProgressWidget::slotProgressDialogVisible( bool b ) 0333 { 0334 // Update the hide/show button when the detailed one is shown/hidden 0335 if ( b ) { 0336 m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-down") ) ); 0337 m_pButton->setToolTip( i18nc("@info:tooltip", "Hide detailed progress window") ); 0338 setMode(); 0339 } else { 0340 m_pButton->setIcon( QIcon::fromTheme( QStringLiteral("go-up") ) ); 0341 m_pButton->setToolTip( i18nc("@info:tooltip", "Show detailed progress window") ); 0342 } 0343 } 0344 0345 #include "moc_statusbarprogresswidget.cpp"