QGIS API Documentation 3.29.0-Master (006c3c0232)
qgsmessagebar.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmessagebar.cpp - description
3 -------------------
4 begin : June 2012
5 copyright : (C) 2012 by Giuseppe Sucameli
6 email : sucameli at faunalia dot it
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsmessagebar.h"
19#include "qgsmessagebaritem.h"
20#include "qgsapplication.h"
21#include "qgsmessagelog.h"
22#include "qgsmessageviewer.h"
23#include "qgssettings.h"
24
25#include <QWidget>
26#include <QPalette>
27#include <QStackedWidget>
28#include <QProgressBar>
29#include <QToolButton>
30#include <QTimer>
31#include <QGridLayout>
32#include <QMenu>
33#include <QMouseEvent>
34#include <QLabel>
35
37 : QFrame( parent )
38
39{
40 QPalette pal = palette();
41 pal.setBrush( backgroundRole(), pal.window() );
42 setPalette( pal );
43 setAutoFillBackground( true );
44 setFrameShape( QFrame::StyledPanel );
45 setFrameShadow( QFrame::Plain );
46
47 mLayout = new QGridLayout( this );
48 const int xMargin = std::max( 9.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.45 );
49 const int yMargin = std::max( 1.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.05 );
50 mLayout->setContentsMargins( xMargin, yMargin, xMargin, yMargin );
51 setLayout( mLayout );
52
53 mCountProgress = new QProgressBar( this );
54 mCountStyleSheet = QString( "QProgressBar { border: 1px solid rgba(0, 0, 0, 75%);"
55 " border-radius: 2px; background: rgba(0, 0, 0, 0);"
56 " image: url(:/images/themes/default/%1) }"
57 "QProgressBar::chunk { background-color: rgba(0, 0, 0, 30%); width: 5px; }" );
58
59 mCountProgress->setStyleSheet( mCountStyleSheet.arg( QLatin1String( "mIconTimerPause.svg" ) ) );
60 mCountProgress->setObjectName( QStringLiteral( "mCountdown" ) );
61 const int barWidth = std::max( 25.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.25 );
62 const int barHeight = std::max( 14.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.7 );
63 mCountProgress->setFixedSize( barWidth, barHeight );
64 mCountProgress->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
65 mCountProgress->setTextVisible( false );
66 mCountProgress->setRange( 0, 5 );
67 mCountProgress->setHidden( true );
68 mLayout->addWidget( mCountProgress, 0, 0, 1, 1 );
69
70 mItemCount = new QLabel( this );
71 mItemCount->setObjectName( QStringLiteral( "mItemCount" ) );
72 mItemCount->setToolTip( tr( "Remaining messages" ) );
73 mItemCount->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
74 mLayout->addWidget( mItemCount, 0, 2, 1, 1 );
75
76 mCloseMenu = new QMenu( this );
77 mCloseMenu->setObjectName( QStringLiteral( "mCloseMenu" ) );
78 mActionCloseAll = new QAction( tr( "Close All" ), this );
79 mCloseMenu->addAction( mActionCloseAll );
80 connect( mActionCloseAll, &QAction::triggered, this, &QgsMessageBar::clearWidgets );
81
82 mCloseBtn = new QToolButton( this );
83 mCloseMenu->setObjectName( QStringLiteral( "mCloseMenu" ) );
84 mCloseBtn->setToolTip( tr( "Close" ) );
85 mCloseBtn->setMinimumWidth( QgsGuiUtils::scaleIconSize( 44 ) );
86 mCloseBtn->setStyleSheet(
87 "QToolButton { border:none; background-color: rgba(0, 0, 0, 0); }"
88 "QToolButton::menu-button { border:none; background-color: rgba(0, 0, 0, 0); }" );
89 mCloseBtn->setCursor( Qt::PointingHandCursor );
90 mCloseBtn->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconClose.svg" ) ) );
91
92 const int iconSize = std::max( 18.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.9 );
93 mCloseBtn->setIconSize( QSize( iconSize, iconSize ) );
94 mCloseBtn->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum );
95 mCloseBtn->setMenu( mCloseMenu );
96 mCloseBtn->setPopupMode( QToolButton::MenuButtonPopup );
97 connect( mCloseBtn, &QAbstractButton::clicked, this, static_cast < bool ( QgsMessageBar::* )() > ( &QgsMessageBar::popWidget ) );
98 mLayout->addWidget( mCloseBtn, 0, 3, 1, 1 );
99
100 mCountdownTimer = new QTimer( this );
101 mCountdownTimer->setInterval( 1000 );
102 connect( mCountdownTimer, &QTimer::timeout, this, &QgsMessageBar::updateCountdown );
103
104 connect( this, &QgsMessageBar::widgetAdded, this, &QgsMessageBar::updateItemCount );
105 connect( this, &QgsMessageBar::widgetRemoved, this, &QgsMessageBar::updateItemCount );
106
107 // start hidden
108 setVisible( false );
109}
110
111void QgsMessageBar::mousePressEvent( QMouseEvent *e )
112{
113 if ( mCountProgress == childAt( e->pos() ) && e->button() == Qt::LeftButton )
114 {
115 if ( mCountdownTimer->isActive() )
116 {
117 mCountdownTimer->stop();
118 mCountProgress->setStyleSheet( mCountStyleSheet.arg( QLatin1String( "mIconTimerContinue.svg" ) ) );
119 }
120 else
121 {
122 mCountdownTimer->start();
123 mCountProgress->setStyleSheet( mCountStyleSheet.arg( QLatin1String( "mIconTimerPause.svg" ) ) );
124 }
125 }
126}
127
128void QgsMessageBar::popItem( QgsMessageBarItem *item )
129{
130 Q_ASSERT( item );
131
132 if ( !mItems.contains( item ) )
133 return;
134
135 if ( item == mItems.at( 0 ) )
136 {
137 mItems.removeOne( item );
138 mLayout->removeWidget( item );
139 item->hide();
140 disconnect( item, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
141 item->deleteLater();
142
143 if ( !mItems.isEmpty() )
144 {
145 showItem( mItems.at( 0 ) );
146 }
147 else
148 {
149 hide();
150 }
151 }
152 else
153 {
154 mItems.removeOne( item );
155 item->deleteLater();
156 }
157
158 emit widgetRemoved( item );
159}
160
162{
163 if ( !item || !mItems.contains( item ) )
164 return false;
165
166 popItem( item );
167 return true;
168}
169
171{
172 if ( mItems.empty() )
173 return false;
174
175 resetCountdown();
176
177 popItem( mItems.at( 0 ) );
178
179 return true;
180}
181
183{
184 if ( mItems.empty() )
185 return true;
186
187 while ( !mItems.isEmpty() )
188 {
189 popWidget();
190 }
191
192 return true;
193}
194
195void QgsMessageBar::pushSuccess( const QString &title, const QString &message )
196{
197 pushMessage( title, message, Qgis::MessageLevel::Success );
198}
199
200void QgsMessageBar::pushInfo( const QString &title, const QString &message )
201{
202 pushMessage( title, message, Qgis::MessageLevel::Info );
203}
204
205void QgsMessageBar::pushWarning( const QString &title, const QString &message )
206{
207 pushMessage( title, message, Qgis::MessageLevel::Warning );
208}
209
210void QgsMessageBar::pushCritical( const QString &title, const QString &message )
211{
212 pushMessage( title, message, Qgis::MessageLevel::Critical );
213}
214
216{
217 // critical/warning messages don't auto dismiss by default
218 switch ( level )
219 {
220 case Qgis::MessageLevel::Success:
221 case Qgis::MessageLevel::Info:
222 case Qgis::MessageLevel::NoLevel:
223 {
224 const QgsSettings settings;
225 return settings.value( QStringLiteral( "qgis/messageTimeout" ), 5 ).toInt();
226 }
227
228 case Qgis::MessageLevel::Warning:
229 case Qgis::MessageLevel::Critical:
230 return 0;
231 }
232 return 0;
233}
234
235void QgsMessageBar::showItem( QgsMessageBarItem *item )
236{
237 Q_ASSERT( item );
238
239 if ( !mItems.empty() )
240 disconnect( mItems.at( 0 ), &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
241
242 if ( mItems.count() >= MAX_ITEMS )
243 removeLowestPriorityOldestItem();
244
245 if ( !mItems.empty() )
246 {
247 mLayout->removeWidget( mItems.at( 0 ) );
248 mItems.at( 0 )->hide();
249 }
250
251 if ( mItems.contains( item ) )
252 mItems.removeOne( item );
253 mItems.prepend( item );
254
255 mLayout->addWidget( item, 0, 1, 1, 1 );
256 item->show();
257
258 if ( item->duration() > 0 )
259 {
260 mCountProgress->setRange( 0, item->duration() );
261 mCountProgress->setValue( item->duration() );
262 mCountProgress->setVisible( true );
263 mCountdownTimer->start();
264 }
265
266 connect( item, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
267
268 if ( item->level() != mPrevLevel )
269 {
270 setStyleSheet( item->getStyleSheet() );
271 mPrevLevel = item->level();
272 }
273
274 show();
275
276 emit widgetAdded( item );
277}
278
279void QgsMessageBar::removeLowestPriorityOldestItem()
280{
281 for ( const Qgis::MessageLevel level : { Qgis::MessageLevel::Success, Qgis::MessageLevel::Info, Qgis::MessageLevel::Warning, Qgis::MessageLevel::Critical } )
282 {
283 for ( int i = mItems.size() - 1; i >= 0; --i )
284 {
285 QgsMessageBarItem *item = mItems.at( i );
286 if ( item->level() == level )
287 {
288 popItem( item );
289 return;
290 }
291 }
292 }
293}
294
296{
297 resetCountdown();
298
299 item->mMessageBar = this;
300
301 // avoid duplicated widget
302 popWidget( item );
303 showItem( item );
304
305 // Log all (non-empty) messages that are sent to the message bar into the message log so the
306 // user can get them back easier.
307 QString formattedTitle;
308 if ( !item->title().isEmpty() && !item->text().isEmpty() )
309 formattedTitle = QStringLiteral( "%1 : %2" ).arg( item->title(), item->text() );
310 else if ( !item->title().isEmpty() )
311 formattedTitle = item->title();
312 else if ( !item->text().isEmpty() )
313 formattedTitle = item->text();
314
315 if ( !formattedTitle.isEmpty() )
316 QgsMessageLog::logMessage( formattedTitle, tr( "Messages" ), item->level() );
317}
318
319QgsMessageBarItem *QgsMessageBar::pushWidget( QWidget *widget, Qgis::MessageLevel level, int duration )
320{
321 QgsMessageBarItem *item = nullptr;
322 item = dynamic_cast<QgsMessageBarItem *>( widget );
323 if ( item )
324 {
325 item->setLevel( level )->setDuration( duration );
326 }
327 else
328 {
329 item = new QgsMessageBarItem( widget, level, duration );
330 }
331 pushItem( item );
332 return item;
333}
334
335void QgsMessageBar::pushMessage( const QString &title, const QString &text, Qgis::MessageLevel level, int duration )
336{
337 // block duplicate items, avoids flooding (and freezing) of the main window
338 for ( auto it = mItems.constBegin(); it != mItems.constEnd(); ++it )
339 {
340 if ( level == ( *it )->level() && title == ( *it )->title() && text == ( *it )->text() )
341 return;
342 }
343
344 QgsMessageBarItem *item = new QgsMessageBarItem( title, text, level, duration );
345 pushItem( item );
346}
347
348void QgsMessageBar::pushMessage( const QString &title, const QString &text, const QString &showMore, Qgis::MessageLevel level, int duration )
349{
351 mv->setWindowTitle( title );
352 mv->setMessageAsPlainText( text + "\n\n" + showMore );
353
354 QToolButton *showMoreButton = new QToolButton();
355 QAction *act = new QAction( showMoreButton );
356 act->setText( tr( "Show more" ) );
357 showMoreButton->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
358 showMoreButton->setCursor( Qt::PointingHandCursor );
359 showMoreButton->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
360 showMoreButton->addAction( act );
361 showMoreButton->setDefaultAction( act );
362 connect( showMoreButton, &QToolButton::triggered, mv, &QDialog::exec );
363 connect( showMoreButton, &QToolButton::triggered, showMoreButton, &QObject::deleteLater );
364
365 if ( duration < 0 )
366 {
367 duration = defaultMessageTimeout( level );
368 }
369
371 title,
372 text,
373 showMoreButton,
374 level,
375 duration );
376 pushItem( item );
377}
378
380{
381 return mItems.value( 0 );
382}
383
384QList<QgsMessageBarItem *> QgsMessageBar::items()
385{
386 return mItems;
387}
388
389QgsMessageBarItem *QgsMessageBar::createMessage( const QString &text, QWidget *parent )
390{
391 QgsMessageBarItem *item = new QgsMessageBarItem( text, Qgis::MessageLevel::Info, 0, parent );
392 return item;
393}
394
395QgsMessageBarItem *QgsMessageBar::createMessage( const QString &title, const QString &text, QWidget *parent )
396{
397 return new QgsMessageBarItem( title, text, Qgis::MessageLevel::Info, 0, parent );
398}
399
400QgsMessageBarItem *QgsMessageBar::createMessage( QWidget *widget, QWidget *parent )
401{
402 return new QgsMessageBarItem( widget, Qgis::MessageLevel::Info, 0, parent );
403}
404
405void QgsMessageBar::pushMessage( const QString &text, Qgis::MessageLevel level, int duration )
406{
407 pushMessage( QString(), text, level, duration );
408}
409
410void QgsMessageBar::updateCountdown()
411{
412 if ( !mCountdownTimer->isActive() )
413 {
414 resetCountdown();
415 return;
416 }
417 if ( mCountProgress->value() < 2 )
418 {
419 popWidget();
420 }
421 else
422 {
423 mCountProgress->setValue( mCountProgress->value() - 1 );
424 }
425}
426
427void QgsMessageBar::resetCountdown()
428{
429 if ( mCountdownTimer->isActive() )
430 mCountdownTimer->stop();
431
432 mCountProgress->setStyleSheet( mCountStyleSheet.arg( QLatin1String( "mIconTimerPause.svg" ) ) );
433 mCountProgress->setVisible( false );
434}
435
436void QgsMessageBar::updateItemCount()
437{
438 const bool moreMessages = mItems.count() > 1;
439 mItemCount->setText( moreMessages ? tr( "%n more", "unread messages", mItems.count() - 1 ) : QString() );
440
441 // do not show the down arrow for opening menu with "close all" if there is just one message
442 mCloseBtn->setMenu( moreMessages ? mCloseMenu : nullptr );
443 mCloseBtn->setPopupMode( moreMessages ? QToolButton::MenuButtonPopup : QToolButton::DelayedPopup );
444}
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:115
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2686
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Represents an item shown within a QgsMessageBar widget.
void styleChanged(const QString &styleSheet)
Emitted when the item's message level has changed and the message bar style will need to be updated a...
QgsMessageBarItem * setLevel(Qgis::MessageLevel level)
Sets the message level for the item, which controls how the message bar is styled when the item is di...
int duration() const
Returns the duration (in seconds) of the message.
QString getStyleSheet()
Returns the styleSheet which should be used to style a QgsMessageBar object when this item is display...
QString text() const
Returns the text for the message.
QString title() const
Returns the title for the message.
Qgis::MessageLevel level() const
Returns the message level for the message.
QgsMessageBarItem * setDuration(int duration)
Sets the duration (in seconds) to show the message for.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
static int defaultMessageTimeout(Qgis::MessageLevel level=Qgis::MessageLevel::NoLevel)
Returns the default timeout in seconds for timed messages of the specified level.
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=-1)
A convenience method for pushing a message with the specified text to the bar.
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar, after hiding the currently visible one and putting it in a stack.
void widgetAdded(QgsMessageBarItem *item)
Emitted whenever an item is added to the bar.
QgsMessageBarItem * currentItem()
Returns the current visible item, or nullptr if no item is shown.
void widgetRemoved(QgsMessageBarItem *item)
Emitted whenever an item was removed from the bar.
QgsMessageBarItem * pushWidget(QWidget *widget, Qgis::MessageLevel level=Qgis::MessageLevel::Info, int duration=0)
Display a widget as a message on the bar, after hiding the currently visible one and putting it in a ...
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
Creates message bar item widget containing a message text to be displayed on the bar.
void pushSuccess(const QString &title, const QString &message)
Pushes a success message with default timeout to the message bar.
QgsMessageBar(QWidget *parent=nullptr)
Constructor for QgsMessageBar.
bool popWidget()
Remove the currently displayed item from the bar and display the next item in the stack.
void pushCritical(const QString &title, const QString &message)
Pushes a critical warning message that must be manually dismissed by the user.
QList< QgsMessageBarItem * > items()
Returns a list of all items currently visible or queued for the bar.
bool clearWidgets()
Removes all items from the bar.
void pushInfo(const QString &title, const QString &message)
Pushes a information message with default timeout to the message bar.
void mousePressEvent(QMouseEvent *e) override
void pushWarning(const QString &title, const QString &message)
Pushes a warning message that must be manually dismissed by the user.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A generic message view for displaying QGIS messages.
void setMessageAsPlainText(const QString &msg)
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...