1 /***************************************************************************
2 qgsdatetimeedit.cpp
3 --------------------------------------
4 Date : 08.2014
5 Copyright : (C) 2014 Denis Rouzaud
6 Email : denis.rouzaud@gmail.com
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 "qgslogger.h"
28
29
30
QgsDateTimeEdit(QWidget * parent)31 QgsDateTimeEdit::QgsDateTimeEdit( QWidget *parent )
32 : QgsDateTimeEdit( QDateTime(), QVariant::DateTime, parent )
33 {
34
35 }
36
37 ///@cond PRIVATE
QgsDateTimeEdit(const QVariant & var,QVariant::Type parserType,QWidget * parent)38 QgsDateTimeEdit::QgsDateTimeEdit( const QVariant &var, QVariant::Type parserType, QWidget *parent )
39 : QDateTimeEdit( var, parserType, parent )
40 , mNullRepresentation( QgsApplication::nullRepresentation() )
41 {
42 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 }
59 ///@endcond
60
setAllowNull(bool allowNull)61 void QgsDateTimeEdit::setAllowNull( bool allowNull )
62 {
63 mAllowNull = allowNull;
64 mClearAction->setVisible( mAllowNull && ( !mIsNull || mIsEmpty ) );
65 }
66
67
clear()68 void QgsDateTimeEdit::clear()
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( QDateTime() );
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
setEmpty()91 void QgsDateTimeEdit::setEmpty()
92 {
93 mClearAction->setVisible( mAllowNull );
94 mIsEmpty = true;
95 }
96
mousePressEvent(QMouseEvent * event)97 void QgsDateTimeEdit::mousePressEvent( QMouseEvent *event )
98 {
99 // catch mouse press on the button (when the current value is null)
100 // in non-calendar mode: modify the date so it leads to showing current date (don't bother about time)
101 // in calendar mode: be sure NULL is displayed when needed and show page of current date in calendar widget
102
103 bool updateCalendar = false;
104
105 if ( mIsNull )
106 {
107 QStyle::SubControl control;
108 if ( calendarPopup() )
109 {
110 QStyleOptionComboBox optCombo;
111 optCombo.init( this );
112 optCombo.editable = true;
113 optCombo.subControls = QStyle::SC_All;
114 control = style()->hitTestComplexControl( QStyle::CC_ComboBox, &optCombo, event->pos(), this );
115
116 if ( control == QStyle::SC_ComboBoxArrow && calendarWidget() )
117 {
118 mCurrentPressEvent = true;
119 // ensure the line edit still displays NULL
120 updateCalendar = true;
121 displayNull( updateCalendar );
122 mCurrentPressEvent = false;
123 }
124 }
125 else
126 {
127 QStyleOptionSpinBox opt;
128 this->initStyleOption( &opt );
129 control = style()->hitTestComplexControl( QStyle::CC_SpinBox, &opt, event->pos(), this );
130
131 if ( control == QStyle::SC_SpinBoxDown || control == QStyle::SC_SpinBoxUp )
132 {
133 mCurrentPressEvent = true;
134 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
135 resetBeforeChange( control == QStyle::SC_SpinBoxDown ? -1 : 1 );
136 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
137 mCurrentPressEvent = false;
138 }
139 }
140 }
141
142 QDateTimeEdit::mousePressEvent( event );
143
144 if ( updateCalendar )
145 {
146 // set calendar page to current date to avoid going to minimal date page when value is null
147 calendarWidget()->setCurrentPage( QDate::currentDate().year(), QDate::currentDate().month() );
148 }
149 }
150
focusOutEvent(QFocusEvent * event)151 void QgsDateTimeEdit::focusOutEvent( QFocusEvent *event )
152 {
153 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
154 {
155 QAbstractSpinBox::focusOutEvent( event );
156 if ( lineEdit()->text() != mNullRepresentation )
157 {
158 displayNull();
159 }
160 emit editingFinished();
161 }
162 else
163 {
164 QDateTimeEdit::focusOutEvent( event );
165 }
166 }
167
focusInEvent(QFocusEvent * event)168 void QgsDateTimeEdit::focusInEvent( QFocusEvent *event )
169 {
170 if ( mAllowNull && mIsNull && !mCurrentPressEvent )
171 {
172 QAbstractSpinBox::focusInEvent( event );
173
174 displayCurrentDate();
175 }
176 else
177 {
178 QDateTimeEdit::focusInEvent( event );
179 }
180 }
181
wheelEvent(QWheelEvent * event)182 void QgsDateTimeEdit::wheelEvent( QWheelEvent *event )
183 {
184 // dateTime might have been set to minimum in calendar mode
185 if ( mAllowNull && mIsNull )
186 {
187 resetBeforeChange( -event->delta() );
188 }
189 QDateTimeEdit::wheelEvent( event );
190 }
191
showEvent(QShowEvent * event)192 void QgsDateTimeEdit::showEvent( QShowEvent *event )
193 {
194 QDateTimeEdit::showEvent( event );
195 if ( mAllowNull && mIsNull &&
196 lineEdit()->text() != mNullRepresentation )
197 {
198 displayNull();
199 }
200 }
201
202 ///@cond PRIVATE
changed(const QVariant & dateTime)203 void QgsDateTimeEdit::changed( const QVariant &dateTime )
204 {
205 mIsEmpty = false;
206 bool isNull = dateTime.isNull();
207 if ( isNull != mIsNull )
208 {
209 mIsNull = isNull;
210 if ( mIsNull )
211 {
212 if ( mOriginalStyleSheet.isNull() )
213 {
214 mOriginalStyleSheet = lineEdit()->styleSheet();
215 }
216 lineEdit()->setStyleSheet( QStringLiteral( "QLineEdit { font-style: italic; color: grey; }" ) );
217 }
218 else
219 {
220 lineEdit()->setStyleSheet( mOriginalStyleSheet );
221 }
222 }
223
224 mClearAction->setVisible( mAllowNull && !mIsNull );
225 if ( !mBlockChangedSignal )
226 emitValueChanged( dateTime );
227 }
228 ///@endcond
229
nullRepresentation() const230 QString QgsDateTimeEdit::nullRepresentation() const
231 {
232 return mNullRepresentation;
233 }
234
setNullRepresentation(const QString & nullRepresentation)235 void QgsDateTimeEdit::setNullRepresentation( const QString &nullRepresentation )
236 {
237 mNullRepresentation = nullRepresentation;
238 if ( mIsNull )
239 {
240 lineEdit()->setText( mNullRepresentation );
241 }
242 }
243
displayNull(bool updateCalendar)244 void QgsDateTimeEdit::displayNull( bool updateCalendar )
245 {
246 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
247 if ( updateCalendar )
248 {
249 // set current time to minimum date time to avoid having
250 // a date selected in calendar widget
251 QDateTimeEdit::setDateTime( minimumDateTime() );
252 }
253 lineEdit()->setCursorPosition( lineEdit()->text().length() );
254 lineEdit()->setText( mNullRepresentation );
255 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
256 }
257
emitValueChanged(const QVariant & value)258 void QgsDateTimeEdit::emitValueChanged( const QVariant &value )
259 {
260 emit QgsDateTimeEdit::valueChanged( value.toDateTime() );
261 }
262
isNull() const263 bool QgsDateTimeEdit::isNull() const
264 {
265 return mAllowNull && mIsNull;
266 }
267
displayCurrentDate()268 void QgsDateTimeEdit::displayCurrentDate()
269 {
270 disconnect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
271 QDateTimeEdit::setDateTime( QDateTime::currentDateTime() );
272 connect( this, &QDateTimeEdit::dateTimeChanged, this, &QgsDateTimeEdit::changed );
273 }
274
resetBeforeChange(int delta)275 void QgsDateTimeEdit::resetBeforeChange( int delta )
276 {
277 QDateTime dt = QDateTime::currentDateTime();
278 switch ( currentSection() )
279 {
280 case QDateTimeEdit::DaySection:
281 dt = dt.addDays( delta );
282 break;
283 case QDateTimeEdit::MonthSection:
284 dt = dt.addMonths( delta );
285 break;
286 case QDateTimeEdit::YearSection:
287 dt = dt.addYears( delta );
288 break;
289 default:
290 break;
291 }
292 if ( dt < minimumDateTime() )
293 {
294 dt = minimumDateTime();
295 }
296 else if ( dt > maximumDateTime() )
297 {
298 dt = maximumDateTime();
299 }
300 QDateTimeEdit::setDateTime( dt );
301 }
302
setDateTime(const QDateTime & dateTime)303 void QgsDateTimeEdit::setDateTime( const QDateTime &dateTime )
304 {
305 mIsEmpty = false;
306
307 // set an undefined date
308 if ( !dateTime.isValid() || dateTime.isNull() )
309 {
310 clear();
311 displayNull();
312 }
313 // Check if it's really changed or crash, see GH #29937
314 else if ( dateTime != QgsDateTimeEdit::dateTime() )
315 {
316 // changed emits a signal, so don't allow it to be emitted from setDateTime
317 mBlockChangedSignal++;
318 QDateTimeEdit::setDateTime( dateTime );
319 mBlockChangedSignal--;
320 changed( dateTime );
321 }
322 }
323
dateTime() const324 QDateTime QgsDateTimeEdit::dateTime() const
325 {
326 if ( isNull() )
327 {
328 return QDateTime();
329 }
330 else
331 {
332 return QDateTimeEdit::dateTime();
333 }
334 }
335
time() const336 QTime QgsDateTimeEdit::time() const
337 {
338 if ( isNull() )
339 {
340 return QTime();
341 }
342 else
343 {
344 return QDateTimeEdit::time();
345 }
346 }
347
date() const348 QDate QgsDateTimeEdit::date() const
349 {
350 if ( isNull() )
351 {
352 return QDate();
353 }
354 else
355 {
356 return QDateTimeEdit::date();
357 }
358 }
359
360
361 //
362 // QgsTimeEdit
363 //
364
QgsTimeEdit(QWidget * parent)365 QgsTimeEdit::QgsTimeEdit( QWidget *parent )
366 : QgsDateTimeEdit( QTime(), QVariant::Time, parent )
367 {
368
369 }
370
setTime(const QTime & time)371 void QgsTimeEdit::setTime( const QTime &time )
372 {
373 mIsEmpty = false;
374
375 // set an undefined date
376 if ( !time.isValid() || time.isNull() )
377 {
378 clear();
379 displayNull();
380 }
381 // Check if it's really changed or crash, see GH #29937
382 else if ( time != QgsTimeEdit::time() )
383 {
384 // changed emits a signal, so don't allow it to be emitted from setTime
385 mBlockChangedSignal++;
386 QDateTimeEdit::setTime( time );
387 mBlockChangedSignal--;
388 changed( time );
389 }
390 }
391
emitValueChanged(const QVariant & value)392 void QgsTimeEdit::emitValueChanged( const QVariant &value )
393 {
394 emit timeValueChanged( value.toTime() );
395 }
396
397
398 //
399 // QgsDateEdit
400 //
401
QgsDateEdit(QWidget * parent)402 QgsDateEdit::QgsDateEdit( QWidget *parent )
403 : QgsDateTimeEdit( QDate(), QVariant::Date, parent )
404 {
405
406 }
407
setDate(const QDate & date)408 void QgsDateEdit::setDate( const QDate &date )
409 {
410 mIsEmpty = false;
411
412 // set an undefined date
413 if ( !date.isValid() || date.isNull() )
414 {
415 clear();
416 displayNull();
417 }
418 // Check if it's really changed or crash, see GH #29937
419 else if ( date != QgsDateEdit::date() )
420 {
421 // changed emits a signal, so don't allow it to be emitted from setDate
422 mBlockChangedSignal++;
423 QDateTimeEdit::setDate( date );
424 mBlockChangedSignal--;
425 changed( date );
426 }
427 }
428
emitValueChanged(const QVariant & value)429 void QgsDateEdit::emitValueChanged( const QVariant &value )
430 {
431 emit dateValueChanged( value.toDate() );
432 }
433