"Fossies" - the Fresh Open Source Software Archive

Member "digikam-6.3.0/core/app/date/ddateedit.cpp" (4 Sep 2019, 12259 Bytes) of package /linux/misc/digikam-6.3.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ddateedit.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.2.0_vs_6.3.0.

    1 /* ============================================================
    2  *
    3  * This file is a part of digiKam project
    4  * https://www.digikam.org
    5  *
    6  * Date        : 2002-01-10
    7  * Description : a combo box to list date.
    8  *               this widget come from libkdepim.
    9  *
   10  * Copyright (C) 2011-2019 by Gilles Caulier <caulier dot gilles at gmail dot com>
   11  * Copyright (C) 2002      by Cornelius Schumacher <schumacher at kde dot org>
   12  * Copyright (C) 2003-2004 by Reinhold Kainhofer <reinhold at kainhofer dot com>
   13  * Copyright (C) 2004      by Tobias Koenig <tokoe at kde dot org>
   14  *
   15  * This program is free software; you can redistribute it
   16  * and/or modify it under the terms of the GNU General
   17  * Public License as published by the Free Software Foundation;
   18  * either version 2, or (at your option)
   19  * any later version.
   20  *
   21  * This program is distributed in the hope that it will be useful,
   22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   24  * GNU General Public License for more details.
   25  *
   26  * ============================================================ */
   27 
   28 #include "ddateedit.h"
   29 
   30 // Qt includes
   31 
   32 #include <QAbstractItemView>
   33 #include <QApplication>
   34 #include <QEvent>
   35 #include <QKeyEvent>
   36 #include <QLineEdit>
   37 #include <QMouseEvent>
   38 #include <QValidator>
   39 #include <QWindow>
   40 #include <QScreen>
   41 #include <QLocale>
   42 
   43 // KDE includes
   44 
   45 #include <klocalizedstring.h>
   46 
   47 // Local includes
   48 
   49 #include "ddatepickerpopup.h"
   50 
   51 namespace Digikam
   52 {
   53 
   54 class Q_DECL_HIDDEN DateValidator : public QValidator
   55 {
   56 public:
   57 
   58     DateValidator(const QStringList& keywords, const QString& dateFormat, QWidget* const parent)
   59         : QValidator(parent),
   60           mKeywords(keywords),
   61           mDateFormat(dateFormat)
   62     {
   63     }
   64 
   65     virtual State validate(QString& str, int&) const
   66     {
   67         int length = str.length();
   68 
   69         // empty string is intermediate so one can clear the edit line and start from scratch
   70         if (length <= 0)
   71         {
   72             return Intermediate;
   73         }
   74 
   75         if (mKeywords.contains(str.toLower()))
   76         {
   77             return Acceptable;
   78         }
   79 
   80         bool ok = QDate::fromString(str, mDateFormat).isValid();
   81 
   82         if (ok)
   83         {
   84             return Acceptable;
   85         }
   86         else
   87         {
   88             return Intermediate;
   89         }
   90     }
   91 
   92 private:
   93 
   94     QStringList mKeywords;
   95     QString     mDateFormat;
   96 };
   97 
   98 // -----------------------------------------------------------------------------------
   99 
  100 class Q_DECL_HIDDEN DDateEdit::Private
  101 {
  102 public:
  103 
  104     explicit Private()
  105       : readOnly(false),
  106         textChanged(false),
  107         discardNextMousePress(false),
  108         popup(nullptr)
  109     {
  110     }
  111 
  112     bool                readOnly;
  113     bool                textChanged;
  114     bool                discardNextMousePress;
  115 
  116     QDate               date;
  117     QString             dateFormat;
  118 
  119     QMap<QString, int>  keywordMap;
  120 
  121     DDatePickerPopup*   popup;
  122 };
  123 
  124 DDateEdit::DDateEdit(QWidget* const parent, const QString& name)
  125     : QComboBox(parent),
  126       d(new Private)
  127 {
  128     setObjectName(name);
  129     // need at least one entry for popup to work
  130     setMaxCount(1);
  131     setEditable(true);
  132 
  133     d->date       = QDate::currentDate();
  134 
  135     d->dateFormat = QLocale().dateFormat(QLocale::ShortFormat);
  136 
  137     if (!d->dateFormat.contains(QLatin1String("yyyy")))
  138     {
  139         d->dateFormat.replace(QLatin1String("yy"),
  140                               QLatin1String("yyyy"));
  141     }
  142 
  143     QString today = d->date.toString(d->dateFormat);
  144 
  145     addItem(today);
  146     setCurrentIndex(0);
  147     setMinimumSize(sizeHint());
  148     setMinimumSize(minimumSizeHint());
  149 
  150     connect(lineEdit(), SIGNAL(returnPressed()),
  151             this, SLOT(lineEnterPressed()));
  152 
  153     connect(this, SIGNAL(currentTextChanged(QString)),
  154             SLOT(slotTextChanged(QString)));
  155 
  156     d->popup = new DDatePickerPopup(DDatePickerPopup::DatePicker | DDatePickerPopup::Words);
  157     d->popup->hide();
  158     d->popup->installEventFilter(this);
  159 
  160     connect(d->popup, SIGNAL(dateChanged(QDate)),
  161             this, SLOT(dateSelected(QDate)));
  162 
  163     // handle keyword entry
  164     setupKeywords();
  165     lineEdit()->installEventFilter(this);
  166 
  167     setValidator(new DateValidator(d->keywordMap.keys(), d->dateFormat, this));
  168 
  169     d->textChanged = false;
  170 }
  171 
  172 DDateEdit::~DDateEdit()
  173 {
  174     delete d->popup;
  175     d->popup = nullptr;
  176     delete d;
  177 }
  178 
  179 void DDateEdit::setDate(const QDate& date)
  180 {
  181     assignDate(date);
  182     updateView();
  183 }
  184 
  185 QDate DDateEdit::date() const
  186 {
  187     return d->date;
  188 }
  189 
  190 void DDateEdit::setReadOnly(bool readOnly)
  191 {
  192     d->readOnly = readOnly;
  193     lineEdit()->setReadOnly(readOnly);
  194 }
  195 
  196 bool DDateEdit::isReadOnly() const
  197 {
  198     return d->readOnly;
  199 }
  200 
  201 void DDateEdit::showPopup()
  202 {
  203     if (d->readOnly)
  204     {
  205         return;
  206     }
  207 
  208     QScreen* screen = qApp->primaryScreen();
  209 
  210     if (QWidget* const widget = nativeParentWidget())
  211     {
  212         if (QWindow* const window = widget->windowHandle())
  213             screen = window->screen();
  214     }
  215 
  216     QRect desk          = screen->geometry();
  217     QPoint popupPoint   = mapToGlobal(QPoint(0, 0));
  218     int dateFrameHeight = d->popup->sizeHint().height();
  219 
  220     if (popupPoint.y() + height() + dateFrameHeight > desk.bottom())
  221     {
  222         popupPoint.setY(popupPoint.y() - dateFrameHeight);
  223     }
  224     else
  225     {
  226         popupPoint.setY(popupPoint.y() + height());
  227     }
  228 
  229     int dateFrameWidth = d->popup->sizeHint().width();
  230 
  231     if (popupPoint.x() + dateFrameWidth > desk.right())
  232     {
  233         popupPoint.setX(desk.right() - dateFrameWidth);
  234     }
  235 
  236     if (popupPoint.x() < desk.left())
  237     {
  238         popupPoint.setX(desk.left());
  239     }
  240 
  241     if (popupPoint.y() < desk.top())
  242     {
  243         popupPoint.setY(desk.top());
  244     }
  245 
  246     if (d->date.isValid())
  247     {
  248         d->popup->setDate(d->date);
  249     }
  250     else
  251     {
  252         d->popup->setDate(QDate::currentDate());
  253     }
  254 
  255     d->popup->popup(popupPoint);
  256 
  257     // The combo box is now shown pressed. Make it show not pressed again
  258     // by causing its (invisible) list box to emit a 'selected' signal.
  259     // First, ensure that the list box contains the date currently displayed.
  260     QDate date                  = parseDate();
  261     assignDate(date);
  262     updateView();
  263     // Now, simulate an Enter to unpress it
  264     QAbstractItemView* const lb = view();
  265 
  266     if (lb)
  267     {
  268         lb->setCurrentIndex(lb->model()->index(0, 0));
  269         QKeyEvent* const keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
  270         QApplication::postEvent(lb, keyEvent);
  271     }
  272 }
  273 
  274 void DDateEdit::dateSelected(const QDate& date)
  275 {
  276     if (assignDate(date))
  277     {
  278         updateView();
  279         emit dateChanged(date);
  280 
  281         if (date.isValid())
  282         {
  283             d->popup->hide();
  284         }
  285     }
  286 }
  287 
  288 void DDateEdit::dateEntered(const QDate& date)
  289 {
  290     if (assignDate(date))
  291     {
  292         updateView();
  293         emit dateChanged(date);
  294     }
  295 }
  296 
  297 void DDateEdit::lineEnterPressed()
  298 {
  299     bool replaced = false;
  300 
  301     QDate date = parseDate(&replaced);
  302 
  303     if (assignDate(date))
  304     {
  305         if (replaced)
  306         {
  307             updateView();
  308         }
  309 
  310         emit dateChanged(date);
  311     }
  312 }
  313 
  314 QDate DDateEdit::parseDate(bool* replaced) const
  315 {
  316     QString text = currentText();
  317     QDate   result;
  318 
  319     if (replaced)
  320     {
  321         (*replaced) = false;
  322     }
  323 
  324     if (text.isEmpty())
  325     {
  326         result = QDate();
  327     }
  328     else if (d->keywordMap.contains(text.toLower()))
  329     {
  330         QDate today = QDate::currentDate();
  331         int i       = d->keywordMap[text.toLower()];
  332 
  333         if (i >= 100)
  334         {
  335             /* A day name has been entered. Convert to offset from today.
  336             * This uses some math tricks to figure out the offset in days
  337             * to the next date the given day of the week occurs. There
  338             * are two cases, that the new day is >= the current day, which means
  339             * the new day has not occurred yet or that the new day < the current day,
  340             * which means the new day is already passed (so we need to find the
  341             * day in the next week).
  342             */
  343             i -= 100;
  344             int currentDay = today.dayOfWeek();
  345 
  346             if (i >= currentDay)
  347             {
  348                 i -= currentDay;
  349             }
  350             else
  351             {
  352                 i += 7 - currentDay;
  353             }
  354         }
  355 
  356         result = today.addDays(i);
  357 
  358         if (replaced)
  359         {
  360             (*replaced) = true;
  361         }
  362     }
  363     else
  364     {
  365         result = QDate::fromString(text, d->dateFormat);
  366     }
  367 
  368     return result;
  369 }
  370 
  371 bool DDateEdit::eventFilter(QObject* object, QEvent* event)
  372 {
  373     if (object == lineEdit())
  374     {
  375         // We only process the focus out event if the text has changed
  376         // since we got focus
  377         if ((event->type() == QEvent::FocusOut) && d->textChanged)
  378         {
  379             lineEnterPressed();
  380             d->textChanged = false;
  381         }
  382         else if (event->type() == QEvent::KeyPress)
  383         {
  384             // Up and down arrow keys step the date
  385             QKeyEvent* const keyEvent = (QKeyEvent*)event;
  386 
  387             if (keyEvent->key() == Qt::Key_Return)
  388             {
  389                 lineEnterPressed();
  390                 return true;
  391             }
  392 
  393             int step = 0;
  394 
  395             if (keyEvent->key() == Qt::Key_Up)
  396             {
  397                 step = 1;
  398             }
  399             else if (keyEvent->key() == Qt::Key_Down)
  400             {
  401                 step = -1;
  402             }
  403 
  404             if (step && !d->readOnly)
  405             {
  406                 QDate date = parseDate();
  407 
  408                 if (date.isValid())
  409                 {
  410                     date = date.addDays(step);
  411 
  412                     if (assignDate(date))
  413                     {
  414                         updateView();
  415                         emit dateChanged(date);
  416                         return true;
  417                     }
  418                 }
  419             }
  420         }
  421     }
  422     else
  423     {
  424         // It's a date picker event
  425         switch (event->type())
  426         {
  427             case QEvent::MouseButtonDblClick:
  428             case QEvent::MouseButtonPress:
  429             {
  430                 QMouseEvent* const mouseEvent = (QMouseEvent*)event;
  431 
  432                 if (!d->popup->rect().contains(mouseEvent->pos()))
  433                 {
  434                     QPoint globalPos = d->popup->mapToGlobal(mouseEvent->pos());
  435 
  436                     if (QApplication::widgetAt(globalPos) == this)
  437                     {
  438                         // The date picker is being closed by a click on the
  439                         // DDateEdit widget. Avoid popping it up again immediately.
  440                         d->discardNextMousePress = true;
  441                     }
  442                 }
  443 
  444                 break;
  445             }
  446             default:
  447                 break;
  448         }
  449     }
  450 
  451     return false;
  452 }
  453 
  454 void DDateEdit::mousePressEvent(QMouseEvent* e)
  455 {
  456     if (e->button() == Qt::LeftButton && d->discardNextMousePress)
  457     {
  458         d->discardNextMousePress = false;
  459         return;
  460     }
  461 
  462     QComboBox::mousePressEvent(e);
  463 }
  464 
  465 void DDateEdit::slotTextChanged(const QString&)
  466 {
  467     QDate date = parseDate();
  468 
  469     if (assignDate(date))
  470     {
  471         emit dateChanged(date);
  472     }
  473 
  474     d->textChanged = true;
  475 }
  476 
  477 void DDateEdit::setupKeywords()
  478 {
  479     // Create the keyword list. This will be used to match against when the user
  480     // enters information.
  481     d->keywordMap.insert(i18n("tomorrow"),   1);
  482     d->keywordMap.insert(i18n("today"),      0);
  483     d->keywordMap.insert(i18n("yesterday"), -1);
  484 
  485     QString dayName;
  486 
  487     for (int i = 1; i <= 7; ++i)
  488     {
  489         dayName = QLocale().standaloneDayName(i, QLocale::LongFormat).toLower();
  490         d->keywordMap.insert(dayName, i + 100);
  491     }
  492 }
  493 
  494 bool DDateEdit::assignDate(const QDate& date)
  495 {
  496     d->date        = date;
  497     d->textChanged = false;
  498     return true;
  499 }
  500 
  501 void DDateEdit::updateView()
  502 {
  503     QString dateString;
  504 
  505     if (d->date.isValid())
  506     {
  507         dateString = d->date.toString(d->dateFormat);
  508     }
  509 
  510     // We do not want to generate a signal here,
  511     // since we explicitly setting the date
  512     bool blocked = signalsBlocked();
  513     blockSignals(true);
  514     removeItem(0);
  515     insertItem(0, dateString);
  516     blockSignals(blocked);
  517 }
  518 
  519 } // namespace Digikam