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       if ( step && !mReadOnly ) {
00275         QDate date = parseDate();
00276         if ( date.isValid() ) {
00277           date = date.addDays( step );
00278           if ( assignDate( date ) ) {
00279             updateView();
00280             emit dateChanged( date );
00281             return true;
00282           }
00283         }
00284       }
00285     }
00286   } else {
00287     // It's a date picker event
00288     switch ( event->type() ) {
00289       case QEvent::MouseButtonDblClick:
00290       case QEvent::MouseButtonPress: {
00291         QMouseEvent *mouseEvent = (QMouseEvent*)event;
00292         if ( !mPopup->rect().contains( mouseEvent->pos() ) ) {
00293           QPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() );
00294           if ( QApplication::widgetAt( globalPos, true ) == this ) {
00295             // The date picker is being closed by a click on the
00296             // KDateEdit widget. Avoid popping it up again immediately.
00297             mDiscardNextMousePress = true;
00298           }
00299         }
00300 
00301         break;
00302       }
00303       default:
00304         break;
00305     }
00306   }
00307 
00308   return false;
00309 }
00310 
00311 void KDateEdit::mousePressEvent( QMouseEvent *event )
00312 {
00313   if ( event->button() == Qt::LeftButton && mDiscardNextMousePress ) {
00314     mDiscardNextMousePress = false;
00315     return;
00316   }
00317 
00318   QComboBox::mousePressEvent( event );
00319 }
00320 
00321 void KDateEdit::slotTextChanged( const QString& )
00322 {
00323   QDate date = parseDate();
00324 
00325   if ( assignDate( date ) )
00326     emit dateChanged( date );
00327 
00328   mTextChanged = true;
00329 }
00330 
00331 void KDateEdit::setupKeywords()
00332 {
00333   // Create the keyword list. This will be used to match against when the user
00334   // enters information.
00335   mKeywordMap.insert( i18n( "tomorrow" ), 1 );
00336   mKeywordMap.insert( i18n( "today" ), 0 );
00337   mKeywordMap.insert( i18n( "yesterday" ), -1 );
00338 
00339   QString dayName;
00340   for ( int i = 1; i <= 7; ++i ) {
00341     dayName = KGlobal::locale()->calendar()->weekDayName( i ).lower();
00342     mKeywordMap.insert( dayName, i + 100 );
00343   }
00344 }
00345 
00346 bool KDateEdit::assignDate( const QDate& date )
00347 {
00348   mDate = date;
00349   mTextChanged = false;
00350   return true;
00351 }
00352 
00353 void KDateEdit::updateView()
00354 {
00355   QString dateString;
00356   if ( mDate.isValid() )
00357     dateString = KGlobal::locale()->formatDate( mDate, true );
00358 
00359   // We do not want to generate a signal here,
00360   // since we explicitly setting the date
00361   bool blocked = signalsBlocked();
00362   blockSignals( true );
00363   changeItem( dateString, 0 );
00364   blockSignals( blocked );
00365 }
00366 
00367 #include "kdateedit.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys