QGIS API Documentation 3.41.0-Master (1deb1daf037)
Loading...
Searching...
No Matches
qgsdatetimeedit.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdatetimeedit.cpp
3 --------------------------------------
4 Date : 08.2014
5 Copyright : (C) 2014 Denis Rouzaud
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#include "moc_qgsdatetimeedit.cpp"
26
27#include "qgsapplication.h"
28#include "qgsvariantutils.h"
29
30
32#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
33 : QgsDateTimeEdit( QDateTime(), QMetaType::Type::QDateTime, parent )
34#else
35 : QgsDateTimeEdit( QDateTime(), QMetaType::QDateTime, parent )
36#endif
37{
38}
39
41#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
42QgsDateTimeEdit::QgsDateTimeEdit( const QVariant &var, QMetaType::Type parserType, QWidget *parent )
43 : QDateTimeEdit( var, parserType, parent )
44#else
45QgsDateTimeEdit::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
69void 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
105bool 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 // Fix wrong internal logic of Qt when pasting text with selected text
113 // when the selection starts in a different position than the stored cursor
114 // position (which selects the currently active section of the date widget)
115 // See: https://github.com/qgis/QGIS/issues/53149
116 if ( event->type() == QEvent::KeyPress )
117 {
118 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
119 if ( keyEvent->matches( QKeySequence::Paste ) && lineEdit()->hasSelectedText() )
120 {
121 const int selectionStart { lineEdit()->selectionStart() };
122 const int selectionEnd { lineEdit()->selectionEnd() };
123 lineEdit()->setCursorPosition( selectionStart );
124 lineEdit()->setSelection( selectionStart, selectionEnd - selectionStart );
125 }
126 }
127
128 return QDateTimeEdit::event( event );
129}
130
131void QgsDateTimeEdit::mousePressEvent( QMouseEvent *event )
132{
133 // catch mouse press on the button (when the current value is null)
134 // in non-calendar mode: modify the date so it leads to showing current date (don't bother about time)
135 // in calendar mode: be sure NULL is displayed when needed and show page of current date in calendar widget
136
137 bool updateCalendar = false;
138
139 if ( mIsNull )
140 {
141 QStyle::SubControl control;
142 if ( calendarPopup() )
143 {
144 QStyleOptionComboBox optCombo;
145#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
146 optCombo.init( this );
147#else
148 optCombo.initFrom( this );
149#endif
150 optCombo.editable = true;
151 optCombo.subControls = QStyle::SC_All;
152 control = style()->hitTestComplexControl( QStyle::CC_ComboBox, &optCombo, event->pos(), this );
153
154 if ( control == QStyle::SC_ComboBoxArrow && calendarWidget() )
155 {
156 mCurrentPressEvent = true;
157 // ensure the line edit still displays NULL
158 updateCalendar = true;
159 displayNull( updateCalendar );
160 mCurrentPressEvent = false;
161 }
162 }
163 else
164 {
165 QStyleOptionSpinBox opt;
166 this->initStyleOption( &opt );
167 control = style()->hitTestComplexControl( QStyle::CC_SpinBox, &opt, event->pos(), this );
168
169 if ( control == QStyle::SC_SpinBoxDown || control == QStyle::SC_SpinBoxUp )
170 {
171 mCurrentPressEvent = true;
172 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
173 resetBeforeChange( control == QStyle::SC_SpinBoxDown ? -1 : 1 );
174 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
175 mCurrentPressEvent = false;
176 }
177 }
178 }
179
180 QDateTimeEdit::mousePressEvent( event );
181
182 if ( updateCalendar )
183 {
184 // set calendar page to current date to avoid going to minimal date page when value is null
185 calendarWidget()->setCurrentPage( QDate::currentDate().year(), QDate::currentDate().month() );
186 }
187}
188
189void QgsDateTimeEdit::focusOutEvent( QFocusEvent *event )
190{
191 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
192 {
193 QAbstractSpinBox::focusOutEvent( event );
194 if ( lineEdit()->text() != mNullRepresentation )
195 {
196 displayNull();
197 }
198 emit editingFinished();
199 }
200 else
201 {
202 QDateTimeEdit::focusOutEvent( event );
203 }
204}
205
206void QgsDateTimeEdit::focusInEvent( QFocusEvent *event )
207{
208 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
209 {
210 QAbstractSpinBox::focusInEvent( event );
211
212 displayCurrentDate();
213 }
214 else
215 {
216 QDateTimeEdit::focusInEvent( event );
217 }
218}
219
220void QgsDateTimeEdit::wheelEvent( QWheelEvent *event )
221{
222 // dateTime might have been set to minimum in calendar mode
223 if ( mAllowNull && mIsNull )
224 {
225 // convert angleDelta to approximate wheel "steps" -- angleDelta is in 1/8 degrees, and according
226 // to Qt docs most mice step in 15 degree increments
227 resetBeforeChange( -event->angleDelta().y() / ( 15 * 8 ) );
228 }
229 QDateTimeEdit::wheelEvent( event );
230}
231
232void QgsDateTimeEdit::showEvent( QShowEvent *event )
233{
234 QDateTimeEdit::showEvent( event );
235 if ( mAllowNull && mIsNull && lineEdit()->text() != mNullRepresentation )
236 {
237 displayNull();
238 }
239}
240
242void QgsDateTimeEdit::changed( const QVariant &dateTime )
243{
244 mIsEmpty = false;
246 if ( isNull != mIsNull )
247 {
248 mIsNull = isNull;
249 if ( mIsNull )
250 {
251 if ( mOriginalStyleSheet.isNull() )
252 {
253 mOriginalStyleSheet = lineEdit()->styleSheet();
254 }
255 lineEdit()->setStyleSheet( QStringLiteral( "QLineEdit { font-style: italic; color: grey; }" ) );
256 }
257 else
258 {
259 lineEdit()->setStyleSheet( mOriginalStyleSheet );
260 }
261 }
262
263 mClearAction->setVisible( mAllowNull && !mIsNull );
264 if ( !mBlockChangedSignal )
265 emitValueChanged( isNull ? QVariant() : dateTime );
266}
268
270{
271 return mNullRepresentation;
272}
273
274void QgsDateTimeEdit::setNullRepresentation( const QString &nullRepresentation )
275{
276 mNullRepresentation = nullRepresentation;
277 if ( mIsNull )
278 {
279 lineEdit()->setText( mNullRepresentation );
280 }
281}
282
283void QgsDateTimeEdit::displayNull( bool updateCalendar )
284{
285 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
286 if ( updateCalendar )
287 {
288 // set current time to minimum date time to avoid having
289 // a date selected in calendar widget
290 QDateTimeEdit::setDateTime( minimumDateTime() );
291 }
292 lineEdit()->setCursorPosition( lineEdit()->text().length() );
293 lineEdit()->setText( mNullRepresentation );
294 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
295}
296
297void QgsDateTimeEdit::emitValueChanged( const QVariant &value )
298{
299 emit QgsDateTimeEdit::valueChanged( value.toDateTime() );
300}
301
303{
304 return mAllowNull && mIsNull;
305}
306
307void QgsDateTimeEdit::displayCurrentDate()
308{
309 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
310 QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
311 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
312}
313
314void QgsDateTimeEdit::resetBeforeChange( int delta )
315{
316 QDateTime dt = QDateTime::currentDateTime();
317 switch ( currentSection() )
318 {
319 case QDateTimeEdit::DaySection:
320 dt = dt.addDays( delta );
321 break;
322 case QDateTimeEdit::MonthSection:
323 dt = dt.addMonths( delta );
324 break;
325 case QDateTimeEdit::YearSection:
326 dt = dt.addYears( delta );
327 break;
328 default:
329 break;
330 }
331 if ( dt < minimumDateTime() )
332 {
333 dt = minimumDateTime();
334 }
335 else if ( dt > maximumDateTime() )
336 {
337 dt = maximumDateTime();
338 }
339 QDateTimeEdit::setDateTime( dt );
340}
341
342void QgsDateTimeEdit::setMinimumEditDateTime()
343{
344 setDateRange( QDate( 1, 1, 1 ), maximumDate() );
345 setMinimumTime( QTime( 0, 0, 0 ) );
346 setMaximumTime( QTime( 23, 59, 59, 999 ) );
347}
348
349void QgsDateTimeEdit::setDateTime( const QDateTime &dateTime )
350{
351 mIsEmpty = false;
352
353 // set an undefined date
354 if ( !dateTime.isValid() || dateTime.isNull() )
355 {
356 clear();
357 displayNull();
358 }
359 // Check if it's really changed or crash, see GH #29937
360 else if ( dateTime != QgsDateTimeEdit::dateTime() )
361 {
362 // changed emits a signal, so don't allow it to be emitted from setDateTime
364 // We need to set the time spec of the set datetime to the widget, otherwise
365 // the dateTime() getter would loose edit, and return local time.
366 QDateTimeEdit::setTimeSpec( dateTime.timeSpec() );
367 QDateTimeEdit::setDateTime( dateTime );
369 changed( dateTime );
370 }
371}
372
374{
375 if ( isNull() )
376 {
377 return QDateTime();
378 }
379 else
380 {
381 return QDateTimeEdit::dateTime();
382 }
383}
384
386{
387 if ( isNull() )
388 {
389 return QTime();
390 }
391 else
392 {
393 return QDateTimeEdit::time();
394 }
395}
396
398{
399 if ( isNull() )
400 {
401 return QDate();
402 }
403 else
404 {
405 return QDateTimeEdit::date();
406 }
407}
408
409
410//
411// QgsTimeEdit
412//
413
414QgsTimeEdit::QgsTimeEdit( QWidget *parent )
415#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
416 : QgsDateTimeEdit( QTime(), QMetaType::Type::QTime, parent )
417#else
418 : QgsDateTimeEdit( QTime(), QMetaType::QTime, parent )
419#endif
420{
421}
422
423void QgsTimeEdit::setTime( const QTime &time )
424{
425 mIsEmpty = false;
426
427 // set an undefined date
428 if ( !time.isValid() || time.isNull() )
429 {
430 clear();
431 displayNull();
432 }
433 // Check if it's really changed or crash, see GH #29937
434 else if ( time != QgsTimeEdit::time() )
435 {
436 // changed emits a signal, so don't allow it to be emitted from setTime
438 QDateTimeEdit::setTime( time );
440 changed( time );
441 }
442}
443
444void QgsTimeEdit::emitValueChanged( const QVariant &value )
445{
446 emit timeValueChanged( value.toTime() );
447}
448
449
450//
451// QgsDateEdit
452//
453
454QgsDateEdit::QgsDateEdit( QWidget *parent )
455#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
456 : QgsDateTimeEdit( QDate(), QMetaType::Type::QDate, parent )
457#else
458 : QgsDateTimeEdit( QDate(), QMetaType::QDate, parent )
459#endif
460{
461}
462
463void QgsDateEdit::setDate( const QDate &date )
464{
465 mIsEmpty = false;
466
467 // set an undefined date
468 if ( !date.isValid() || date.isNull() )
469 {
470 clear();
471 displayNull();
472 }
473 // Check if it's really changed or crash, see GH #29937
474 else if ( date != QgsDateEdit::date() )
475 {
476 // changed emits a signal, so don't allow it to be emitted from setDate
478 QDateTimeEdit::setDate( date );
480 changed( date );
481 }
482}
483
484void QgsDateEdit::emitValueChanged( const QVariant &value )
485{
486 emit dateValueChanged( value.toDate() );
487}
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.