QGIS API Documentation  3.37.0-Master (a5b4d9743e8)
qgsdatetimeedit.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdatetimeedit.cpp
3  --------------------------------------
4  Date : 08.2014
5  Copyright : (C) 2014 Denis Rouzaud
6  Email : [email protected]
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include <QAction>
17 #include <QCalendarWidget>
18 #include <QLineEdit>
19 #include <QMouseEvent>
20 #include <QStyle>
21 #include <QStyleOptionSpinBox>
22 
23 
24 #include "qgsdatetimeedit.h"
25 
26 #include "qgsapplication.h"
27 #include "qgsvariantutils.h"
28 
29 
31 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
32  : QgsDateTimeEdit( QDateTime(), QVariant::DateTime, parent )
33 #else
34  : QgsDateTimeEdit( QDateTime(), QMetaType::QDateTime, parent )
35 #endif
36 {
37 
38 }
39 
41 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
42 QgsDateTimeEdit::QgsDateTimeEdit( const QVariant &var, QVariant::Type parserType, QWidget *parent )
43  : QDateTimeEdit( var, parserType, parent )
44 #else
45 QgsDateTimeEdit::QgsDateTimeEdit( const QVariant & var, QMetaType::Type parserType, QWidget * parent )
46  : QDateTimeEdit( var, parserType, parent )
47 #endif
48  , mNullRepresentation( QgsApplication::nullRepresentation() )
49 {
50  const QIcon clearIcon = QgsApplication::getThemeIcon( "/mIconClearText.svg" );
51  mClearAction = new QAction( clearIcon, tr( "clear" ), this );
52  mClearAction->setCheckable( false );
53  lineEdit()->addAction( mClearAction, QLineEdit::TrailingPosition );
54  mClearAction->setVisible( mAllowNull );
55  connect( mClearAction, &QAction::triggered, this, &QgsDateTimeEdit::clear );
56 
57  connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
58 
59  // enable calendar widget by default so it's already created
60  setCalendarPopup( true );
61 
62  setMinimumEditDateTime();
63 
64  // init with current time so mIsNull is properly initialized
65  QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
66 }
68 
69 void QgsDateTimeEdit::setAllowNull( bool allowNull )
70 {
71  mAllowNull = allowNull;
72  mClearAction->setVisible( !isReadOnly() && mAllowNull && ( !mIsNull || mIsEmpty ) );
73 }
74 
75 
77 {
78  if ( mAllowNull )
79  {
80  displayCurrentDate();
81 
82  // Check if it's really changed or crash, see GH #29937
83  if ( ! dateTime().isNull() )
84  {
85  changed( QVariant() );
86  }
87 
88  // emit signal of QDateTime::dateTimeChanged with an invalid date
89  // anyway, using parent's signal should be avoided
90  // If you consequently connect parent's dateTimeChanged signal
91  // and call dateTime() afterwards there is no warranty to
92  // have a proper NULL value handling
93  disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
94  emit dateTimeChanged( QDateTime() );
95  connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
96  }
97 }
98 
100 {
101  mClearAction->setVisible( mAllowNull );
102  mIsEmpty = true;
103 }
104 
105 bool QgsDateTimeEdit::event( QEvent *event )
106 {
107  if ( event->type() == QEvent::ReadOnlyChange || event->type() == QEvent::EnabledChange )
108  {
109  mClearAction->setVisible( !isReadOnly() && mAllowNull && ( !mIsNull || mIsEmpty ) );
110  }
111 
112  return QDateTimeEdit::event( event );
113 }
114 
115 void QgsDateTimeEdit::mousePressEvent( QMouseEvent *event )
116 {
117  // catch mouse press on the button (when the current value is null)
118  // in non-calendar mode: modify the date so it leads to showing current date (don't bother about time)
119  // in calendar mode: be sure NULL is displayed when needed and show page of current date in calendar widget
120 
121  bool updateCalendar = false;
122 
123  if ( mIsNull )
124  {
125  QStyle::SubControl control;
126  if ( calendarPopup() )
127  {
128  QStyleOptionComboBox optCombo;
129 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
130  optCombo.init( this );
131 #else
132  optCombo.initFrom( this );
133 #endif
134  optCombo.editable = true;
135  optCombo.subControls = QStyle::SC_All;
136  control = style()->hitTestComplexControl( QStyle::CC_ComboBox, &optCombo, event->pos(), this );
137 
138  if ( control == QStyle::SC_ComboBoxArrow && calendarWidget() )
139  {
140  mCurrentPressEvent = true;
141  // ensure the line edit still displays NULL
142  updateCalendar = true;
143  displayNull( updateCalendar );
144  mCurrentPressEvent = false;
145  }
146  }
147  else
148  {
149  QStyleOptionSpinBox opt;
150  this->initStyleOption( &opt );
151  control = style()->hitTestComplexControl( QStyle::CC_SpinBox, &opt, event->pos(), this );
152 
153  if ( control == QStyle::SC_SpinBoxDown || control == QStyle::SC_SpinBoxUp )
154  {
155  mCurrentPressEvent = true;
156  disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
157  resetBeforeChange( control == QStyle::SC_SpinBoxDown ? -1 : 1 );
158  connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
159  mCurrentPressEvent = false;
160  }
161  }
162  }
163 
164  QDateTimeEdit::mousePressEvent( event );
165 
166  if ( updateCalendar )
167  {
168  // set calendar page to current date to avoid going to minimal date page when value is null
169  calendarWidget()->setCurrentPage( QDate::currentDate().year(), QDate::currentDate().month() );
170  }
171 }
172 
173 void QgsDateTimeEdit::focusOutEvent( QFocusEvent *event )
174 {
175  if ( mAllowNull && mIsNull && !mCurrentPressEvent )
176  {
177  QAbstractSpinBox::focusOutEvent( event );
178  if ( lineEdit()->text() != mNullRepresentation )
179  {
180  displayNull();
181  }
182  emit editingFinished();
183  }
184  else
185  {
186  QDateTimeEdit::focusOutEvent( event );
187  }
188 }
189 
190 void QgsDateTimeEdit::focusInEvent( QFocusEvent *event )
191 {
192  if ( mAllowNull && mIsNull && !mCurrentPressEvent )
193  {
194  QAbstractSpinBox::focusInEvent( event );
195 
196  displayCurrentDate();
197  }
198  else
199  {
200  QDateTimeEdit::focusInEvent( event );
201  }
202 }
203 
204 void QgsDateTimeEdit::wheelEvent( QWheelEvent *event )
205 {
206  // dateTime might have been set to minimum in calendar mode
207  if ( mAllowNull && mIsNull )
208  {
209  // convert angleDelta to approximate wheel "steps" -- angleDelta is in 1/8 degrees, and according
210  // to Qt docs most mice step in 15 degree increments
211  resetBeforeChange( -event->angleDelta().y() / ( 15 * 8 ) );
212  }
213  QDateTimeEdit::wheelEvent( event );
214 }
215 
216 void QgsDateTimeEdit::showEvent( QShowEvent *event )
217 {
218  QDateTimeEdit::showEvent( event );
219  if ( mAllowNull && mIsNull &&
220  lineEdit()->text() != mNullRepresentation )
221  {
222  displayNull();
223  }
224 }
225 
227 void QgsDateTimeEdit::changed( const QVariant &dateTime )
228 {
229  mIsEmpty = false;
230  const bool isNull = QgsVariantUtils::isNull( dateTime );
231  if ( isNull != mIsNull )
232  {
233  mIsNull = isNull;
234  if ( mIsNull )
235  {
236  if ( mOriginalStyleSheet.isNull() )
237  {
238  mOriginalStyleSheet = lineEdit()->styleSheet();
239  }
240  lineEdit()->setStyleSheet( QStringLiteral( "QLineEdit { font-style: italic; color: grey; }" ) );
241  }
242  else
243  {
244  lineEdit()->setStyleSheet( mOriginalStyleSheet );
245  }
246  }
247 
248  mClearAction->setVisible( mAllowNull && !mIsNull );
249  if ( !mBlockChangedSignal )
250  emitValueChanged( isNull ? QVariant() : dateTime );
251 }
253 
255 {
256  return mNullRepresentation;
257 }
258 
259 void QgsDateTimeEdit::setNullRepresentation( const QString &nullRepresentation )
260 {
261  mNullRepresentation = nullRepresentation;
262  if ( mIsNull )
263  {
264  lineEdit()->setText( mNullRepresentation );
265  }
266 }
267 
268 void QgsDateTimeEdit::displayNull( bool updateCalendar )
269 {
270  disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
271  if ( updateCalendar )
272  {
273  // set current time to minimum date time to avoid having
274  // a date selected in calendar widget
275  QDateTimeEdit::setDateTime( minimumDateTime() );
276  }
277  lineEdit()->setCursorPosition( lineEdit()->text().length() );
278  lineEdit()->setText( mNullRepresentation );
279  connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
280 }
281 
282 void QgsDateTimeEdit::emitValueChanged( const QVariant &value )
283 {
284  emit QgsDateTimeEdit::valueChanged( value.toDateTime() );
285 }
286 
288 {
289  return mAllowNull && mIsNull;
290 }
291 
292 void QgsDateTimeEdit::displayCurrentDate()
293 {
294  disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
295  QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
296  connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
297 }
298 
299 void QgsDateTimeEdit::resetBeforeChange( int delta )
300 {
301  QDateTime dt = QDateTime::currentDateTime();
302  switch ( currentSection() )
303  {
304  case QDateTimeEdit::DaySection:
305  dt = dt.addDays( delta );
306  break;
307  case QDateTimeEdit::MonthSection:
308  dt = dt.addMonths( delta );
309  break;
310  case QDateTimeEdit::YearSection:
311  dt = dt.addYears( delta );
312  break;
313  default:
314  break;
315  }
316  if ( dt < minimumDateTime() )
317  {
318  dt = minimumDateTime();
319  }
320  else if ( dt > maximumDateTime() )
321  {
322  dt = maximumDateTime();
323  }
324  QDateTimeEdit::setDateTime( dt );
325 }
326 
327 void QgsDateTimeEdit::setMinimumEditDateTime()
328 {
329  setDateRange( QDate( 1, 1, 1 ), maximumDate() );
330  setMinimumTime( QTime( 0, 0, 0 ) );
331  setMaximumTime( QTime( 23, 59, 59, 999 ) );
332 }
333 
334 void QgsDateTimeEdit::setDateTime( const QDateTime &dateTime )
335 {
336  mIsEmpty = false;
337 
338  // set an undefined date
339  if ( !dateTime.isValid() || dateTime.isNull() )
340  {
341  clear();
342  displayNull();
343  }
344  // Check if it's really changed or crash, see GH #29937
345  else if ( dateTime != QgsDateTimeEdit::dateTime() )
346  {
347  // changed emits a signal, so don't allow it to be emitted from setDateTime
349  QDateTimeEdit::setDateTime( dateTime );
351  changed( dateTime );
352  }
353 }
354 
355 QDateTime QgsDateTimeEdit::dateTime() const
356 {
357  if ( isNull() )
358  {
359  return QDateTime();
360  }
361  else
362  {
363  return QDateTimeEdit::dateTime();
364  }
365 }
366 
368 {
369  if ( isNull() )
370  {
371  return QTime();
372  }
373  else
374  {
375  return QDateTimeEdit::time();
376  }
377 }
378 
380 {
381  if ( isNull() )
382  {
383  return QDate();
384  }
385  else
386  {
387  return QDateTimeEdit::date();
388  }
389 }
390 
391 
392 //
393 // QgsTimeEdit
394 //
395 
396 QgsTimeEdit::QgsTimeEdit( QWidget *parent )
397 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
398  : QgsDateTimeEdit( QTime(), QVariant::Time, parent )
399 #else
400  : QgsDateTimeEdit( QTime(), QMetaType::QTime, parent )
401 #endif
402 {
403 
404 }
405 
406 void QgsTimeEdit::setTime( const QTime &time )
407 {
408  mIsEmpty = false;
409 
410  // set an undefined date
411  if ( !time.isValid() || time.isNull() )
412  {
413  clear();
414  displayNull();
415  }
416  // Check if it's really changed or crash, see GH #29937
417  else if ( time != QgsTimeEdit::time() )
418  {
419  // changed emits a signal, so don't allow it to be emitted from setTime
421  QDateTimeEdit::setTime( time );
423  changed( time );
424  }
425 }
426 
427 void QgsTimeEdit::emitValueChanged( const QVariant &value )
428 {
429  emit timeValueChanged( value.toTime() );
430 }
431 
432 
433 //
434 // QgsDateEdit
435 //
436 
437 QgsDateEdit::QgsDateEdit( QWidget *parent )
438 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
439  : QgsDateTimeEdit( QDate(), QVariant::Date, parent )
440 #else
441  : QgsDateTimeEdit( QDate(), QMetaType::QDate, parent )
442 #endif
443 {
444 
445 }
446 
447 void QgsDateEdit::setDate( const QDate &date )
448 {
449  mIsEmpty = false;
450 
451  // set an undefined date
452  if ( !date.isValid() || date.isNull() )
453  {
454  clear();
455  displayNull();
456  }
457  // Check if it's really changed or crash, see GH #29937
458  else if ( date != QgsDateEdit::date() )
459  {
460  // changed emits a signal, so don't allow it to be emitted from setDate
462  QDateTimeEdit::setDate( date );
464  changed( date );
465  }
466 }
467 
468 void QgsDateEdit::emitValueChanged( const QVariant &value )
469 {
470  emit dateValueChanged( value.toDate() );
471 }
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void setDate(const QDate &date)
Sets the date for the widget and handles null dates.
void dateValueChanged(const QDate &date)
Signal emitted whenever the date changes.
QgsDateEdit(QWidget *parent=nullptr)
Constructor for QgsDateEdit.
void emitValueChanged(const QVariant &value) override
Emits the widget's correct value changed signal.
The QgsDateTimeEdit class is a QDateTimeEdit with the capability of setting/reading null date/times.
void wheelEvent(QWheelEvent *event) override
void setAllowNull(bool allowNull)
Determines if the widget allows setting null date/time.
void setNullRepresentation(const QString &null)
Sets the widget's null representation, which defaults to QgsApplication::nullRepresentation().
int mBlockChangedSignal
Block change signals if true.
void showEvent(QShowEvent *event) override
QDateTime dateTime() const
Returns the date time which can be a null date/time.
void focusInEvent(QFocusEvent *event) override
bool isNull() const
Returns true if the widget is currently set to a null value.
virtual void emitValueChanged(const QVariant &value)
Emits the widget's correct value changed signal.
void mousePressEvent(QMouseEvent *event) override
void setDateTime(const QDateTime &dateTime)
Set the date time in the widget and handles null date times.
QTime time() const
Returns the time which can be a null time.
void setEmpty()
Resets the widget to show no value (ie, an "unknown" state).
QString nullRepresentation() const
Returns the widget's NULL representation, which defaults to QgsApplication::nullRepresentation().
void focusOutEvent(QFocusEvent *event) override
bool mIsEmpty
true if the widget is empty
void displayNull(bool updateCalendar=false)
write the null value representation to the line edit without changing the value
void clear() override
Set the current date as NULL.
bool event(QEvent *event) override
Reimplemented to enable/disable the clear action depending on read-only status.
QgsDateTimeEdit(QWidget *parent=nullptr)
Constructor for QgsDateTimeEdit.
QDate date() const
Returns the date which can be a null date.
void valueChanged(const QDateTime &date)
Signal emitted whenever the value changes.
QgsTimeEdit(QWidget *parent=nullptr)
Constructor for QgsTimeEdit.
void emitValueChanged(const QVariant &value) override
Emits the widget's correct value changed signal.
void timeValueChanged(const QTime &time)
Signal emitted whenever the time changes.
void setTime(const QTime &time)
Sets the time for the widget and handles null times.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.