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