QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 "qgsdatetimeedit.h"
17
18#include "qgsapplication.h"
19#include "qgsvariantutils.h"
20
21#include <QAction>
22#include <QCalendarWidget>
23#include <QLineEdit>
24#include <QMouseEvent>
25#include <QStyle>
26#include <QStyleOptionSpinBox>
27
28#include "moc_qgsdatetimeedit.cpp"
29
31#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
32 : QgsDateTimeEdit( QDateTime(), QMetaType::Type::QDateTime, parent )
33#else
34 : QgsDateTimeEdit( QDateTime(), QMetaType::QDateTime, parent )
35#endif
36{
37}
38
40#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
41QgsDateTimeEdit::QgsDateTimeEdit( const QVariant &var, QMetaType::Type parserType, QWidget *parent )
42 : QDateTimeEdit( var, parserType, parent )
43#else
44QgsDateTimeEdit::QgsDateTimeEdit( const QVariant &var, QMetaType::Type parserType, QWidget *parent )
45 : QDateTimeEdit( var, parserType, parent )
46#endif
47 , mNullRepresentation( QgsApplication::nullRepresentation() )
48{
49 const QIcon clearIcon = QgsApplication::getThemeIcon( "/mIconClearText.svg" );
50 mClearAction = new QAction( clearIcon, tr( "clear" ), this );
51 mClearAction->setCheckable( false );
52 lineEdit()->addAction( mClearAction, QLineEdit::TrailingPosition );
53 mClearAction->setVisible( mAllowNull );
54 connect( mClearAction, &QAction::triggered, this, &QgsDateTimeEdit::clear );
55
56 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
57
58 // enable calendar widget by default so it's already created
59 setCalendarPopup( true );
60
61 setMinimumEditDateTime();
62
63 // init with current time so mIsNull is properly initialized
64 QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
65}
67
69{
70 mAllowNull = allowNull;
71 mClearAction->setVisible( !isReadOnly() && mAllowNull && ( !mIsNull || mIsEmpty ) );
72}
73
74
76{
77 if ( mAllowNull )
78 {
79 displayCurrentDate();
80
81 // Check if it's really changed or crash, see GH #29937
82 if ( !dateTime().isNull() )
83 {
84 changed( QVariant() );
85 }
86
87 // emit signal of QDateTime::dateTimeChanged with an invalid date
88 // anyway, using parent's signal should be avoided
89 // If you consequently connect parent's dateTimeChanged signal
90 // and call dateTime() afterwards there is no warranty to
91 // have a proper NULL value handling
92 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
93 emit dateTimeChanged( QDateTime() );
94 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
95 }
96}
97
99{
100 mClearAction->setVisible( mAllowNull );
101 mIsEmpty = true;
102}
103
105{
106 if ( event->type() == QEvent::ReadOnlyChange || event->type() == QEvent::EnabledChange )
107 {
108 mClearAction->setVisible( !isReadOnly() && mAllowNull && ( !mIsNull || mIsEmpty ) );
109 }
110
111 // Fix wrong internal logic of Qt when pasting text with selected text
112 // when the selection starts in a different position than the stored cursor
113 // position (which selects the currently active section of the date widget)
114 // See: https://github.com/qgis/QGIS/issues/53149
115 if ( event->type() == QEvent::KeyPress )
116 {
117 QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
118 if ( keyEvent->matches( QKeySequence::Paste ) && lineEdit()->hasSelectedText() )
119 {
120 const int selectionStart { lineEdit()->selectionStart() };
121 const int selectionEnd { lineEdit()->selectionEnd() };
122 lineEdit()->setCursorPosition( selectionStart );
123 lineEdit()->setSelection( selectionStart, selectionEnd - selectionStart );
124 }
125 }
126
127 return QDateTimeEdit::event( event );
128}
129
131{
132 // catch mouse press on the button (when the current value is null)
133 // in non-calendar mode: modify the date so it leads to showing current date (don't bother about time)
134 // in calendar mode: be sure NULL is displayed when needed and show page of current date in calendar widget
135
136 bool updateCalendar = false;
137
138 if ( mIsNull )
139 {
140 QStyle::SubControl control;
141 if ( calendarPopup() )
142 {
143 QStyleOptionComboBox optCombo;
144#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
145 optCombo.init( this );
146#else
147 optCombo.initFrom( this );
148#endif
149 optCombo.editable = true;
150 optCombo.subControls = QStyle::SC_All;
151 control = style()->hitTestComplexControl( QStyle::CC_ComboBox, &optCombo, event->pos(), this );
152
153 if ( control == QStyle::SC_ComboBoxArrow && calendarWidget() )
154 {
155 mCurrentPressEvent = true;
156 // ensure the line edit still displays NULL
157 updateCalendar = true;
158 displayNull( updateCalendar );
159 mCurrentPressEvent = false;
160 }
161 }
162 else
163 {
164 QStyleOptionSpinBox opt;
165 this->initStyleOption( &opt );
166 control = style()->hitTestComplexControl( QStyle::CC_SpinBox, &opt, event->pos(), this );
167
168 if ( control == QStyle::SC_SpinBoxDown || control == QStyle::SC_SpinBoxUp )
169 {
170 mCurrentPressEvent = true;
171 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
172 resetBeforeChange( control == QStyle::SC_SpinBoxDown ? -1 : 1 );
173 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
174 mCurrentPressEvent = false;
175 }
176 }
177 }
178
179 QDateTimeEdit::mousePressEvent( event );
180
181 if ( updateCalendar )
182 {
183 // set calendar page to current date to avoid going to minimal date page when value is null
184 calendarWidget()->setCurrentPage( QDate::currentDate().year(), QDate::currentDate().month() );
185 }
186}
187
189{
190 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
191 {
192 // should this be QDateTimeEdit::focusOutEvent?? It was always QAbstractSpinBox,
193 // and there's no clue if that was intentional...
194 QAbstractSpinBox::focusOutEvent( event ); // clazy:exclude=skipped-base-method
195 if ( lineEdit()->text() != mNullRepresentation )
196 {
197 displayNull();
198 }
199 emit editingFinished();
200 }
201 else
202 {
203 QDateTimeEdit::focusOutEvent( event );
204 }
205}
206
208{
209 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
210 {
211 // should this be QDateTimeEdit::focusOutEvent?? It was always QAbstractSpinBox,
212 // and there's no clue if that was intentional...
213 QAbstractSpinBox::focusInEvent( event ); // clazy:exclude=skipped-base-method
214
215 displayCurrentDate();
216 }
217 else
218 {
219 QDateTimeEdit::focusInEvent( event );
220 }
221}
222
224{
225 // dateTime might have been set to minimum in calendar mode
226 if ( mAllowNull && mIsNull )
227 {
228 // convert angleDelta to approximate wheel "steps" -- angleDelta is in 1/8 degrees, and according
229 // to Qt docs most mice step in 15 degree increments
230 resetBeforeChange( -event->angleDelta().y() / ( 15 * 8 ) );
231 }
232 QDateTimeEdit::wheelEvent( event );
233}
234
236{
237 QDateTimeEdit::showEvent( event );
238 if ( mAllowNull && mIsNull && lineEdit()->text() != mNullRepresentation )
239 {
240 displayNull();
241 }
242}
243
245void QgsDateTimeEdit::changed( const QVariant &dateTime )
246{
247 mIsEmpty = false;
249 if ( isNull != mIsNull )
250 {
251 mIsNull = isNull;
252 if ( mIsNull )
253 {
254 if ( mOriginalStyleSheet.isNull() )
255 {
256 mOriginalStyleSheet = lineEdit()->styleSheet();
257 }
258 lineEdit()->setStyleSheet( QStringLiteral( "QLineEdit { font-style: italic; color: grey; }" ) );
259 }
260 else
261 {
262 lineEdit()->setStyleSheet( mOriginalStyleSheet );
263 }
264 }
265
266 mClearAction->setVisible( mAllowNull && !mIsNull );
267 if ( !mBlockChangedSignal )
268 emitValueChanged( isNull ? QVariant() : dateTime );
269}
271
273{
274 return mNullRepresentation;
275}
276
278{
279 mNullRepresentation = nullRepresentation;
280 if ( mIsNull )
281 {
282 lineEdit()->setText( mNullRepresentation );
283 }
284}
285
286void QgsDateTimeEdit::displayNull( bool updateCalendar )
287{
288 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
289 if ( updateCalendar )
290 {
291 // set current time to minimum date time to avoid having
292 // a date selected in calendar widget
293 QDateTimeEdit::setDateTime( minimumDateTime() );
294 }
295 lineEdit()->setCursorPosition( lineEdit()->text().length() );
296 lineEdit()->setText( mNullRepresentation );
297 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
298}
299
300void QgsDateTimeEdit::emitValueChanged( const QVariant &value )
301{
302 emit QgsDateTimeEdit::valueChanged( value.toDateTime() );
303}
304
306{
307 return mAllowNull && mIsNull;
308}
309
310void QgsDateTimeEdit::displayCurrentDate()
311{
312 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
313 QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
314 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
315}
316
317void QgsDateTimeEdit::resetBeforeChange( int delta )
318{
319 QDateTime dt = QDateTime::currentDateTime();
320 switch ( currentSection() )
321 {
322 case QDateTimeEdit::DaySection:
323 dt = dt.addDays( delta );
324 break;
325 case QDateTimeEdit::MonthSection:
326 dt = dt.addMonths( delta );
327 break;
328 case QDateTimeEdit::YearSection:
329 dt = dt.addYears( delta );
330 break;
331 default:
332 break;
333 }
334 if ( dt < minimumDateTime() )
335 {
336 dt = minimumDateTime();
337 }
338 else if ( dt > maximumDateTime() )
339 {
340 dt = maximumDateTime();
341 }
342 QDateTimeEdit::setDateTime( dt );
343}
344
345void QgsDateTimeEdit::setMinimumEditDateTime()
346{
347 setDateRange( QDate( 1, 1, 1 ), maximumDate() );
348 setMinimumTime( QTime( 0, 0, 0 ) );
349 setMaximumTime( QTime( 23, 59, 59, 999 ) );
350}
351
353{
354 mIsEmpty = false;
355
356 // set an undefined date
357 if ( !dateTime.isValid() || dateTime.isNull() )
358 {
359 clear();
360 displayNull();
361 }
362 // Check if it's really changed or crash, see GH #29937
363 else if ( dateTime != QgsDateTimeEdit::dateTime() )
364 {
365 // changed emits a signal, so don't allow it to be emitted from setDateTime
367 // We need to set the time spec of the set datetime to the widget, otherwise
368 // the dateTime() getter would loose edit, and return local time.
369 // QDateTimeEdit::setTimeZone has been introduced in Qt 6.7 to properly handle
370 // timezone instead of QDateTimeEdit::setTimeSpec
371 // QDateTimeEdit::setTimeSpec has been deprecated since Qt 6.10
372#if QT_VERSION >= QT_VERSION_CHECK( 6, 7, 0 )
373 QDateTimeEdit::setTimeZone( dateTime.timeZone() );
374#else
375 QDateTimeEdit::setTimeSpec( dateTime.timeSpec() );
376#endif
377
378 QDateTimeEdit::setDateTime( dateTime );
380 changed( dateTime );
381 }
382}
383
385{
386 if ( isNull() )
387 {
388 return QDateTime();
389 }
390 else
391 {
392 return QDateTimeEdit::dateTime();
393 }
394}
395
397{
398 if ( isNull() )
399 {
400 return QTime();
401 }
402 else
403 {
404 return QDateTimeEdit::time();
405 }
406}
407
409{
410 if ( isNull() )
411 {
412 return QDate();
413 }
414 else
415 {
416 return QDateTimeEdit::date();
417 }
418}
419
420
421//
422// QgsTimeEdit
423//
424
425QgsTimeEdit::QgsTimeEdit( QWidget *parent )
426#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
427 : QgsDateTimeEdit( QTime(), QMetaType::Type::QTime, parent )
428#else
429 : QgsDateTimeEdit( QTime(), QMetaType::QTime, parent )
430#endif
431{
432}
433
434void QgsTimeEdit::setTime( const QTime &time )
435{
436 mIsEmpty = false;
437
438 // set an undefined date
439 if ( !time.isValid() || time.isNull() )
440 {
441 clear();
442 displayNull();
443 }
444 // Check if it's really changed or crash, see GH #29937
445 else if ( time != QgsTimeEdit::time() )
446 {
447 // changed emits a signal, so don't allow it to be emitted from setTime
449 QDateTimeEdit::setTime( time );
451 changed( time );
452 }
453}
454
455void QgsTimeEdit::emitValueChanged( const QVariant &value )
456{
457 emit timeValueChanged( value.toTime() );
458}
459
460
461//
462// QgsDateEdit
463//
464
465QgsDateEdit::QgsDateEdit( QWidget *parent )
466#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
467 : QgsDateTimeEdit( QDate(), QMetaType::Type::QDate, parent )
468#else
469 : QgsDateTimeEdit( QDate(), QMetaType::QDate, parent )
470#endif
471{
472}
473
474void QgsDateEdit::setDate( const QDate &date )
475{
476 mIsEmpty = false;
477
478 // set an undefined date
479 if ( !date.isValid() || date.isNull() )
480 {
481 clear();
482 displayNull();
483 }
484 // Check if it's really changed or crash, see GH #29937
485 else if ( date != QgsDateEdit::date() )
486 {
487 // changed emits a signal, so don't allow it to be emitted from setDate
489 QDateTimeEdit::setDate( date );
491 changed( date );
492 }
493}
494
495void QgsDateEdit::emitValueChanged( const QVariant &value )
496{
497 emit dateValueChanged( value.toDate() );
498}
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.
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.