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