QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 
36 QgsMessageBar::QgsMessageBar( QWidget *parent )
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 
111 void 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 
128 void 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 
195 void QgsMessageBar::pushSuccess( const QString &title, const QString &message )
196 {
197  pushMessage( title, message, Qgis::MessageLevel::Success );
198 }
199 
200 void QgsMessageBar::pushInfo( const QString &title, const QString &message )
201 {
202  pushMessage( title, message, Qgis::MessageLevel::Info );
203 }
204 
205 void QgsMessageBar::pushWarning( const QString &title, const QString &message )
206 {
207  pushMessage( title, message, Qgis::MessageLevel::Warning );
208 }
209 
210 void 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 
235 void 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 
279 void 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 
319 QgsMessageBarItem *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 
335 void 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 
348 void 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 
384 QList<QgsMessageBarItem *> QgsMessageBar::items()
385 {
386  return mItems;
387 }
388 
389 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &text, QWidget *parent )
390 {
391  QgsMessageBarItem *item = new QgsMessageBarItem( text, Qgis::MessageLevel::Info, 0, parent );
392  return item;
393 }
394 
395 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &title, const QString &text, QWidget *parent )
396 {
397  return new QgsMessageBarItem( title, text, Qgis::MessageLevel::Info, 0, parent );
398 }
399 
400 QgsMessageBarItem *QgsMessageBar::createMessage( QWidget *widget, QWidget *parent )
401 {
402  return new QgsMessageBarItem( widget, Qgis::MessageLevel::Info, 0, parent );
403 }
404 
405 void QgsMessageBar::pushMessage( const QString &text, Qgis::MessageLevel level, int duration )
406 {
407  pushMessage( QString(), text, level, duration );
408 }
409 
410 void 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 
427 void 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 
436 void 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:106
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:1040
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)
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,...