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