QGIS API Documentation  3.6.0-Noosa (5873452)
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  if ( mCurrentItem )
137  {
138  QWidget *widget = mCurrentItem;
139  mLayout->removeWidget( widget );
140  mCurrentItem->hide();
141  disconnect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
142  mCurrentItem->deleteLater();
143  mCurrentItem = nullptr;
144  }
145 
146  if ( !mItems.isEmpty() )
147  {
148  showItem( mItems.at( 0 ) );
149  }
150  else
151  {
152  hide();
153  }
154  }
155  else
156  {
157  mItems.removeOne( item );
158  }
159 
160  emit widgetRemoved( item );
161 }
162 
164 {
165  if ( !item || !mCurrentItem )
166  return false;
167 
168  if ( item == mCurrentItem )
169  {
170  popItem( mCurrentItem );
171  return true;
172  }
173 
174  Q_FOREACH ( QgsMessageBarItem *existingItem, mItems )
175  {
176  if ( existingItem == item )
177  {
178  mItems.removeOne( existingItem );
179  existingItem->deleteLater();
180  return true;
181  }
182  }
183 
184  return false;
185 }
186 
188 {
189  if ( !mCurrentItem )
190  return false;
191 
192  resetCountdown();
193 
194  QgsMessageBarItem *item = mCurrentItem;
195  popItem( item );
196 
197  return true;
198 }
199 
201 {
202  if ( !mCurrentItem && mItems.empty() )
203  return true;
204 
205  while ( !mItems.isEmpty() )
206  {
207  popWidget();
208  }
209  popWidget();
210 
211  return !mCurrentItem && mItems.empty();
212 }
213 
214 void QgsMessageBar::pushSuccess( const QString &title, const QString &message )
215 {
216  pushMessage( title, message, Qgis::Success );
217 }
218 
219 void QgsMessageBar::pushInfo( const QString &title, const QString &message )
220 {
221  pushMessage( title, message, Qgis::Info );
222 }
223 
224 void QgsMessageBar::pushWarning( const QString &title, const QString &message )
225 {
226  pushMessage( title, message, Qgis::Warning );
227 }
228 
229 void QgsMessageBar::pushCritical( const QString &title, const QString &message )
230 {
231  pushMessage( title, message, Qgis::Critical );
232 }
233 
234 void QgsMessageBar::showItem( QgsMessageBarItem *item )
235 {
236  Q_ASSERT( item );
237 
238  if ( mCurrentItem )
239  disconnect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
240 
241  if ( item == mCurrentItem )
242  return;
243 
244  if ( mItems.contains( item ) )
245  mItems.removeOne( item );
246 
247  if ( mCurrentItem )
248  {
249  mItems.prepend( mCurrentItem );
250  mLayout->removeWidget( mCurrentItem );
251  mCurrentItem->hide();
252  }
253 
254  mCurrentItem = item;
255  mLayout->addWidget( item, 0, 1, 1, 1 );
256  mCurrentItem->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( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet );
267  setStyleSheet( item->getStyleSheet() );
268  show();
269 
270  emit widgetAdded( item );
271 }
272 
274 {
275  resetCountdown();
276 
277  item->mMessageBar = this;
278 
279  // avoid duplicated widget
280  popWidget( item );
281  showItem( item );
282 
283  // Log all messages that are sent to the message bar into the message log so the
284  // user can get them back easier.
285  QString formattedTitle = QStringLiteral( "%1 : %2" ).arg( item->title(), item->text() );
286  QgsMessageLog::logMessage( formattedTitle, tr( "Messages" ), item->level() );
287 }
288 
289 QgsMessageBarItem *QgsMessageBar::pushWidget( QWidget *widget, Qgis::MessageLevel level, int duration )
290 {
291  QgsMessageBarItem *item = nullptr;
292  item = dynamic_cast<QgsMessageBarItem *>( widget );
293  if ( item )
294  {
295  item->setLevel( level )->setDuration( duration );
296  }
297  else
298  {
299  item = new QgsMessageBarItem( widget, level, duration );
300  }
301  pushItem( item );
302  return item;
303 }
304 
305 void QgsMessageBar::pushMessage( const QString &title, const QString &text, Qgis::MessageLevel level, int duration )
306 {
307  // keep the number of items in the message bar to a maximum of 20, avoids flooding (and freezing) of the main window
308  if ( mItems.count() > 20 )
309  mItems.removeFirst();
310 
311  // block duplicate items, avoids flooding (and freezing) of the main window
312  for ( auto it = mItems.constBegin(); it != mItems.constEnd(); ++it )
313  {
314  if ( level == ( *it )->level() && title == ( *it )->title() && text == ( *it )->text() )
315  return;
316  }
317 
318  QgsMessageBarItem *item = new QgsMessageBarItem( title, text, level, duration );
319  pushItem( item );
320 }
321 
322 void QgsMessageBar::pushMessage( const QString &title, const QString &text, const QString &showMore, Qgis::MessageLevel level, int duration )
323 {
325  mv->setWindowTitle( title );
326  mv->setMessageAsPlainText( text + "\n\n" + showMore );
327 
328  QToolButton *showMoreButton = new QToolButton();
329  QAction *act = new QAction( showMoreButton );
330  act->setText( tr( "Show more" ) );
331  showMoreButton->setStyleSheet( QStringLiteral( "background-color: rgba(255, 255, 255, 0); color: black; text-decoration: underline;" ) );
332  showMoreButton->setCursor( Qt::PointingHandCursor );
333  showMoreButton->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Preferred );
334  showMoreButton->addAction( act );
335  showMoreButton->setDefaultAction( act );
336  connect( showMoreButton, &QToolButton::triggered, mv, &QDialog::exec );
337  connect( showMoreButton, &QToolButton::triggered, showMoreButton, &QObject::deleteLater );
338 
340  title,
341  text,
342  showMoreButton,
343  level,
344  duration );
345  pushItem( item );
346 }
347 
348 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &text, QWidget *parent )
349 {
350  QgsMessageBarItem *item = new QgsMessageBarItem( text, Qgis::Info, 0, parent );
351  return item;
352 }
353 
354 QgsMessageBarItem *QgsMessageBar::createMessage( const QString &title, const QString &text, QWidget *parent )
355 {
356  return new QgsMessageBarItem( title, text, Qgis::Info, 0, parent );
357 }
358 
359 QgsMessageBarItem *QgsMessageBar::createMessage( QWidget *widget, QWidget *parent )
360 {
361  return new QgsMessageBarItem( widget, Qgis::Info, 0, parent );
362 }
363 
364 void QgsMessageBar::updateCountdown()
365 {
366  if ( !mCountdownTimer->isActive() )
367  {
368  resetCountdown();
369  return;
370  }
371  if ( mCountProgress->value() < 2 )
372  {
373  popWidget();
374  }
375  else
376  {
377  mCountProgress->setValue( mCountProgress->value() - 1 );
378  }
379 }
380 
381 void QgsMessageBar::resetCountdown()
382 {
383  if ( mCountdownTimer->isActive() )
384  mCountdownTimer->stop();
385 
386  mCountProgress->setStyleSheet( mCountStyleSheet.arg( QStringLiteral( "mIconTimerPause.svg" ) ) );
387  mCountProgress->setVisible( false );
388 }
389 
390 void QgsMessageBar::updateItemCount()
391 {
392  mItemCount->setText( !mItems.isEmpty() ? tr( "%n more", "unread messages", mItems.count() ) : QString() );
393 
394  // do not show the down arrow for opening menu with "close all" if there is just one message
395  mCloseBtn->setMenu( !mItems.isEmpty() ? mCloseMenu : nullptr );
396  mCloseBtn->setPopupMode( !mItems.isEmpty() ? QToolButton::MenuButtonPopup : QToolButton::DelayedPopup );
397 }
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
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.