Warning, file /office/skrooge/skgbasegui/kdateedit.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of libkdepim. 0003 0004 Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org> 0005 Copyright (c) 2002 David Jarvie <software@astrojar.org.uk> 0006 Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0007 Copyright (c) 2004 Tobias Koenig <tokoe@kde.org> 0008 0009 This library is free software; you can redistribute it and/or 0010 modify it under the terms of the GNU Library General Public 0011 License as published by the Free Software Foundation; either 0012 0013 This library is distributed in the hope that it will be useful, 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 // krazy:excludeall=qclasses as we want to subclass from QComboBox, not KComboBox 0023 0024 #include "kdateedit.h" 0025 0026 #include <klocalizedstring.h> 0027 0028 #include <qabstractitemview.h> 0029 #include <qapplication.h> 0030 #include <qcompleter.h> 0031 #include <qdesktopwidget.h> 0032 #include <qevent.h> 0033 #include <qlineedit.h> 0034 #include <qscreen.h> 0035 #include <qvalidator.h> 0036 0037 #include "kdatevalidator.h" 0038 0039 using namespace KPIM; 0040 0041 KDateEdit::KDateEdit(QWidget* iParent) 0042 : QComboBox(iParent), mReadOnly(false) 0043 { 0044 // need at least one entry for popup to work 0045 setMaxCount(1); 0046 setEditable(true); 0047 0048 // Check if we can use the QLocale::ShortFormat 0049 mAlternativeDateFormatToUse = QLocale().dateFormat(QLocale::ShortFormat); 0050 if (!mAlternativeDateFormatToUse.contains(QStringLiteral("yyyy"))) { 0051 mAlternativeDateFormatToUse = mAlternativeDateFormatToUse.replace(QStringLiteral("yy"), QStringLiteral("yyyy")); 0052 } 0053 0054 mDate = QDate::currentDate(); 0055 QString today = QLocale().toString(mDate, mAlternativeDateFormatToUse); 0056 0057 addItem(today); 0058 setCurrentIndex(0); 0059 0060 connect(lineEdit(), &QLineEdit::returnPressed, this, &KDateEdit::lineEnterPressed); 0061 connect(this, &KDateEdit::editTextChanged, this, &KDateEdit::slotTextChanged); 0062 0063 mPopup = new KDatePickerPopup(KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), this); 0064 mPopup->hide(); 0065 mPopup->installEventFilter(this); 0066 0067 connect(mPopup, &KDatePickerPopup::dateChanged, this, &KDateEdit::dateSelected); 0068 0069 // handle keyword entry 0070 setupKeywords(); 0071 lineEdit()->installEventFilter(this); 0072 0073 auto newValidator = new KDateValidator(this); 0074 newValidator->setKeywords(mKeywordMap.keys()); 0075 setValidator(newValidator); 0076 0077 mTextChanged = false; 0078 } 0079 0080 KDateEdit::~KDateEdit() 0081 = default; 0082 0083 void KDateEdit::setDate(QDate iDate) 0084 { 0085 assignDate(iDate); 0086 updateView(); 0087 } 0088 0089 QDate KDateEdit::date() const 0090 { 0091 return mDate; 0092 } 0093 0094 void KDateEdit::setReadOnly(bool readOnly) 0095 { 0096 mReadOnly = readOnly; 0097 lineEdit()->setReadOnly(readOnly); 0098 } 0099 0100 bool KDateEdit::isReadOnly() const 0101 { 0102 return mReadOnly; 0103 } 0104 0105 void KDateEdit::showPopup() 0106 { 0107 if (mReadOnly) { 0108 return; 0109 } 0110 0111 QRect desk = QGuiApplication::primaryScreen()->geometry(); 0112 0113 QPoint popupPoint = mapToGlobal(QPoint(0, 0)); 0114 0115 int dateFrameHeight = mPopup->sizeHint().height(); 0116 if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) { 0117 popupPoint.setY(popupPoint.y() - dateFrameHeight); 0118 } else { 0119 popupPoint.setY(popupPoint.y() + height()); 0120 } 0121 0122 int dateFrameWidth = mPopup->sizeHint().width(); 0123 if (popupPoint.x() + dateFrameWidth > desk.right()) { 0124 popupPoint.setX(desk.right() - dateFrameWidth); 0125 } 0126 0127 if (popupPoint.x() < desk.left()) { 0128 popupPoint.setX(desk.left()); 0129 } 0130 0131 if (popupPoint.y() < desk.top()) { 0132 popupPoint.setY(desk.top()); 0133 } 0134 0135 if (mDate.isValid()) { 0136 mPopup->setDate(mDate); 0137 } else { 0138 mPopup->setDate(QDate::currentDate()); 0139 } 0140 0141 mPopup->popup(popupPoint); 0142 0143 // The combo box is now shown pressed. Make it show not pressed again 0144 // by causing its (invisible) list box to emit a 'selected' signal. 0145 // First, ensure that the list box contains the date currently displayed. 0146 QDate date2 = parseDate(); 0147 assignDate(date2); 0148 updateView(); 0149 0150 // Now, simulate an Enter to unpress it 0151 QAbstractItemView* lb = view(); 0152 if (lb != nullptr) { 0153 lb->setCurrentIndex(lb->model()->index(0, 0)); 0154 QKeyEvent* keyEvent = 0155 new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier); 0156 QApplication::postEvent(lb, keyEvent); 0157 } 0158 } 0159 0160 void KDateEdit::dateSelected(QDate iDate) 0161 { 0162 if (assignDate(iDate)) { 0163 updateView(); 0164 emit dateChanged(iDate); 0165 emit dateEntered(iDate); 0166 0167 if (iDate.isValid()) { 0168 mPopup->hide(); 0169 } 0170 } 0171 } 0172 0173 void KDateEdit::lineEnterPressed() 0174 { 0175 bool replaced = false; 0176 0177 QDate date2 = parseDate(&replaced); 0178 0179 if (assignDate(date2)) { 0180 if (replaced) { 0181 updateView(); 0182 } 0183 0184 emit dateChanged(date2); 0185 emit dateEntered(date2); 0186 } 0187 } 0188 0189 QDate KDateEdit::parseDate(bool* replaced) const 0190 { 0191 QString text = currentText(); 0192 QDate result; 0193 0194 if (replaced != nullptr) { 0195 (*replaced) = false; 0196 } 0197 0198 if (text.isEmpty()) { 0199 result = QDate(); 0200 } else if (mKeywordMap.contains(text.toLower())) { 0201 QDate today = QDate::currentDate(); 0202 int i = mKeywordMap[ text.toLower()]; 0203 if (i == 30) { 0204 today = today.addMonths(1); 0205 } else if (i >= 100) { 0206 /* A day name has been entered. Convert to offset from today. 0207 * This uses some math tricks to figure out the offset in days 0208 * to the next date the given day of the week occurs. There 0209 * are two cases, that the new day is >= the current day, which means 0210 * the new day has not occurred yet or that the new day < the current day, 0211 * which means the new day is already passed (so we need to find the 0212 * day in the next week). 0213 */ 0214 i -= 100; 0215 int currentDay = today.dayOfWeek(); 0216 if (i >= currentDay) { 0217 i -= currentDay; 0218 } else { 0219 i += 7 - currentDay; 0220 } 0221 } 0222 0223 result = today.addDays(i); 0224 if (replaced != nullptr) { 0225 (*replaced) = true; 0226 } 0227 } else { 0228 result = QLocale().toDate(text, mAlternativeDateFormatToUse); 0229 } 0230 0231 return result; 0232 } 0233 0234 void KDateEdit::focusOutEvent(QFocusEvent* e) 0235 { 0236 if (mTextChanged) { 0237 lineEnterPressed(); 0238 mTextChanged = false; 0239 } 0240 QComboBox::focusOutEvent(e); 0241 } 0242 0243 void KDateEdit::keyPressEvent(QKeyEvent* e) 0244 { 0245 QDate date2; 0246 0247 if (!mReadOnly) { 0248 switch (e->key()) { 0249 case Qt::Key_Up: 0250 date2 = parseDate(); 0251 if (!date2.isValid()) { 0252 break; 0253 } 0254 if ((e->modifiers() & Qt::ControlModifier) != 0u) { // Warning OSX: The KeypadModifier value will also be set when an arrow key is pressed as the arrow keys are considered part of the keypad. 0255 date2 = date2.addMonths(1); 0256 } else { 0257 date2 = date2.addDays(1); 0258 } 0259 break; 0260 case Qt::Key_Down: 0261 date2 = parseDate(); 0262 if (!date2.isValid()) { 0263 break; 0264 } 0265 if ((e->modifiers() & Qt::ControlModifier) != 0u) { // Warning OSX: The KeypadModifier value will also be set when an arrow key is pressed as the arrow keys are considered part of the keypad. 0266 date2 = date2.addMonths(-1); 0267 } else { 0268 date2 = date2.addDays(-1); 0269 } 0270 break; 0271 case Qt::Key_PageUp: 0272 date2 = parseDate(); 0273 if (!date2.isValid()) { 0274 break; 0275 } 0276 date2 = date2.addMonths(1); 0277 break; 0278 case Qt::Key_PageDown: 0279 date2 = parseDate(); 0280 if (!date2.isValid()) { 0281 break; 0282 } 0283 date2 = date2.addMonths(-1); 0284 break; 0285 case Qt::Key_Equal: 0286 date2 = QDate::currentDate(); 0287 break; 0288 default: {} 0289 } 0290 0291 if (date2.isValid() && assignDate(date2)) { 0292 e->accept(); 0293 updateView(); 0294 emit dateChanged(date2); 0295 emit dateEntered(date2); 0296 return; 0297 } 0298 } 0299 0300 QComboBox::keyPressEvent(e); 0301 } 0302 0303 bool KDateEdit::eventFilter(QObject* iObject, QEvent* iEvent) 0304 { 0305 if (iObject == lineEdit()) { 0306 // We only process the focus out event if the text has changed 0307 // since we got focus 0308 if ((iEvent->type() == QEvent::FocusOut) && mTextChanged) { 0309 lineEnterPressed(); 0310 mTextChanged = false; 0311 } else if (iEvent->type() == QEvent::KeyPress) { 0312 // Up and down arrow keys step the date 0313 auto* keyEvent = dynamic_cast<QKeyEvent*>(iEvent); 0314 if (keyEvent && (keyEvent && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter))) { 0315 lineEnterPressed(); 0316 return true; 0317 } 0318 } 0319 } 0320 0321 return QComboBox::eventFilter(iObject, iEvent); 0322 } 0323 0324 void KDateEdit::slotTextChanged(const QString& /*unused*/) 0325 { 0326 QDate date2 = parseDate(); 0327 0328 if (assignDate(date2)) { 0329 emit dateChanged(date2); 0330 } 0331 0332 mTextChanged = true; 0333 } 0334 0335 void KDateEdit::setupKeywords() 0336 { 0337 // Create the keyword list. This will be used to match against when the user 0338 // enters information. 0339 mKeywordMap.insert(i18nc("the day after today", "tomorrow"), 1); 0340 mKeywordMap.insert(i18nc("this day", "today"), 0); 0341 mKeywordMap.insert(i18nc("the day before today", "yesterday"), -1); 0342 mKeywordMap.insert(i18nc("the week after this week", "next week"), 7); 0343 mKeywordMap.insert(i18nc("the month after this month", "next month"), 30); 0344 0345 QString dayName; 0346 for (int i = 1; i <= 7; ++i) { 0347 dayName = QLocale().dayName(i).toLower(); 0348 mKeywordMap.insert(dayName, i + 100); 0349 } 0350 0351 auto comp = new QCompleter(mKeywordMap.keys(), this); 0352 comp->setCaseSensitivity(Qt::CaseInsensitive); 0353 comp->setCompletionMode(QCompleter::InlineCompletion); 0354 setCompleter(comp); 0355 } 0356 0357 bool KDateEdit::assignDate(QDate iDate) 0358 { 0359 mDate = iDate; 0360 mTextChanged = false; 0361 return true; 0362 } 0363 0364 void KDateEdit::updateView() 0365 { 0366 QString dateString; 0367 if (mDate.isValid()) { 0368 dateString = QLocale().toString(mDate, mAlternativeDateFormatToUse); 0369 } 0370 0371 // We do not want to generate a signal here, 0372 // since we explicitly setting the date 0373 bool blocked = signalsBlocked(); 0374 blockSignals(true); 0375 removeItem(0); 0376 insertItem(0, dateString); 0377 blockSignals(blocked); 0378 } 0379 0380