libkdepim

kdateedit.cpp

00001 /*
00002     This file is part of libkdepim.
00003 
00004     Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (c) 2002 David Jarvie <software@astrojar.org.uk>
00006     Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00007     Copyright (c) 2004 Tobias Koenig <tokoe@kde.org>
00008 
00009     This library is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU Library General Public
00011     License as published by the Free Software Foundation; either
00012     version 2 of the License, or (at your option) any later version.
00013 
00014     This library is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017     Library General Public License for more details.
00018 
00019     You should have received a copy of the GNU Library General Public License
00020     along with this library; see the file COPYING.LIB.  If not, write to
00021     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00022     Boston, MA 02110-1301, USA.
00023 */
00024 
00025 #include <qapplication.h>
00026 #include <qlineedit.h>
00027 #include <qlistbox.h>
00028 #include <qvalidator.h>
00029 
00030 #include <kcalendarsystem.h>
00031 #include <kglobal.h>
00032 #include <kglobalsettings.h>
00033 #include <klocale.h>
00034 
00035 #include "kdateedit.h"
00036 
00037 class DateValidator : public QValidator
00038 {
00039   public:
00040     DateValidator( const QStringList &keywords, QWidget* parent, const char* name = 0 )
00041       : QValidator( parent, name ), mKeywords( keywords )
00042     {}
00043 
00044     virtual State validate( QString &str, int& ) const
00045     {
00046       int length = str.length();
00047 
00048       // empty string is intermediate so one can clear the edit line and start from scratch
00049       if ( length <= 0 )
00050         return Intermediate;
00051 
00052       if ( mKeywords.contains( str.lower() ) )
00053         return Acceptable;
00054 
00055       bool ok = false;
00056       KGlobal::locale()->readDate( str, &ok );
00057       if ( ok )
00058         return Acceptable;
00059       else
00060         return Intermediate;
00061     }
00062 
00063   private:
00064     QStringList mKeywords;
00065 };
00066 
00067 KDateEdit::KDateEdit( QWidget *parent, const char *name )
00068   : QComboBox( true, parent, name ),
00069     mReadOnly( false ),
00070     mDiscardNextMousePress( false )
00071 {
00072   // need at least one entry for popup to work
00073   setMaxCount( 1 );
00074 
00075   mDate = QDate::currentDate();
00076   QString today = KGlobal::locale()->formatDate( mDate, true );
00077 
00078   insertItem( today );
00079   setCurrentItem( 0 );
00080   changeItem( today, 0 );
00081   setMinimumSize( sizeHint() );
00082 
00083   connect( lineEdit(), SIGNAL( returnPressed() ),
00084            this, SLOT( lineEnterPressed() ) );
00085   connect( this, SIGNAL( textChanged( const QString& ) ),
00086            SLOT( slotTextChanged( const QString& ) ) );
00087 
00088   mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words );
00089   mPopup->hide();
00090   mPopup->installEventFilter( this );
00091 
00092   connect( mPopup, SIGNAL( dateChanged( QDate ) ),
00093            SLOT( dateSelected( QDate ) ) );
00094 
00095   // handle keyword entry
00096   setupKeywords();
00097   lineEdit()->installEventFilter( this );
00098 
00099   setValidator( new DateValidator( mKeywordMap.keys(), this ) );
00100 
00101   mTextChanged = false;
00102 }
00103 
00104 KDateEdit::~KDateEdit()
00105 {
00106   delete mPopup;
00107   mPopup = 0;
00108 }
00109 
00110 void KDateEdit::setDate( const QDate& date )
00111 {
00112   assignDate( date );
00113   updateView();
00114 }
00115 
00116 QDate KDateEdit::date() const
00117 {
00118   return mDate;
00119 }
00120 
00121 void KDateEdit::setReadOnly( bool readOnly )
00122 {
00123   mReadOnly = readOnly;
00124   lineEdit()->setReadOnly( readOnly );
00125 }
00126 
00127 bool KDateEdit::isReadOnly() const
00128 {
00129   return mReadOnly;
00130 }
00131 
00132 void KDateEdit::popup()
00133 {
00134   if ( mReadOnly )
00135     return;
00136 
00137   QRect desk = KGlobalSettings::desktopGeometry( this );
00138 
00139   QPoint popupPoint = mapToGlobal( QPoint( 0,0 ) );
00140 
00141   int dateFrameHeight = mPopup->sizeHint().height();
00142   if ( popupPoint.y() + height() + dateFrameHeight > desk.bottom() )
00143     popupPoint.setY( popupPoint.y() - dateFrameHeight );
00144   else
00145     popupPoint.setY( popupPoint.y() + height() );
00146 
00147   int dateFrameWidth = mPopup->sizeHint().width();
00148   if ( popupPoint.x() + dateFrameWidth > desk.right() )
00149     popupPoint.setX( desk.right() - dateFrameWidth );
00150 
00151   if ( popupPoint.x() < desk.left() )
00152     popupPoint.setX( desk.left() );
00153 
00154   if ( popupPoint.y() < desk.top() )
00155     popupPoint.setY( desk.top() );
00156 
00157   if ( mDate.isValid() )
00158     mPopup->setDate( mDate );
00159   else
00160     mPopup->setDate( QDate::currentDate() );
00161 
00162   mPopup->popup( popupPoint );
00163 
00164   // The combo box is now shown pressed. Make it show not pressed again
00165   // by causing its (invisible) list box to emit a 'selected' signal.
00166   // First, ensure that the list box contains the date currently displayed.
00167   QDate date = parseDate();
00168   assignDate( date );
00169   updateView();
00170   // Now, simulate an Enter to unpress it
00171   QListBox *lb = listBox();
00172   if (lb) {
00173     lb->setCurrentItem(0);
00174     QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, 0, 0);
00175     QApplication::postEvent(lb, keyEvent);
00176   }
00177 }
00178 
00179 void KDateEdit::dateSelected( QDate date )
00180 {
00181   if (assignDate( date ) ) {
00182     updateView();
00183     emit dateChanged( date );
00184     emit dateEntered( date );
00185 
00186     if ( date.isValid() ) {
00187       mPopup->hide();
00188     }
00189   }
00190 }
00191 
00192 void KDateEdit::lineEnterPressed()
00193 {
00194   bool replaced = false;
00195 
00196   QDate date = parseDate( &replaced );
00197 
00198   if (assignDate( date ) ) {
00199     if ( replaced )
00200       updateView();
00201 
00202     emit dateChanged( date );
00203     emit dateEntered( date );
00204   }
00205 }
00206 
00207 QDate KDateEdit::parseDate( bool *replaced ) const
00208 {
00209   QString text = currentText();
00210   QDate result;
00211 
00212   if ( replaced )
00213     (*replaced) = false;
00214 
00215   if ( text.isEmpty() )
00216     result = QDate();
00217   else if ( mKeywordMap.contains( text.lower() ) ) {
00218     QDate today = QDate::currentDate();
00219     int i = mKeywordMap[ text.lower() ];
00220     if ( i >= 100 ) {
00221       /* A day name has been entered. Convert to offset from today.
00222        * This uses some math tricks to figure out the offset in days
00223        * to the next date the given day of the week occurs. There
00224        * are two cases, that the new day is >= the current day, which means
00225        * the new day has not occurred yet or that the new day < the current day,
00226        * which means the new day is already passed (so we need to find the
00227        * day in the next week).
00228        */
00229       i -= 100;
00230       int currentDay = today.dayOfWeek();
00231       if ( i >= currentDay )
00232         i -= currentDay;
00233       else
00234         i += 7 - currentDay;
00235     }
00236 
00237     result = today.addDays( i );
00238     if ( replaced )
00239       (*replaced) = true;
00240   } else {
00241     result = KGlobal::locale()->readDate( text );
00242   }
00243 
00244   return result;
00245 }
00246 
00247 bool KDateEdit::eventFilter( QObject *object, QEvent *event )
00248 {
00249   if ( object == lineEdit() ) {
00250     // We only process the focus out event if the text has changed
00251     // since we got focus
00252     if ( (event->type() == QEvent::FocusOut) && mTextChanged ) {
00253       lineEnterPressed();
00254       mTextChanged = false;
00255     } else if ( event->type() == QEvent::KeyPress ) {
00256       // Up and down arrow keys step the date
00257       QKeyEvent* keyEvent = (QKeyEvent*)event;
00258 
00259       if ( keyEvent->key() == Qt::Key_Return ) {
00260         lineEnterPressed();
00261         return true;
00262       }
00263 
00264       int step = 0;
00265       if ( keyEvent->key() == Qt::Key_Up )
00266         step = 1;
00267       else if ( keyEvent->key() == Qt::Key_Down )
00268         step = -1;
00269       // TODO: If it's not an input key, but something like Return, Enter, Tab, etc..., don't eat the keypress, but handle it through to the default eventfilter!
00270       if ( step && !mReadOnly ) {
00271         QDate date = parseDate();
00272         if ( date.isValid() ) {
00273           date = date.addDays( step );
00274           if ( assignDate( date ) ) {
00275             updateView();
00276             emit dateChanged( date );
00277             emit dateEntered( date );
00278             return true;
00279           }
00280         }
00281       }
00282     }
00283   } else {
00284     // It's a date picker event
00285     switch ( event->type() ) {
00286       case QEvent::MouseButtonDblClick:
00287       case QEvent::MouseButtonPress: {
00288         QMouseEvent *mouseEvent = (QMouseEvent*)event;
00289         if ( !mPopup->rect().contains( mouseEvent->pos() ) ) {
00290           QPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() );
00291           if ( QApplication::widgetAt( globalPos, true ) == this ) {
00292             // The date picker is being closed by a click on the
00293             // KDateEdit widget. Avoid popping it up again immediately.
00294             mDiscardNextMousePress = true;
00295           }
00296         }
00297 
00298         break;
00299       }
00300       default:
00301         break;
00302     }
00303   }
00304 
00305   return false;
00306 }
00307 
00308 void KDateEdit::mousePressEvent( QMouseEvent *event )
00309 {
00310   if ( event->button() == Qt::LeftButton && mDiscardNextMousePress ) {
00311     mDiscardNextMousePress = false;
00312     return;
00313   }
00314 
00315   QComboBox::mousePressEvent( event );
00316 }
00317 
00318 void KDateEdit::slotTextChanged( const QString& )
00319 {
00320   QDate date = parseDate();
00321 
00322   if ( assignDate( date ) )
00323     emit dateChanged( date );
00324 
00325   mTextChanged = true;
00326 }
00327 
00328 void KDateEdit::setupKeywords()
00329 {
00330   // Create the keyword list. This will be used to match against when the user
00331   // enters information.
00332   mKeywordMap.insert( i18n( "tomorrow" ), 1 );
00333   mKeywordMap.insert( i18n( "today" ), 0 );
00334   mKeywordMap.insert( i18n( "yesterday" ), -1 );
00335 
00336   QString dayName;
00337   for ( int i = 1; i <= 7; ++i ) {
00338     dayName = KGlobal::locale()->calendar()->weekDayName( i ).lower();
00339     mKeywordMap.insert( dayName, i + 100 );
00340   }
00341 }
00342 
00343 bool KDateEdit::assignDate( const QDate& date )
00344 {
00345   mDate = date;
00346   mTextChanged = false;
00347   return true;
00348 }
00349 
00350 void KDateEdit::updateView()
00351 {
00352   QString dateString;
00353   if ( mDate.isValid() )
00354     dateString = KGlobal::locale()->formatDate( mDate, true );
00355 
00356   // We do not want to generate a signal here,
00357   // since we explicitly setting the date
00358   bool blocked = signalsBlocked();
00359   blockSignals( true );
00360   changeItem( dateString, 0 );
00361   blockSignals( blocked );
00362 }
00363 
00364 #include "kdateedit.moc"