libkdepim

kdateedit.cpp

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