QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
24 #include <QWidget>
25 #include <QPalette>
26 #include <QStackedWidget>
27 #include <QProgressBar>
28 #include <QToolButton>
29 #include <QTimer>
30 #include <QGridLayout>
31 #include <QMenu>
32 #include <QMouseEvent>
33 #include <QLabel>
34 
35 QgsMessageBar::QgsMessageBar( QWidget *parent )
36  : QFrame( parent )
37 
38 {
39  QPalette pal = palette();
40  pal.setBrush( backgroundRole(), pal.window() );
41  setPalette( pal );
42  setAutoFillBackground( true );
43  setFrameShape( QFrame::StyledPanel );
44  setFrameShadow( QFrame::Plain );
45 
46  mLayout = new QGridLayout( this );
47  const int xMargin = std::max( 9.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.45 );
48  const int yMargin = std::max( 1.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.05 );
49  mLayout->setContentsMargins( xMargin, yMargin, xMargin, yMargin );
50  setLayout( mLayout );
51 
52  mCountProgress = new QProgressBar( this );
53  mCountStyleSheet = QString( "QProgressBar { border: 1px solid rgba(0, 0, 0, 75%);"
54  " border-radius: 2px; background: rgba(0, 0, 0, 0);"
55  " image: url(:/images/themes/default/%1) }"
56  "QProgressBar::chunk { background-color: rgba(0, 0, 0, 30%); width: 5px; }" );
57 
58  mCountProgress->setStyleSheet( mCountStyleSheet.arg( QStringLiteral( "mIconTimerPause.svg" ) ) );
59  mCountProgress->setObjectName( QStringLiteral( "mCountdown" ) );
60  const int barWidth = std::max( 25.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.25 );
61  const int barHeight = std::max( 14.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.7 );
62  mCountProgress->setFixedSize( barWidth, barHeight );
63  mCountProgress->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
64  mCountProgress->setTextVisible( false );
65  mCountProgress->setRange( 0, 5 );
66  mCountProgress->setHidden( true );
67  mLayout->addWidget( mCountProgress, 0, 0, 1, 1 );
68 
69  mItemCount = new QLabel( this );
70  mItemCount->setObjectName( QStringLiteral( "mItemCount" ) );
71  mItemCount->setToolTip( tr( "Remaining messages" ) );
72  mItemCount->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
73  mLayout->addWidget( mItemCount, 0, 2, 1, 1 );
74 
75  mCloseMenu = new QMenu( this );
76  mCloseMenu->setObjectName( QStringLiteral( "mCloseMenu" ) );
77  mActionCloseAll = new QAction( tr( "Close All" ), this );
78  mCloseMenu->addAction( mActionCloseAll );
79  connect( mActionCloseAll, &QAction::triggered, this, &QgsMessageBar::clearWidgets );
80 
81  mCloseBtn = new QToolButton( this );
82  mCloseMenu->setObjectName( QStringLiteral( "mCloseMenu" ) );
83  mCloseBtn->setToolTip( tr( "Close" ) );
84  mCloseBtn->setMinimumWidth( 40 );
85  mCloseBtn->setStyleSheet(
86  "QToolButton { border:none; background-color: rgba(0, 0, 0, 0); }"
87  "QToolButton::menu-button { border:none; background-color: rgba(0, 0, 0, 0); }" );
88  mCloseBtn->setCursor( Qt::PointingHandCursor );
89  mCloseBtn->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconClose.svg" ) ) );
90 
91  const int iconSize = std::max( 18.0, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 0.9 );
92  mCloseBtn->setIconSize( QSize( iconSize, iconSize ) );
93  mCloseBtn->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum );
94  mCloseBtn->setMenu( mCloseMenu );
95  mCloseBtn->setPopupMode( QToolButton::MenuButtonPopup );
96  connect( mCloseBtn, &QAbstractButton::clicked, this, static_cast < bool ( QgsMessageBar::* )() > ( &QgsMessageBar::popWidget ) );
97  mLayout->addWidget( mCloseBtn, 0, 3, 1, 1 );
98 
99  mCountdownTimer = new QTimer( this );
100  mCountdownTimer->setInterval( 1000 );
101  connect( mCountdownTimer, &QTimer::timeout, this, &QgsMessageBar::updateCountdown );
102 
103  connect( this, &QgsMessageBar::widgetAdded, this, &QgsMessageBar::updateItemCount );
104  connect( this, &QgsMessageBar::widgetRemoved, this, &QgsMessageBar::updateItemCount );
105 
106  // start hidden
107  setVisible( false );
108 }
109 
110 void QgsMessageBar::mousePressEvent( QMouseEvent *e )
111 {
112  if ( mCountProgress == childAt( e->pos() ) && e->button() == Qt::LeftButton )
113  {
114  if ( mCountdownTimer->isActive() )
115  {
116  mCountdownTimer->stop();
117  mCountProgress->setStyleSheet( mCountStyleSheet.arg( QStringLiteral( "mIconTimerContinue.svg" ) ) );
118  }
119  else
120  {
121  mCountdownTimer->start();
122  mCountProgress->setStyleSheet( mCountStyleSheet.arg( QStringLiteral( "mIconTimerPause.svg" ) ) );
123  }
124  }
125 }
126 
127 void QgsMessageBar::popItem( QgsMessageBarItem *item )
128 {
129  Q_ASSERT( item );
130 
131  if ( item != mCurrentItem && !mItems.contains( item ) )
132  return;
133 
134  if ( item == mCurrentItem )
135  {
136  mLayout->removeWidget( mCurrentItem );
137  mCurrentItem->hide();
138  disconnect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
139  mCurrentItem->deleteLater();
140  mCurrentItem = nullptr;
141 
142  if ( !mItems.isEmpty() )
143  {
144  showItem( mItems.at( 0 ) );
145  }
146  else
147  {
148  hide();
149  }
150  }
151  else
152  {
153  mItems.removeOne( item );
154  }
155 
156  emit widgetRemoved( item );
157 }
158 
160 {
161  if ( !item || !mCurrentItem )
162  return false;
163 
164  if ( item == mCurrentItem )
165  {
166  popItem( mCurrentItem );
167  return true;
168  }
169 
170  for ( QgsMessageBarItem *existingItem : qgis::as_const( mItems ) )
171  {
172  if ( existingItem == item )
173  {
174  mItems.removeOne( existingItem );
175  existingItem->deleteLater();
176  return true;
177  }
178  }
179 
180  return false;
181 }
182 
184 {
185  if ( !mCurrentItem )
186  return false;
187 
188  resetCountdown();
189 
190  QgsMessageBarItem *item = mCurrentItem;
191  popItem( item );
192 
193  return true;
194 }
195 
197 {
198  if ( !mCurrentItem && mItems.empty() )
199  return true;
200 
201  while ( !mItems.isEmpty() )
202  {
203  popWidget();
204  }
205  popWidget();
206 
207  return !mCurrentItem && mItems.empty();
208 }
209 
210 void QgsMessageBar::pushSuccess( const QString &title, const QString &message )
211 {
212  pushMessage( title, message, Qgis::Success );
213 }
214 
215 void QgsMessageBar::pushInfo( const QString &title, const QString &message )
216 {
217  pushMessage( title, message, Qgis::Info );
218 }
219 
220 void QgsMessageBar::pushWarning( const QString &title, const QString &message )
221 {
222  pushMessage( title, message, Qgis::Warning );
223 }
224 
225 void QgsMessageBar::pushCritical( const QString &title, const QString &message )
226 {
227  pushMessage( title, message, Qgis::Critical );
228 }
229 
230 void QgsMessageBar::showItem( QgsMessageBarItem *item )
231 {
232  Q_ASSERT( item );
233 
234  if ( mCurrentItem )
235  disconnect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
236 
237  if ( item == mCurrentItem )
238  return;
239 
240  if ( mItems.contains( item ) )
241  mItems.removeOne( item );
242 
243  if ( mCurrentItem )
244  {
245  mItems.prepend( mCurrentItem );
246  mLayout->removeWidget( mCurrentItem );
247  mCurrentItem->hide();
248  }
249 
250  mCurrentItem = item;
251  mLayout->addWidget( item, 0, 1, 1, 1 );
252  mCurrentItem->show();
253 
254  if ( item->duration() > 0 )
255  {
256  mCountProgress->setRange( 0, item->duration() );
257  mCountProgress->setValue( item->duration() );
258  mCountProgress->setVisible( true );
259  mCountdownTimer->start();
260  }
261 
262  connect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
263  setStyleSheet( item->getStyleSheet() );
264  show();
265 
266  emit widgetAdded( item );
267 }
268 
270 {
271  resetCountdown();
272 
273  item->mMessageBar = this;
274 
275  // avoid duplicated widget
276  popWidget( item );
277  showItem( item );
278 
279  // Log all (non-empty) messages that are sent to the message bar into the message log so the
280  // user can get them back easier.
281  QString formattedTitle;
282  if ( !item->title().isEmpty() && !item->text().isEmpty() )
283  formattedTitle = QStringLiteral( "%1 : %2" ).arg( item->title(), item->text() );
284  else if ( !item->title().isEmpty() )
285  formattedTitle = item->title();
286  else if ( !item->text().isEmpty() )
287  formattedTitle = item->text();
288 
289  if ( !formattedTitle.isEmpty() )
290  QgsMessageLog::logMessage( formattedTitle, tr( "Messages" ), item->level() );
291 }
292 
293 QgsMessageBarItem *QgsMessageBar::pushWidget( QWidget *widget, Qgis::MessageLevel level, int duration )
294 {
295  QgsMessageBarItem *item = nullptr;
296  item = dynamic_cast<QgsMessageBarItem *>( widget );
297  if ( item )
298  {
299  item->setLevel( level )->setDuration( duration );
300  }
301  else
302  {
303  item = new QgsMessageBarItem( widget, level, duration );
304  }
305  pushItem( item );
306  return item;
307 }
308 
309 void QgsMessageBar::pushMessage( const QString &title, const QString &text, Qgis::MessageLevel level, int duration )
310 {
311  // keep the number of items in the message bar to a maximum of 20, avoids flooding (and freezing) of the main window
312  if ( mItems.count() > 20 )
313  mItems.removeFirst();
314 
315  // block duplicate items, avoids flooding (and freezing) of the main window
316  for ( auto it = mItems.constBegin(); it != mItems.constEnd(); ++it )
317  {
318  if ( level == ( *it )->level() && title == ( *it )->title() && text == ( *it )->text() )
319  return;
320  }
321 
322  QgsMessageBarItem *item = new QgsMessageBarItem( title, text, level, duration );
323  pushItem( item );
324 }
325 
326 void QgsMessageBar::pushMessage( const QString &title, const QString &text, const QString &showMore, Qgis::MessageLevel level, int duration )
327 {
329  mv->setWindowTitle( title );
330  mv->setMessageAsPlainText( text + "\n\n" + showMore );
331 
332  QToolButton *showMoreButton = new QToolButton();
333  QAction *act = new QAction( showMoreButton );
334  act->setText( tr( "Show more" ) );
335  showMoreButton->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
336  showMoreButton->setCursor( Qt::PointingHandCursor );
337  showMoreButton->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
338  showMoreButton->addAction( act );
339  showMoreButton->setDefaultAction( act );
340  connect( showMoreButton, &QToolButton::triggered, mv, &QDialog::exec );
341  connect( showMoreButton, &QToolButton::triggered, showMoreButton, &QObject::deleteLater );
342 
344  title,
345  text,
346  showMoreButton,
347  level,
348  duration );
349  pushItem( item );
350 }
351 
352 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &text, QWidget *parent )
353 {
354  QgsMessageBarItem *item = new QgsMessageBarItem( text, Qgis::Info, 0, parent );
355  return item;
356 }
357 
358 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &title, const QString &text, QWidget *parent )
359 {
360  return new QgsMessageBarItem( title, text, Qgis::Info, 0, parent );
361 }
362 
363 QgsMessageBarItem *QgsMessageBar::createMessage( QWidget *widget, QWidget *parent )
364 {
365  return new QgsMessageBarItem( widget, Qgis::Info, 0, parent );
366 }
367 
368 void QgsMessageBar::updateCountdown()
369 {
370  if ( !mCountdownTimer->isActive() )
371  {
372  resetCountdown();
373  return;
374  }
375  if ( mCountProgress->value() < 2 )
376  {
377  popWidget();
378  }
379  else
380  {
381  mCountProgress->setValue( mCountProgress->value() - 1 );
382  }
383 }
384 
385 void QgsMessageBar::resetCountdown()
386 {
387  if ( mCountdownTimer->isActive() )
388  mCountdownTimer->stop();
389 
390  mCountProgress->setStyleSheet( mCountStyleSheet.arg( QStringLiteral( "mIconTimerPause.svg" ) ) );
391  mCountProgress->setVisible( false );
392 }
393 
394 void QgsMessageBar::updateItemCount()
395 {
396  mItemCount->setText( !mItems.isEmpty() ? tr( "%n more", "unread messages", mItems.count() ) : QString() );
397 
398  // do not show the down arrow for opening menu with "close all" if there is just one message
399  mCloseBtn->setMenu( !mItems.isEmpty() ? mCloseMenu : nullptr );
400  mCloseBtn->setPopupMode( !mItems.isEmpty() ? QToolButton::MenuButtonPopup : QToolButton::DelayedPopup );
401 }
QgsMessageBar(QWidget *parent=nullptr)
Constructor for QgsMessageBar.
void mousePressEvent(QMouseEvent *e) override
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:139
void pushInfo(const QString &title, const QString &message)
Pushes a information message with default timeout to the message bar.
QString title() const
Returns the title for the message.
void widgetRemoved(QgsMessageBarItem *item)
emitted when a message widget was removed from the bar
QgsMessageBarItem * pushWidget(QWidget *widget, Qgis::MessageLevel level=Qgis::Info, int duration=0)
Display a widget as a message on the bar after hiding the currently visible one and putting it in a s...
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:45
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
bool clearWidgets()
Remove all items from the bar&#39;s widget list.
int duration() const
returns the duration in second of the message
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:66
void pushSuccess(const QString &title, const QString &message)
Pushes a success message with default timeout to the message bar.
Qgis::MessageLevel level() const
Returns the message level for the message.
static QgsMessageBarItem * createMessage(const QString &text, QWidget *parent=nullptr)
make out a widget containing a message to be displayed on the bar
void pushWarning(const QString &title, const QString &message)
Pushes a warning with default timeout to the message bar.
QString text() const
Returns the text for the message.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void setMessageAsPlainText(const QString &msg)
void widgetAdded(QgsMessageBarItem *item)
emitted when a message widget is added to the bar
void pushMessage(const QString &text, Qgis::MessageLevel level=Qgis::Info, int duration=5)
convenience method for pushing a message to the bar
Definition: qgsmessagebar.h:88
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window&#39;s toolbar icons.
QString getStyleSheet()
returns the styleSheet
void pushItem(QgsMessageBarItem *item)
Display a message item on the bar after hiding the currently visible one and putting it in a stack...
QgsMessageBarItem * setDuration(int duration)
A generic message view for displaying QGIS messages.
void styleChanged(const QString &styleSheet)
emitted when the message level has changed
bool popWidget()
Remove the currently displayed widget from the bar and display the next in the stack if any or hide t...
QgsMessageBarItem * setLevel(Qgis::MessageLevel level)
void pushCritical(const QString &title, const QString &message)
Pushes a critical warning with default timeout to the message bar.