kdateedit.cpp
00001 /* 00002 This file is part of libtdepim. 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 <tqapplication.h> 00026 #include <tqlineedit.h> 00027 #include <tqlistbox.h> 00028 #include <tqvalidator.h> 00029 00030 #include <kcalendarsystem.h> 00031 #include <tdeglobal.h> 00032 #include <tdeglobalsettings.h> 00033 #include <tdelocale.h> 00034 00035 #include "kdateedit.h" 00036 00037 class DateValidator : public TQValidator 00038 { 00039 public: 00040 DateValidator( const TQStringList &keywords, TQWidget* parent, const char* name = 0 ) 00041 : TQValidator( TQT_TQOBJECT(parent), name ), mKeywords( keywords ) 00042 {} 00043 00044 virtual State validate( TQString &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 TDEGlobal::locale()->readDate( str, &ok ); 00057 if ( ok ) 00058 return Acceptable; 00059 else 00060 return Intermediate; 00061 } 00062 00063 private: 00064 TQStringList mKeywords; 00065 }; 00066 00067 KDateEdit::KDateEdit( TQWidget *parent, const char *name ) 00068 : TQComboBox( 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 = TQDate::currentDate(); 00076 TQString today = TDEGlobal::locale()->formatDate( mDate, true ); 00077 00078 insertItem( today ); 00079 setCurrentItem( 0 ); 00080 changeItem( today, 0 ); 00081 setMinimumSize( sizeHint() ); 00082 00083 connect( lineEdit(), TQT_SIGNAL( returnPressed() ), 00084 this, TQT_SLOT( lineEnterPressed() ) ); 00085 connect( this, TQT_SIGNAL( textChanged( const TQString& ) ), 00086 TQT_SLOT( slotTextChanged( const TQString& ) ) ); 00087 00088 mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words ); 00089 mPopup->hide(); 00090 mPopup->installEventFilter( this ); 00091 00092 connect( mPopup, TQT_SIGNAL( dateChanged( TQDate ) ), 00093 TQT_SLOT( dateSelected( TQDate ) ) ); 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 TQDate& date ) 00111 { 00112 assignDate( date ); 00113 updateView(); 00114 } 00115 00116 TQDate 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 TQRect desk = TDEGlobalSettings::desktopGeometry( this ); 00138 00139 TQPoint popupPoint = mapToGlobal( TQPoint( 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( TQDate::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 TQDate date = parseDate(); 00168 assignDate( date ); 00169 updateView(); 00170 // Now, simulate an Enter to unpress it 00171 TQListBox *lb = listBox(); 00172 if (lb) { 00173 lb->setCurrentItem(0); 00174 TQKeyEvent* keyEvent = new TQKeyEvent(TQEvent::KeyPress, TQt::Key_Enter, 0, 0); 00175 TQApplication::postEvent(lb, keyEvent); 00176 } 00177 } 00178 00179 void KDateEdit::dateSelected( TQDate 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 TQDate 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 TQDate KDateEdit::parseDate( bool *replaced ) const 00208 { 00209 TQString text = currentText(); 00210 TQDate result; 00211 00212 if ( replaced ) 00213 (*replaced) = false; 00214 00215 if ( text.isEmpty() ) 00216 result = TQDate(); 00217 else if ( mKeywordMap.contains( text.lower() ) ) { 00218 TQDate today = TQDate::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 = TDEGlobal::locale()->readDate( text ); 00242 } 00243 00244 return result; 00245 } 00246 00247 bool KDateEdit::eventFilter( TQObject *object, TQEvent *event ) 00248 { 00249 if ( TQT_BASE_OBJECT(object) == TQT_BASE_OBJECT(lineEdit()) ) { 00250 // We only process the focus out event if the text has changed 00251 // since we got focus 00252 if ( (event->type() == TQEvent::FocusOut) && mTextChanged ) { 00253 lineEnterPressed(); 00254 mTextChanged = false; 00255 } else if ( event->type() == TQEvent::KeyPress ) { 00256 // Up and down arrow keys step the date 00257 TQKeyEvent* keyEvent = (TQKeyEvent*)event; 00258 00259 if ( keyEvent->key() == TQt::Key_Return ) { 00260 lineEnterPressed(); 00261 return true; 00262 } 00263 00264 int step = 0; 00265 if ( keyEvent->key() == TQt::Key_Up ) 00266 step = 1; 00267 else if ( keyEvent->key() == TQt::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 TQDate 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 TQEvent::MouseButtonDblClick: 00287 case TQEvent::MouseButtonPress: { 00288 TQMouseEvent *mouseEvent = (TQMouseEvent*)event; 00289 if ( !TQT_TQRECT_OBJECT(mPopup->rect()).contains( mouseEvent->pos() ) ) { 00290 TQPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() ); 00291 if ( TQApplication::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( TQMouseEvent *event ) 00309 { 00310 if ( event->button() == Qt::LeftButton && mDiscardNextMousePress ) { 00311 mDiscardNextMousePress = false; 00312 return; 00313 } 00314 00315 TQComboBox::mousePressEvent( event ); 00316 } 00317 00318 void KDateEdit::slotTextChanged( const TQString& ) 00319 { 00320 TQDate 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 TQString dayName; 00337 for ( int i = 1; i <= 7; ++i ) { 00338 dayName = TDEGlobal::locale()->calendar()->weekDayName( i ).lower(); 00339 mKeywordMap.insert( dayName, i + 100 ); 00340 } 00341 } 00342 00343 bool KDateEdit::assignDate( const TQDate& date ) 00344 { 00345 mDate = date; 00346 mTextChanged = false; 00347 return true; 00348 } 00349 00350 void KDateEdit::updateView() 00351 { 00352 TQString dateString; 00353 if ( mDate.isValid() ) 00354 dateString = TDEGlobal::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"