summaryrefslogtreecommitdiffstats
path: root/libkdepim/kdateedit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libkdepim/kdateedit.cpp')
-rw-r--r--libkdepim/kdateedit.cpp364
1 files changed, 364 insertions, 0 deletions
diff --git a/libkdepim/kdateedit.cpp b/libkdepim/kdateedit.cpp
new file mode 100644
index 000000000..29b65279d
--- /dev/null
+++ b/libkdepim/kdateedit.cpp
@@ -0,0 +1,364 @@
+/*
+ This file is part of libkdepim.
+
+ Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2002 David Jarvie <software@astrojar.org.uk>
+ Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2004 Tobias Koenig <tokoe@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qapplication.h>
+#include <qlineedit.h>
+#include <qlistbox.h>
+#include <qvalidator.h>
+
+#include <kcalendarsystem.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+
+#include "kdateedit.h"
+
+class DateValidator : public QValidator
+{
+ public:
+ DateValidator( const QStringList &keywords, QWidget* parent, const char* name = 0 )
+ : QValidator( parent, name ), mKeywords( keywords )
+ {}
+
+ virtual State validate( QString &str, int& ) const
+ {
+ int length = str.length();
+
+ // empty string is intermediate so one can clear the edit line and start from scratch
+ if ( length <= 0 )
+ return Intermediate;
+
+ if ( mKeywords.contains( str.lower() ) )
+ return Acceptable;
+
+ bool ok = false;
+ KGlobal::locale()->readDate( str, &ok );
+ if ( ok )
+ return Acceptable;
+ else
+ return Intermediate;
+ }
+
+ private:
+ QStringList mKeywords;
+};
+
+KDateEdit::KDateEdit( QWidget *parent, const char *name )
+ : QComboBox( true, parent, name ),
+ mReadOnly( false ),
+ mDiscardNextMousePress( false )
+{
+ // need at least one entry for popup to work
+ setMaxCount( 1 );
+
+ mDate = QDate::currentDate();
+ QString today = KGlobal::locale()->formatDate( mDate, true );
+
+ insertItem( today );
+ setCurrentItem( 0 );
+ changeItem( today, 0 );
+ setMinimumSize( sizeHint() );
+
+ connect( lineEdit(), SIGNAL( returnPressed() ),
+ this, SLOT( lineEnterPressed() ) );
+ connect( this, SIGNAL( textChanged( const QString& ) ),
+ SLOT( slotTextChanged( const QString& ) ) );
+
+ mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words );
+ mPopup->hide();
+ mPopup->installEventFilter( this );
+
+ connect( mPopup, SIGNAL( dateChanged( QDate ) ),
+ SLOT( dateSelected( QDate ) ) );
+
+ // handle keyword entry
+ setupKeywords();
+ lineEdit()->installEventFilter( this );
+
+ setValidator( new DateValidator( mKeywordMap.keys(), this ) );
+
+ mTextChanged = false;
+}
+
+KDateEdit::~KDateEdit()
+{
+ delete mPopup;
+ mPopup = 0;
+}
+
+void KDateEdit::setDate( const QDate& date )
+{
+ assignDate( date );
+ updateView();
+}
+
+QDate KDateEdit::date() const
+{
+ return mDate;
+}
+
+void KDateEdit::setReadOnly( bool readOnly )
+{
+ mReadOnly = readOnly;
+ lineEdit()->setReadOnly( readOnly );
+}
+
+bool KDateEdit::isReadOnly() const
+{
+ return mReadOnly;
+}
+
+void KDateEdit::popup()
+{
+ if ( mReadOnly )
+ return;
+
+ QRect desk = KGlobalSettings::desktopGeometry( this );
+
+ QPoint popupPoint = mapToGlobal( QPoint( 0,0 ) );
+
+ int dateFrameHeight = mPopup->sizeHint().height();
+ if ( popupPoint.y() + height() + dateFrameHeight > desk.bottom() )
+ popupPoint.setY( popupPoint.y() - dateFrameHeight );
+ else
+ popupPoint.setY( popupPoint.y() + height() );
+
+ int dateFrameWidth = mPopup->sizeHint().width();
+ if ( popupPoint.x() + dateFrameWidth > desk.right() )
+ popupPoint.setX( desk.right() - dateFrameWidth );
+
+ if ( popupPoint.x() < desk.left() )
+ popupPoint.setX( desk.left() );
+
+ if ( popupPoint.y() < desk.top() )
+ popupPoint.setY( desk.top() );
+
+ if ( mDate.isValid() )
+ mPopup->setDate( mDate );
+ else
+ mPopup->setDate( QDate::currentDate() );
+
+ mPopup->popup( popupPoint );
+
+ // The combo box is now shown pressed. Make it show not pressed again
+ // by causing its (invisible) list box to emit a 'selected' signal.
+ // First, ensure that the list box contains the date currently displayed.
+ QDate date = parseDate();
+ assignDate( date );
+ updateView();
+ // Now, simulate an Enter to unpress it
+ QListBox *lb = listBox();
+ if (lb) {
+ lb->setCurrentItem(0);
+ QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, 0, 0);
+ QApplication::postEvent(lb, keyEvent);
+ }
+}
+
+void KDateEdit::dateSelected( QDate date )
+{
+ if (assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+ emit dateEntered( date );
+
+ if ( date.isValid() ) {
+ mPopup->hide();
+ }
+ }
+}
+
+void KDateEdit::lineEnterPressed()
+{
+ bool replaced = false;
+
+ QDate date = parseDate( &replaced );
+
+ if (assignDate( date ) ) {
+ if ( replaced )
+ updateView();
+
+ emit dateChanged( date );
+ emit dateEntered( date );
+ }
+}
+
+QDate KDateEdit::parseDate( bool *replaced ) const
+{
+ QString text = currentText();
+ QDate result;
+
+ if ( replaced )
+ (*replaced) = false;
+
+ if ( text.isEmpty() )
+ result = QDate();
+ else if ( mKeywordMap.contains( text.lower() ) ) {
+ QDate today = QDate::currentDate();
+ int i = mKeywordMap[ text.lower() ];
+ if ( i >= 100 ) {
+ /* A day name has been entered. Convert to offset from today.
+ * This uses some math tricks to figure out the offset in days
+ * to the next date the given day of the week occurs. There
+ * are two cases, that the new day is >= the current day, which means
+ * the new day has not occurred yet or that the new day < the current day,
+ * which means the new day is already passed (so we need to find the
+ * day in the next week).
+ */
+ i -= 100;
+ int currentDay = today.dayOfWeek();
+ if ( i >= currentDay )
+ i -= currentDay;
+ else
+ i += 7 - currentDay;
+ }
+
+ result = today.addDays( i );
+ if ( replaced )
+ (*replaced) = true;
+ } else {
+ result = KGlobal::locale()->readDate( text );
+ }
+
+ return result;
+}
+
+bool KDateEdit::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object == lineEdit() ) {
+ // We only process the focus out event if the text has changed
+ // since we got focus
+ if ( (event->type() == QEvent::FocusOut) && mTextChanged ) {
+ lineEnterPressed();
+ mTextChanged = false;
+ } else if ( event->type() == QEvent::KeyPress ) {
+ // Up and down arrow keys step the date
+ QKeyEvent* keyEvent = (QKeyEvent*)event;
+
+ if ( keyEvent->key() == Qt::Key_Return ) {
+ lineEnterPressed();
+ return true;
+ }
+
+ int step = 0;
+ if ( keyEvent->key() == Qt::Key_Up )
+ step = 1;
+ else if ( keyEvent->key() == Qt::Key_Down )
+ step = -1;
+ // 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!
+ if ( step && !mReadOnly ) {
+ QDate date = parseDate();
+ if ( date.isValid() ) {
+ date = date.addDays( step );
+ if ( assignDate( date ) ) {
+ updateView();
+ emit dateChanged( date );
+ emit dateEntered( date );
+ return true;
+ }
+ }
+ }
+ }
+ } else {
+ // It's a date picker event
+ switch ( event->type() ) {
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonPress: {
+ QMouseEvent *mouseEvent = (QMouseEvent*)event;
+ if ( !mPopup->rect().contains( mouseEvent->pos() ) ) {
+ QPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() );
+ if ( QApplication::widgetAt( globalPos, true ) == this ) {
+ // The date picker is being closed by a click on the
+ // KDateEdit widget. Avoid popping it up again immediately.
+ mDiscardNextMousePress = true;
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+void KDateEdit::mousePressEvent( QMouseEvent *event )
+{
+ if ( event->button() == Qt::LeftButton && mDiscardNextMousePress ) {
+ mDiscardNextMousePress = false;
+ return;
+ }
+
+ QComboBox::mousePressEvent( event );
+}
+
+void KDateEdit::slotTextChanged( const QString& )
+{
+ QDate date = parseDate();
+
+ if ( assignDate( date ) )
+ emit dateChanged( date );
+
+ mTextChanged = true;
+}
+
+void KDateEdit::setupKeywords()
+{
+ // Create the keyword list. This will be used to match against when the user
+ // enters information.
+ mKeywordMap.insert( i18n( "tomorrow" ), 1 );
+ mKeywordMap.insert( i18n( "today" ), 0 );
+ mKeywordMap.insert( i18n( "yesterday" ), -1 );
+
+ QString dayName;
+ for ( int i = 1; i <= 7; ++i ) {
+ dayName = KGlobal::locale()->calendar()->weekDayName( i ).lower();
+ mKeywordMap.insert( dayName, i + 100 );
+ }
+}
+
+bool KDateEdit::assignDate( const QDate& date )
+{
+ mDate = date;
+ mTextChanged = false;
+ return true;
+}
+
+void KDateEdit::updateView()
+{
+ QString dateString;
+ if ( mDate.isValid() )
+ dateString = KGlobal::locale()->formatDate( mDate, true );
+
+ // We do not want to generate a signal here,
+ // since we explicitly setting the date
+ bool blocked = signalsBlocked();
+ blockSignals( true );
+ changeItem( dateString, 0 );
+ blockSignals( blocked );
+}
+
+#include "kdateedit.moc"