1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcalendarwidget.h"
41
42#include <qabstractitemmodel.h>
43#include <qitemdelegate.h>
44#include <qdatetime.h>
45#include <qtableview.h>
46#include <qlayout.h>
47#include <qevent.h>
48#include <qtextformat.h>
49#include <qheaderview.h>
50#include <private/qwidget_p.h>
51#include <qpushbutton.h>
52#include <qtoolbutton.h>
53#include <qlabel.h>
54#include <qspinbox.h>
55#include <qmenu.h>
56#include <qapplication.h>
57#include <private/qapplication_p.h>
58#include <qbasictimer.h>
59#include <qstylepainter.h>
60#include <qcalendar.h>
61
62#include <vector>
63
64QT_BEGIN_NAMESPACE
65
66enum {
67 RowCount = 6,
68 ColumnCount = 7,
69 HeaderColumn = 0,
70 HeaderRow = 0,
71 MinimumDayOffset = 1
72};
73
74namespace {
75
76static QString formatNumber(int number, int fieldWidth)
77{
78 return QString::number(number).rightJustified(fieldWidth, QLatin1Char('0'));
79}
80
81class QCalendarDateSectionValidator
82{
83public:
84
85 enum Section {
86 NextSection,
87 ThisSection,
88 PrevSection
89 };
90
91 QCalendarDateSectionValidator() {}
92 virtual ~QCalendarDateSectionValidator() {}
93 virtual Section handleKey(int key) = 0;
94 virtual QDate applyToDate(QDate date, QCalendar cal = QCalendar()) const = 0;
95 virtual void setDate(QDate date, QCalendar cal = QCalendar()) = 0;
96 virtual QString text() const = 0;
97 virtual QString text(QDate date, QCalendar cal, int repeat) const = 0;
98
99 QLocale m_locale;
100
101protected:
102 static QString highlightString(const QString &str, int pos);
103};
104
105QString QCalendarDateSectionValidator::highlightString(const QString &str, int pos)
106{
107 if (pos == 0)
108 return QLatin1String("<b>") + str + QLatin1String("</b>");
109 int startPos = str.length() - pos;
110 return str.midRef(0, startPos) + QLatin1String("<b>") + str.midRef(startPos, pos) + QLatin1String("</b>");
111
112}
113
114class QCalendarDayValidator : public QCalendarDateSectionValidator
115{
116
117public:
118 QCalendarDayValidator();
119 virtual Section handleKey(int key) override;
120 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
121 virtual void setDate(QDate date, QCalendar cal) override;
122 virtual QString text() const override;
123 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
124private:
125 int m_pos;
126 int m_day;
127 int m_oldDay;
128};
129
130QCalendarDayValidator::QCalendarDayValidator()
131 : QCalendarDateSectionValidator(), m_pos(0), m_day(1), m_oldDay(1)
132{
133}
134
135QCalendarDateSectionValidator::Section QCalendarDayValidator::handleKey(int key)
136{
137 if (key == Qt::Key_Right || key == Qt::Key_Left) {
138 m_pos = 0;
139 return QCalendarDateSectionValidator::ThisSection;
140 } else if (key == Qt::Key_Up) {
141 m_pos = 0;
142 ++m_day;
143 if (m_day > 31)
144 m_day = 1;
145 return QCalendarDateSectionValidator::ThisSection;
146 } else if (key == Qt::Key_Down) {
147 m_pos = 0;
148 --m_day;
149 if (m_day < 1)
150 m_day = 31;
151 return QCalendarDateSectionValidator::ThisSection;
152 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
153 --m_pos;
154 if (m_pos < 0)
155 m_pos = 1;
156
157 if (m_pos == 0)
158 m_day = m_oldDay;
159 else
160 m_day = m_day / 10;
161 //m_day = m_oldDay / 10 * 10 + m_day / 10;
162
163 if (m_pos == 0)
164 return QCalendarDateSectionValidator::PrevSection;
165 return QCalendarDateSectionValidator::ThisSection;
166 }
167 if (key < Qt::Key_0 || key > Qt::Key_9)
168 return QCalendarDateSectionValidator::ThisSection;
169 int pressedKey = key - Qt::Key_0;
170 if (m_pos == 0)
171 m_day = pressedKey;
172 else
173 m_day = m_day % 10 * 10 + pressedKey;
174 if (m_day > 31)
175 m_day = 31;
176 ++m_pos;
177 if (m_pos > 1) {
178 m_pos = 0;
179 return QCalendarDateSectionValidator::NextSection;
180 }
181 return QCalendarDateSectionValidator::ThisSection;
182}
183
184QDate QCalendarDayValidator::applyToDate(QDate date, QCalendar cal) const
185{
186 auto parts = cal.partsFromDate(date);
187 if (!parts.isValid())
188 return QDate();
189 parts.day = qMin(qMax(1, m_day), cal.daysInMonth(parts.month, parts.year));
190 return cal.dateFromParts(parts);
191}
192
193void QCalendarDayValidator::setDate(QDate date, QCalendar cal)
194{
195 m_day = m_oldDay = date.day(cal);
196 m_pos = 0;
197}
198
199QString QCalendarDayValidator::text() const
200{
201 return highlightString(formatNumber(m_day, 2), m_pos);
202}
203
204QString QCalendarDayValidator::text(QDate date, QCalendar cal, int repeat) const
205{
206 if (repeat <= 1) {
207 return QString::number(date.day(cal));
208 } else if (repeat == 2) {
209 return formatNumber(date.day(cal), 2);
210 } else if (repeat == 3) {
211 return m_locale.dayName(date.dayOfWeek(cal), QLocale::ShortFormat);
212 } else /* repeat >= 4 */ {
213 return m_locale.dayName(date.dayOfWeek(cal), QLocale::LongFormat);
214 }
215}
216
217//////////////////////////////////
218
219class QCalendarMonthValidator : public QCalendarDateSectionValidator
220{
221
222public:
223 QCalendarMonthValidator();
224 virtual Section handleKey(int key) override;
225 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
226 virtual void setDate(QDate date, QCalendar cal) override;
227 virtual QString text() const override;
228 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
229private:
230 int m_pos;
231 int m_month;
232 int m_oldMonth;
233};
234
235QCalendarMonthValidator::QCalendarMonthValidator()
236 : QCalendarDateSectionValidator(), m_pos(0), m_month(1), m_oldMonth(1)
237{
238}
239
240QCalendarDateSectionValidator::Section QCalendarMonthValidator::handleKey(int key)
241{
242 if (key == Qt::Key_Right || key == Qt::Key_Left) {
243 m_pos = 0;
244 return QCalendarDateSectionValidator::ThisSection;
245 } else if (key == Qt::Key_Up) {
246 m_pos = 0;
247 ++m_month;
248 if (m_month > 12)
249 m_month = 1;
250 return QCalendarDateSectionValidator::ThisSection;
251 } else if (key == Qt::Key_Down) {
252 m_pos = 0;
253 --m_month;
254 if (m_month < 1)
255 m_month = 12;
256 return QCalendarDateSectionValidator::ThisSection;
257 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
258 --m_pos;
259 if (m_pos < 0)
260 m_pos = 1;
261
262 if (m_pos == 0)
263 m_month = m_oldMonth;
264 else
265 m_month = m_month / 10;
266 //m_month = m_oldMonth / 10 * 10 + m_month / 10;
267
268 if (m_pos == 0)
269 return QCalendarDateSectionValidator::PrevSection;
270 return QCalendarDateSectionValidator::ThisSection;
271 }
272 if (key < Qt::Key_0 || key > Qt::Key_9)
273 return QCalendarDateSectionValidator::ThisSection;
274 int pressedKey = key - Qt::Key_0;
275 if (m_pos == 0)
276 m_month = pressedKey;
277 else
278 m_month = m_month % 10 * 10 + pressedKey;
279 if (m_month > 12)
280 m_month = 12;
281 ++m_pos;
282 if (m_pos > 1) {
283 m_pos = 0;
284 return QCalendarDateSectionValidator::NextSection;
285 }
286 return QCalendarDateSectionValidator::ThisSection;
287}
288
289QDate QCalendarMonthValidator::applyToDate(QDate date, QCalendar cal) const
290{
291 auto parts = cal.partsFromDate(date);
292 if (!parts.isValid())
293 return QDate();
294 parts.month = qMin(qMax(1, m_month), cal.monthsInYear(parts.year));
295 parts.day = qMin(parts.day, cal.daysInMonth(m_month, parts.year)); // m_month or parts.month ?
296 return cal.dateFromParts(parts);
297}
298
299void QCalendarMonthValidator::setDate(QDate date, QCalendar cal)
300{
301 m_month = m_oldMonth = date.month(cal);
302 m_pos = 0;
303}
304
305QString QCalendarMonthValidator::text() const
306{
307 return highlightString(formatNumber(m_month, 2), m_pos);
308}
309
310QString QCalendarMonthValidator::text(QDate date, QCalendar cal, int repeat) const
311{
312 const auto parts = cal.partsFromDate(date);
313 // Numeric forms:
314 if (repeat <= 1)
315 return QString::number(parts.month);
316 if (repeat == 2)
317 return formatNumber(parts.month, 2);
318 // Text forms:
319 if (repeat == 3)
320 return cal.standaloneMonthName(m_locale, parts.month, parts.year, QLocale::ShortFormat);
321 /* repeat >= 4 */
322 return cal.standaloneMonthName(m_locale, parts.month, parts.year, QLocale::LongFormat);
323}
324
325//////////////////////////////////
326
327class QCalendarYearValidator : public QCalendarDateSectionValidator
328{
329
330public:
331 QCalendarYearValidator();
332 virtual Section handleKey(int key) override;
333 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
334 virtual void setDate(QDate date, QCalendar cal) override;
335 virtual QString text() const override;
336 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
337private:
338 int pow10(int n);
339 int m_pos;
340 int m_year;
341 int m_oldYear;
342};
343
344QCalendarYearValidator::QCalendarYearValidator()
345 : QCalendarDateSectionValidator(), m_pos(0), m_year(2000), m_oldYear(2000)
346{
347 // TODO: What to use (for non-Gregorian calendars) as default year?
348 // Maybe 1360 for Jalali, 1420 for Islamic, etc.
349}
350
351int QCalendarYearValidator::pow10(int n)
352{
353 int power = 1;
354 for (int i = 0; i < n; i++)
355 power *= 10;
356 return power;
357}
358
359QCalendarDateSectionValidator::Section QCalendarYearValidator::handleKey(int key)
360{
361 if (key == Qt::Key_Right || key == Qt::Key_Left) {
362 m_pos = 0;
363 return QCalendarDateSectionValidator::ThisSection;
364 } else if (key == Qt::Key_Up) {
365 m_pos = 0;
366 ++m_year;
367 return QCalendarDateSectionValidator::ThisSection;
368 } else if (key == Qt::Key_Down) {
369 m_pos = 0;
370 --m_year;
371 return QCalendarDateSectionValidator::ThisSection;
372 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
373 --m_pos;
374 if (m_pos < 0)
375 m_pos = 3;
376
377 int pow = pow10(m_pos);
378 m_year = m_oldYear / pow * pow + m_year % (pow * 10) / 10;
379
380 if (m_pos == 0)
381 return QCalendarDateSectionValidator::PrevSection;
382 return QCalendarDateSectionValidator::ThisSection;
383 }
384 if (key < Qt::Key_0 || key > Qt::Key_9)
385 return QCalendarDateSectionValidator::ThisSection;
386 int pressedKey = key - Qt::Key_0;
387 int pow = pow10(m_pos);
388 m_year = m_year / (pow * 10) * (pow * 10) + m_year % pow * 10 + pressedKey;
389 ++m_pos;
390 if (m_pos > 3) {
391 m_pos = 0;
392 return QCalendarDateSectionValidator::NextSection;
393 }
394 return QCalendarDateSectionValidator::ThisSection;
395}
396
397QDate QCalendarYearValidator::applyToDate(QDate date, QCalendar cal) const
398{
399 auto parts = cal.partsFromDate(date);
400 if (!parts.isValid())
401 return QDate();
402 // This widget does not support negative years (some calendars may support)
403 parts.year = qMax(1, m_year);
404 parts.day = qMin(parts.day, cal.daysInMonth(parts.month, parts.year));
405 return cal.dateFromParts(parts);
406}
407
408void QCalendarYearValidator::setDate(QDate date, QCalendar cal)
409{
410 m_year = m_oldYear = date.year(cal);
411 m_pos = 0;
412}
413
414QString QCalendarYearValidator::text() const
415{
416 return highlightString(formatNumber(m_year, 4), m_pos);
417}
418
419QString QCalendarYearValidator::text(QDate date, QCalendar cal, int repeat) const
420{
421 if (repeat < 4)
422 return formatNumber(date.year(cal) % 100, 2);
423 return QString::number(date.year(cal));
424}
425
426///////////////////////////////////
427
428struct SectionToken {
429 Q_DECL_CONSTEXPR SectionToken(QCalendarDateSectionValidator *v, int rep)
430 : validator(v), repeat(rep) {}
431
432 QCalendarDateSectionValidator *validator;
433 int repeat;
434};
435} // unnamed namespace
436Q_DECLARE_TYPEINFO(SectionToken, Q_PRIMITIVE_TYPE);
437namespace {
438
439class QCalendarDateValidator
440{
441public:
442 QCalendarDateValidator();
443 ~QCalendarDateValidator();
444
445 void handleKeyEvent(QKeyEvent *keyEvent, QCalendar cal);
446 QString currentText(QCalendar cal) const;
447 QDate currentDate() const { return m_currentDate; }
448 void setFormat(const QString &format);
449 void setInitialDate(QDate date, QCalendar cal);
450
451 void setLocale(const QLocale &locale);
452
453private:
454 void toNextToken();
455 void toPreviousToken();
456 void applyToDate(QCalendar cal);
457
458 int countRepeat(const QString &str, int index) const;
459 void clear();
460
461 QStringList m_separators;
462 std::vector<SectionToken> m_tokens;
463 QCalendarYearValidator m_yearValidator;
464 QCalendarMonthValidator m_monthValidator;
465 QCalendarDayValidator m_dayValidator;
466
467 int m_currentToken;
468
469 QDate m_initialDate;
470 QDate m_currentDate;
471
472 QCalendarDateSectionValidator::Section m_lastSectionMove;
473};
474
475QCalendarDateValidator::QCalendarDateValidator()
476 : m_currentToken(-1),
477 m_initialDate(QDate::currentDate()),
478 m_currentDate(m_initialDate),
479 m_lastSectionMove(QCalendarDateSectionValidator::ThisSection)
480{
481}
482
483void QCalendarDateValidator::setLocale(const QLocale &locale)
484{
485 m_yearValidator.m_locale = locale;
486 m_monthValidator.m_locale = locale;
487 m_dayValidator.m_locale = locale;
488}
489
490QCalendarDateValidator::~QCalendarDateValidator()
491{
492 clear();
493}
494
495// from qdatetime.cpp
496int QCalendarDateValidator::countRepeat(const QString &str, int index) const
497{
498 Q_ASSERT(index >= 0 && index < str.size());
499 int count = 1;
500 const QChar ch = str.at(index);
501 while (index + count < str.size() && str.at(index + count) == ch)
502 ++count;
503 return count;
504}
505
506void QCalendarDateValidator::setInitialDate(QDate date, QCalendar cal)
507{
508 m_yearValidator.setDate(date, cal);
509 m_monthValidator.setDate(date, cal);
510 m_dayValidator.setDate(date, cal);
511 m_initialDate = date;
512 m_currentDate = date;
513 m_lastSectionMove = QCalendarDateSectionValidator::ThisSection;
514}
515
516QString QCalendarDateValidator::currentText(QCalendar cal) const
517{
518 QString str;
519 const int numSeps = m_separators.size();
520 const int numTokens = int(m_tokens.size());
521 for (int i = 0; i < numSeps; ++i) {
522 str += m_separators.at(i);
523 if (i < numTokens) {
524 const SectionToken &token = m_tokens[i];
525 if (i == m_currentToken)
526 str += token.validator->text();
527 else
528 str += token.validator->text(m_currentDate, cal, token.repeat);
529 }
530 }
531 return str;
532}
533
534void QCalendarDateValidator::clear()
535{
536 m_tokens.clear();
537 m_separators.clear();
538
539 m_currentToken = -1;
540}
541
542void QCalendarDateValidator::setFormat(const QString &format)
543{
544 clear();
545
546 int pos = 0;
547 const QLatin1Char quote('\'');
548 bool quoting = false;
549 QString separator;
550 while (pos < format.size()) {
551 const QStringRef mid = format.midRef(pos);
552 int offset = 1;
553
554 if (mid.startsWith(quote)) {
555 quoting = !quoting;
556 } else {
557 const QChar nextChar = format.at(pos);
558 if (quoting) {
559 separator += nextChar;
560 quoting = false;
561 } else {
562 QCalendarDateSectionValidator *validator = nullptr;
563 if (nextChar == QLatin1Char('d')) {
564 offset = qMin(4, countRepeat(format, pos));
565 validator = &m_dayValidator;
566 } else if (nextChar == QLatin1Char('M')) {
567 offset = qMin(4, countRepeat(format, pos));
568 validator = &m_monthValidator;
569 } else if (nextChar == QLatin1Char('y')) {
570 offset = qMin(4, countRepeat(format, pos));
571 validator = &m_yearValidator;
572 } else {
573 separator += nextChar;
574 }
575 if (validator) {
576 m_tokens.push_back(SectionToken(validator, offset));
577 m_separators.append(separator);
578 separator = QString();
579 if (m_currentToken < 0)
580 m_currentToken = int(m_tokens.size()) - 1;
581
582 }
583 }
584 }
585 pos += offset;
586 }
587 m_separators += separator;
588}
589
590void QCalendarDateValidator::applyToDate(QCalendar cal)
591{
592 m_currentDate = m_yearValidator.applyToDate(m_currentDate, cal);
593 m_currentDate = m_monthValidator.applyToDate(m_currentDate, cal);
594 m_currentDate = m_dayValidator.applyToDate(m_currentDate, cal);
595}
596
597void QCalendarDateValidator::toNextToken()
598{
599 if (m_currentToken < 0)
600 return;
601 ++m_currentToken;
602 m_currentToken %= m_tokens.size();
603}
604
605void QCalendarDateValidator::toPreviousToken()
606{
607 if (m_currentToken < 0)
608 return;
609 --m_currentToken;
610 m_currentToken %= m_tokens.size();
611}
612
613void QCalendarDateValidator::handleKeyEvent(QKeyEvent *keyEvent,QCalendar cal)
614{
615 if (m_currentToken < 0)
616 return;
617
618 int key = keyEvent->key();
619 if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) {
620 if (key == Qt::Key_Back || key == Qt::Key_Backspace)
621 toPreviousToken();
622 }
623 if (key == Qt::Key_Right)
624 toNextToken();
625 else if (key == Qt::Key_Left)
626 toPreviousToken();
627
628 m_lastSectionMove = m_tokens[m_currentToken].validator->handleKey(key);
629
630 applyToDate(cal);
631 if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection)
632 toNextToken();
633 else if (m_lastSectionMove == QCalendarDateSectionValidator::PrevSection)
634 toPreviousToken();
635}
636
637//////////////////////////////////
638
639class QCalendarTextNavigator: public QObject
640{
641 Q_OBJECT
642public:
643 QCalendarTextNavigator(QObject *parent = nullptr)
644 : QObject(parent), m_dateText(nullptr), m_dateFrame(nullptr), m_dateValidator(nullptr),
645 m_widget(nullptr), m_editDelay(1500), m_date(QDate::currentDate()) {}
646
647 QWidget *widget() const;
648 void setWidget(QWidget *widget);
649
650 int dateEditAcceptDelay() const;
651 void setDateEditAcceptDelay(int delay);
652
653 void setDate(QDate date);
654
655 bool eventFilter(QObject *o, QEvent *e) override;
656 void timerEvent(QTimerEvent *e) override;
657
658signals:
659 void dateChanged(QDate date);
660 void editingFinished();
661
662private:
663 void applyDate();
664 void updateDateLabel();
665 void createDateLabel();
666 void removeDateLabel();
667
668 QLabel *m_dateText;
669 QFrame *m_dateFrame;
670 QBasicTimer m_acceptTimer;
671 QCalendarDateValidator *m_dateValidator;
672 QWidget *m_widget;
673 int m_editDelay;
674
675 QDate m_date;
676 const QCalendar m_calendar;
677};
678
679QWidget *QCalendarTextNavigator::widget() const
680{
681 return m_widget;
682}
683
684void QCalendarTextNavigator::setWidget(QWidget *widget)
685{
686 m_widget = widget;
687}
688
689void QCalendarTextNavigator::setDate(QDate date)
690{
691 m_date = date;
692}
693
694void QCalendarTextNavigator::updateDateLabel()
695{
696 if (!m_widget)
697 return;
698
699 m_acceptTimer.start(m_editDelay, this);
700
701 m_dateText->setText(m_dateValidator->currentText(m_calendar));
702
703 QSize s = m_dateFrame->sizeHint();
704 QRect r = m_widget->geometry(); // later, just the table section
705 QRect newRect((r.width() - s.width()) / 2, (r.height() - s.height()) / 2, s.width(), s.height());
706 m_dateFrame->setGeometry(newRect);
707 // need to set palette after geometry update as phonestyle sets transparency
708 // effect in move event.
709 QPalette p = m_dateFrame->palette();
710 p.setBrush(QPalette::Window, m_dateFrame->window()->palette().brush(QPalette::Window));
711 m_dateFrame->setPalette(p);
712
713 m_dateFrame->raise();
714 m_dateFrame->show();
715}
716
717void QCalendarTextNavigator::applyDate()
718{
719 QDate date = m_dateValidator->currentDate();
720 if (m_date == date)
721 return;
722
723 m_date = date;
724 emit dateChanged(date);
725}
726
727void QCalendarTextNavigator::createDateLabel()
728{
729 if (m_dateFrame)
730 return;
731 m_dateFrame = new QFrame(m_widget);
732 QVBoxLayout *vl = new QVBoxLayout;
733 m_dateText = new QLabel;
734 vl->addWidget(m_dateText);
735 m_dateFrame->setLayout(vl);
736 m_dateFrame->setFrameShadow(QFrame::Plain);
737 m_dateFrame->setFrameShape(QFrame::Box);
738 m_dateValidator = new QCalendarDateValidator();
739 m_dateValidator->setLocale(m_widget->locale());
740 m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat));
741 m_dateValidator->setInitialDate(m_date, m_calendar);
742
743 m_dateFrame->setAutoFillBackground(true);
744 m_dateFrame->setBackgroundRole(QPalette::Window);
745}
746
747void QCalendarTextNavigator::removeDateLabel()
748{
749 if (!m_dateFrame)
750 return;
751 m_acceptTimer.stop();
752 m_dateFrame->hide();
753 m_dateFrame->deleteLater();
754 delete m_dateValidator;
755 m_dateFrame = nullptr;
756 m_dateText = nullptr;
757 m_dateValidator = nullptr;
758}
759
760bool QCalendarTextNavigator::eventFilter(QObject *o, QEvent *e)
761{
762 if (m_widget) {
763 if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) {
764 QKeyEvent* ke = (QKeyEvent*)e;
765 if ((ke->text().length() > 0 && ke->text().at(0).isPrint()) || m_dateFrame) {
766 if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Select) {
767 applyDate();
768 emit editingFinished();
769 removeDateLabel();
770#if QT_CONFIG(shortcut)
771 } else if (ke->matches(QKeySequence::Cancel)) {
772 removeDateLabel();
773#endif
774 } else if (e->type() == QEvent::KeyPress) {
775 createDateLabel();
776 m_dateValidator->handleKeyEvent(ke, m_calendar);
777 updateDateLabel();
778 }
779 ke->accept();
780 return true;
781 }
782 // If we are navigating let the user finish his date in old locate.
783 // If we change our mind and want it to update immediately simply uncomment below
784 /*
785 } else if (e->type() == QEvent::LocaleChange) {
786 if (m_dateValidator) {
787 m_dateValidator->setLocale(m_widget->locale());
788 m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat));
789 updateDateLabel();
790 }
791 */
792 }
793 }
794 return QObject::eventFilter(o,e);
795}
796
797void QCalendarTextNavigator::timerEvent(QTimerEvent *e)
798{
799 if (e->timerId() == m_acceptTimer.timerId()) {
800 applyDate();
801 removeDateLabel();
802 }
803}
804
805int QCalendarTextNavigator::dateEditAcceptDelay() const
806{
807 return m_editDelay;
808}
809
810void QCalendarTextNavigator::setDateEditAcceptDelay(int delay)
811{
812 m_editDelay = delay;
813}
814
815class QCalendarView;
816
817// a small helper class that replaces a QMap<Qt::DayOfWeek, T>,
818// but requires T to have a member-swap and a default constructor
819// which should be cheap (no memory allocations)
820
821QT_WARNING_PUSH
822QT_WARNING_DISABLE_MSVC(4351) // "new behavior: elements of array ... will be default initialized"
823
824template <typename T>
825class StaticDayOfWeekAssociativeArray {
826 bool contained[7];
827 T data[7];
828
829 static Q_DECL_CONSTEXPR int day2idx(Qt::DayOfWeek day) noexcept { return int(day) - 1; } // alt: day % 7
830public:
831 Q_DECL_CONSTEXPR StaticDayOfWeekAssociativeArray() noexcept(noexcept(T()))
832#ifdef Q_COMPILER_CONSTEXPR
833 : contained{}, data{} // arrays require uniform initialization
834#else
835 : contained(), data()
836#endif
837 {}
838
839 Q_DECL_CONSTEXPR bool contains(Qt::DayOfWeek day) const noexcept { return contained[day2idx(day)]; }
840 Q_DECL_CONSTEXPR const T &value(Qt::DayOfWeek day) const noexcept { return data[day2idx(day)]; }
841
842 Q_DECL_RELAXED_CONSTEXPR T &operator[](Qt::DayOfWeek day) noexcept
843 {
844 const int idx = day2idx(day);
845 contained[idx] = true;
846 return data[idx];
847 }
848
849 Q_DECL_RELAXED_CONSTEXPR void insert(Qt::DayOfWeek day, T v) noexcept
850 {
851 operator[](day).swap(v);
852 }
853};
854
855QT_WARNING_POP
856
857class QCalendarModel : public QAbstractTableModel
858{
859 Q_OBJECT
860public:
861 QCalendarModel(QObject *parent = nullptr);
862
863 int rowCount(const QModelIndex &parent) const override
864 {
865 if (parent.isValid())
866 return 0;
867 return RowCount + m_firstRow;
868 }
869
870 int columnCount(const QModelIndex &parent) const override
871 {
872 if (parent.isValid())
873 return 0;
874 return ColumnCount + m_firstColumn;
875 }
876
877 QVariant data(const QModelIndex &index, int role) const override;
878 Qt::ItemFlags flags(const QModelIndex &index) const override;
879
880 void showMonth(int year, int month);
881 void setDate(QDate d);
882
883 void setCalendar(QCalendar c);
884 QCalendar calendar() const;
885
886 void setMinimumDate(QDate date);
887 void setMaximumDate(QDate date);
888
889 void setRange(QDate min, QDate max);
890
891 void setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format);
892
893 void setFirstColumnDay(Qt::DayOfWeek dayOfWeek);
894 Qt::DayOfWeek firstColumnDay() const;
895
896 bool weekNumbersShown() const;
897 void setWeekNumbersShown(bool show);
898
899 QTextCharFormat formatForCell(int row, int col) const;
900 Qt::DayOfWeek dayOfWeekForColumn(int section) const;
901 int columnForDayOfWeek(Qt::DayOfWeek day) const;
902 QDate dateForCell(int row, int column) const;
903 void cellForDate(QDate date, int *row, int *column) const;
904 QString dayName(Qt::DayOfWeek day) const;
905
906 void setView(QCalendarView *view)
907 { m_view = view; }
908
909 void internalUpdate();
910 QDate referenceDate() const;
911 int columnForFirstOfMonth(QDate date) const;
912
913 QString monthName(const QLocale &locale, int month)
914 {
915 return m_calendar.standaloneMonthName(locale, month, m_shownYear, QLocale::LongFormat);
916 }
917
918 int m_firstColumn;
919 int m_firstRow;
920 QCalendar m_calendar;
921 QDate m_date;
922 QDate m_minimumDate;
923 QDate m_maximumDate;
924 int m_shownYear;
925 int m_shownMonth;
926 Qt::DayOfWeek m_firstDay;
927 QCalendarWidget::HorizontalHeaderFormat m_horizontalHeaderFormat;
928 bool m_weekNumbersShown;
929 StaticDayOfWeekAssociativeArray<QTextCharFormat> m_dayFormats;
930 QMap<QDate, QTextCharFormat> m_dateFormats;
931 QTextCharFormat m_headerFormat;
932 QCalendarView *m_view;
933};
934
935class QCalendarView : public QTableView
936{
937 Q_OBJECT
938public:
939 QCalendarView(QWidget *parent = nullptr);
940
941 void internalUpdate() { updateGeometries(); }
942 void setReadOnly(bool enable);
943 virtual void keyboardSearch(const QString & search) override { Q_UNUSED(search) }
944
945signals:
946 void showDate(QDate date);
947 void changeDate(QDate date, bool changeMonth);
948 void clicked(QDate date);
949 void editingFinished();
950protected:
951 QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
952 void mouseDoubleClickEvent(QMouseEvent *event) override;
953 void mousePressEvent(QMouseEvent *event) override;
954 void mouseMoveEvent(QMouseEvent *event) override;
955 void mouseReleaseEvent(QMouseEvent *event) override;
956#if QT_CONFIG(wheelevent)
957 void wheelEvent(QWheelEvent *event) override;
958#endif
959 void keyPressEvent(QKeyEvent *event) override;
960 bool event(QEvent *event) override;
961
962 QDate handleMouseEvent(QMouseEvent *event);
963public:
964 bool readOnly;
965private:
966 bool validDateClicked;
967#ifdef QT_KEYPAD_NAVIGATION
968 QDate origDate;
969#endif
970};
971
972QCalendarModel::QCalendarModel(QObject *parent)
973 : QAbstractTableModel(parent),
974 m_firstColumn(1),
975 m_firstRow(1),
976 m_date(QDate::currentDate()),
977 m_minimumDate(QDate::fromJulianDay(1)),
978 m_maximumDate(9999, 12, 31),
979 m_shownYear(m_date.year(m_calendar)),
980 m_shownMonth(m_date.month(m_calendar)),
981 m_firstDay(QLocale().firstDayOfWeek()),
982 m_horizontalHeaderFormat(QCalendarWidget::ShortDayNames),
983 m_weekNumbersShown(true),
984 m_view(nullptr)
985{
986}
987
988Qt::DayOfWeek QCalendarModel::dayOfWeekForColumn(int column) const
989{
990 int col = column - m_firstColumn;
991 if (col < 0 || col > 6)
992 return Qt::Sunday;
993 int day = m_firstDay + col;
994 if (day > 7)
995 day -= 7;
996 return Qt::DayOfWeek(day);
997}
998
999int QCalendarModel::columnForDayOfWeek(Qt::DayOfWeek day) const
1000{
1001 if (day < 1 || unsigned(day) > unsigned(7))
1002 return -1;
1003 int column = (int)day - (int)m_firstDay;
1004 if (column < 0)
1005 column += 7;
1006 return column + m_firstColumn;
1007}
1008
1009/*
1010This simple algorithm tries to generate a valid date from the month shown.
1011Some months don't contain a first day (e.g. Jan of -4713 year,
1012so QDate (-4713, 1, 1) would be invalid). In that case we try to generate
1013another valid date for that month. Later, returned date's day is the number of cells
1014calendar widget will reserve for days before referenceDate. (E.g. if returned date's
1015day is 16, that day will be placed in 3rd or 4th row, not in the 1st or 2nd row).
1016Depending on referenceData we can change behaviour of Oct 1582. If referenceDate is 1st
1017of Oct we render 1 Oct in 1st or 2nd row. If referenceDate is 17 of Oct we show always 16
1018dates before 17 of Oct, and since this month contains the hole 5-14 Oct, the first of Oct
1019will be rendered in 2nd or 3rd row, showing more dates from previous month.
1020*/
1021QDate QCalendarModel::referenceDate() const
1022{
1023 // TODO: Check this
1024 int refDay = 1;
1025 while (refDay <= 31) {
1026 QDate refDate(m_shownYear, m_shownMonth, refDay, m_calendar);
1027 if (refDate.isValid())
1028 return refDate;
1029 refDay += 1;
1030 }
1031 return QDate();
1032}
1033
1034int QCalendarModel::columnForFirstOfMonth(QDate date) const
1035{
1036 return (columnForDayOfWeek(static_cast<Qt::DayOfWeek>(m_calendar.dayOfWeek(date)))
1037 - (date.day(m_calendar) % 7) + 8) % 7;
1038}
1039
1040QDate QCalendarModel::dateForCell(int row, int column) const
1041{
1042 if (row < m_firstRow || row > m_firstRow + RowCount - 1 ||
1043 column < m_firstColumn || column > m_firstColumn + ColumnCount - 1)
1044 return QDate();
1045 const QDate refDate = referenceDate();
1046 if (!refDate.isValid())
1047 return QDate();
1048
1049 const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate);
1050 if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset)
1051 row -= 1;
1052
1053 const int requestedDay =
1054 7 * (row - m_firstRow) + column - columnForFirstOfShownMonth - refDate.day(m_calendar) + 1;
1055 return refDate.addDays(requestedDay);
1056}
1057
1058void QCalendarModel::cellForDate(QDate date, int *row, int *column) const
1059{
1060 if (!row && !column)
1061 return;
1062
1063 if (row)
1064 *row = -1;
1065 if (column)
1066 *column = -1;
1067
1068 const QDate refDate = referenceDate();
1069 if (!refDate.isValid())
1070 return;
1071
1072 const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate);
1073 const int requestedPosition = (refDate.daysTo(date) - m_firstColumn +
1074 columnForFirstOfShownMonth + refDate.day(m_calendar) - 1);
1075
1076 int c = requestedPosition % 7;
1077 int r = requestedPosition / 7;
1078 if (c < 0) {
1079 c += 7;
1080 r -= 1;
1081 }
1082
1083 if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset)
1084 r += 1;
1085
1086 if (r < 0 || r > RowCount - 1 || c < 0 || c > ColumnCount - 1)
1087 return;
1088
1089 if (row)
1090 *row = r + m_firstRow;
1091 if (column)
1092 *column = c + m_firstColumn;
1093}
1094
1095QString QCalendarModel::dayName(Qt::DayOfWeek day) const
1096{
1097 switch (m_horizontalHeaderFormat) {
1098 case QCalendarWidget::SingleLetterDayNames: {
1099 QString standaloneDayName = m_view->locale().standaloneDayName(day, QLocale::NarrowFormat);
1100 if (standaloneDayName == m_view->locale().dayName(day, QLocale::NarrowFormat))
1101 return standaloneDayName.left(1);
1102 return standaloneDayName;
1103 }
1104 case QCalendarWidget::ShortDayNames:
1105 return m_view->locale().dayName(day, QLocale::ShortFormat);
1106 case QCalendarWidget::LongDayNames:
1107 return m_view->locale().dayName(day, QLocale::LongFormat);
1108 default:
1109 break;
1110 }
1111 return QString();
1112}
1113
1114QTextCharFormat QCalendarModel::formatForCell(int row, int col) const
1115{
1116 QPalette pal;
1117 QPalette::ColorGroup cg = QPalette::Active;
1118 if (m_view) {
1119 pal = m_view->palette();
1120 if (!m_view->isEnabled())
1121 cg = QPalette::Disabled;
1122 else if (!m_view->isActiveWindow())
1123 cg = QPalette::Inactive;
1124 }
1125
1126 QTextCharFormat format;
1127 format.setFont(m_view->font());
1128 bool header = (m_weekNumbersShown && col == HeaderColumn)
1129 || (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow);
1130 format.setBackground(pal.brush(cg, header ? QPalette::AlternateBase : QPalette::Base));
1131 format.setForeground(pal.brush(cg, QPalette::Text));
1132 if (header) {
1133 format.merge(m_headerFormat);
1134 }
1135
1136 if (col >= m_firstColumn && col < m_firstColumn + ColumnCount) {
1137 Qt::DayOfWeek dayOfWeek = dayOfWeekForColumn(col);
1138 if (m_dayFormats.contains(dayOfWeek))
1139 format.merge(m_dayFormats.value(dayOfWeek));
1140 }
1141
1142 if (!header) {
1143 QDate date = dateForCell(row, col);
1144 format.merge(m_dateFormats.value(date));
1145 if(date < m_minimumDate || date > m_maximumDate)
1146 format.setBackground(pal.brush(cg, QPalette::Window));
1147 if (m_shownMonth != date.month(m_calendar))
1148 format.setForeground(pal.brush(QPalette::Disabled, QPalette::Text));
1149 }
1150 return format;
1151}
1152
1153QVariant QCalendarModel::data(const QModelIndex &index, int role) const
1154{
1155 if (role == Qt::TextAlignmentRole)
1156 return (int) Qt::AlignCenter;
1157
1158 int row = index.row();
1159 int column = index.column();
1160
1161 if(role == Qt::DisplayRole) {
1162 if (m_weekNumbersShown && column == HeaderColumn
1163 && row >= m_firstRow && row < m_firstRow + RowCount) {
1164 QDate date = dateForCell(row, columnForDayOfWeek(Qt::Monday));
1165 if (date.isValid())
1166 return date.weekNumber();
1167 }
1168 if (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow
1169 && column >= m_firstColumn && column < m_firstColumn + ColumnCount)
1170 return dayName(dayOfWeekForColumn(column));
1171 QDate date = dateForCell(row, column);
1172 if (date.isValid())
1173 return date.day(m_calendar);
1174 return QString();
1175 }
1176
1177 QTextCharFormat fmt = formatForCell(row, column);
1178 if (role == Qt::BackgroundRole)
1179 return fmt.background().color();
1180 if (role == Qt::ForegroundRole)
1181 return fmt.foreground().color();
1182 if (role == Qt::FontRole)
1183 return fmt.font();
1184 if (role == Qt::ToolTipRole)
1185 return fmt.toolTip();
1186 return QVariant();
1187}
1188
1189Qt::ItemFlags QCalendarModel::flags(const QModelIndex &index) const
1190{
1191 QDate date = dateForCell(index.row(), index.column());
1192 if (!date.isValid())
1193 return QAbstractTableModel::flags(index);
1194 if (date < m_minimumDate)
1195 return { };
1196 if (date > m_maximumDate)
1197 return { };
1198 return QAbstractTableModel::flags(index);
1199}
1200
1201void QCalendarModel::setDate(QDate d)
1202{
1203 m_date = d;
1204 if (m_date < m_minimumDate)
1205 m_date = m_minimumDate;
1206 else if (m_date > m_maximumDate)
1207 m_date = m_maximumDate;
1208}
1209
1210void QCalendarModel::setCalendar(QCalendar c)
1211{
1212 m_calendar = c;
1213 m_shownYear = m_date.year(c);
1214 m_shownMonth = m_date.month(c);
1215 internalUpdate();
1216 m_view->internalUpdate();
1217}
1218
1219QCalendar QCalendarModel::calendar() const
1220{
1221 return m_calendar;
1222}
1223
1224void QCalendarModel::showMonth(int year, int month)
1225{
1226 if (m_shownYear == year && m_shownMonth == month)
1227 return;
1228
1229 m_shownYear = year;
1230 m_shownMonth = month;
1231
1232 internalUpdate();
1233}
1234
1235void QCalendarModel::setMinimumDate(QDate d)
1236{
1237 if (!d.isValid() || d == m_minimumDate)
1238 return;
1239
1240 m_minimumDate = d;
1241 if (m_maximumDate < m_minimumDate)
1242 m_maximumDate = m_minimumDate;
1243 if (m_date < m_minimumDate)
1244 m_date = m_minimumDate;
1245 internalUpdate();
1246}
1247
1248void QCalendarModel::setMaximumDate(QDate d)
1249{
1250 if (!d.isValid() || d == m_maximumDate)
1251 return;
1252
1253 m_maximumDate = d;
1254 if (m_minimumDate > m_maximumDate)
1255 m_minimumDate = m_maximumDate;
1256 if (m_date > m_maximumDate)
1257 m_date = m_maximumDate;
1258 internalUpdate();
1259}
1260
1261void QCalendarModel::setRange(QDate min, QDate max)
1262{
1263 m_minimumDate = min;
1264 m_maximumDate = max;
1265 if (m_minimumDate > m_maximumDate)
1266 qSwap(m_minimumDate, m_maximumDate);
1267 if (m_date < m_minimumDate)
1268 m_date = m_minimumDate;
1269 if (m_date > m_maximumDate)
1270 m_date = m_maximumDate;
1271 internalUpdate();
1272}
1273
1274void QCalendarModel::internalUpdate()
1275{
1276 QModelIndex begin = index(0, 0);
1277 QModelIndex end = index(m_firstRow + RowCount - 1, m_firstColumn + ColumnCount - 1);
1278 emit dataChanged(begin, end);
1279 emit headerDataChanged(Qt::Vertical, 0, m_firstRow + RowCount - 1);
1280 emit headerDataChanged(Qt::Horizontal, 0, m_firstColumn + ColumnCount - 1);
1281}
1282
1283void QCalendarModel::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format)
1284{
1285 if (m_horizontalHeaderFormat == format)
1286 return;
1287
1288 int oldFormat = m_horizontalHeaderFormat;
1289 m_horizontalHeaderFormat = format;
1290 if (oldFormat == QCalendarWidget::NoHorizontalHeader) {
1291 beginInsertRows(QModelIndex(), 0, 0);
1292 m_firstRow = 1;
1293 endInsertRows();
1294 } else if (m_horizontalHeaderFormat == QCalendarWidget::NoHorizontalHeader) {
1295 beginRemoveRows(QModelIndex(), 0, 0);
1296 m_firstRow = 0;
1297 endRemoveRows();
1298 }
1299 internalUpdate();
1300}
1301
1302void QCalendarModel::setFirstColumnDay(Qt::DayOfWeek dayOfWeek)
1303{
1304 if (m_firstDay == dayOfWeek)
1305 return;
1306
1307 m_firstDay = dayOfWeek;
1308 internalUpdate();
1309}
1310
1311Qt::DayOfWeek QCalendarModel::firstColumnDay() const
1312{
1313 return m_firstDay;
1314}
1315
1316bool QCalendarModel::weekNumbersShown() const
1317{
1318 return m_weekNumbersShown;
1319}
1320
1321void QCalendarModel::setWeekNumbersShown(bool show)
1322{
1323 if (m_weekNumbersShown == show)
1324 return;
1325
1326 m_weekNumbersShown = show;
1327 if (show) {
1328 beginInsertColumns(QModelIndex(), 0, 0);
1329 m_firstColumn = 1;
1330 endInsertColumns();
1331 } else {
1332 beginRemoveColumns(QModelIndex(), 0, 0);
1333 m_firstColumn = 0;
1334 endRemoveColumns();
1335 }
1336 internalUpdate();
1337}
1338
1339QCalendarView::QCalendarView(QWidget *parent)
1340 : QTableView(parent),
1341 readOnly(false),
1342 validDateClicked(false)
1343{
1344 setTabKeyNavigation(false);
1345 setShowGrid(false);
1346 verticalHeader()->setVisible(false);
1347 horizontalHeader()->setVisible(false);
1348 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1349 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1350}
1351
1352QModelIndex QCalendarView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1353{
1354 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1355 if (!calendarModel)
1356 return QTableView::moveCursor(cursorAction, modifiers);
1357
1358 QCalendar cal = calendarModel->calendar();
1359
1360 if (readOnly)
1361 return currentIndex();
1362
1363 QModelIndex index = currentIndex();
1364 QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(index.row(), index.column());
1365 switch (cursorAction) {
1366 case QAbstractItemView::MoveUp:
1367 currentDate = currentDate.addDays(-7);
1368 break;
1369 case QAbstractItemView::MoveDown:
1370 currentDate = currentDate.addDays(7);
1371 break;
1372 case QAbstractItemView::MoveLeft:
1373 currentDate = currentDate.addDays(isRightToLeft() ? 1 : -1);
1374 break;
1375 case QAbstractItemView::MoveRight:
1376 currentDate = currentDate.addDays(isRightToLeft() ? -1 : 1);
1377 break;
1378 case QAbstractItemView::MoveHome: {
1379 auto parts = cal.partsFromDate(currentDate);
1380 if (parts.isValid()) {
1381 parts.day = 1;
1382 currentDate = cal.dateFromParts(parts);
1383 }
1384 }
1385 break;
1386 case QAbstractItemView::MoveEnd: {
1387 auto parts = cal.partsFromDate(currentDate);
1388 if (parts.isValid()) {
1389 parts.day = cal.daysInMonth(parts.month, parts.year);
1390 currentDate = cal.dateFromParts(parts);
1391 }
1392 }
1393 break;
1394 case QAbstractItemView::MovePageUp:
1395 currentDate = currentDate.addMonths(-1, cal);
1396 break;
1397 case QAbstractItemView::MovePageDown:
1398 currentDate = currentDate.addMonths(1, cal);
1399 break;
1400 case QAbstractItemView::MoveNext:
1401 case QAbstractItemView::MovePrevious:
1402 return currentIndex();
1403 default:
1404 break;
1405 }
1406 emit changeDate(currentDate, true);
1407 return currentIndex();
1408}
1409
1410void QCalendarView::keyPressEvent(QKeyEvent *event)
1411{
1412#ifdef QT_KEYPAD_NAVIGATION
1413 if (event->key() == Qt::Key_Select) {
1414 if (QApplicationPrivate::keypadNavigationEnabled()) {
1415 if (!hasEditFocus()) {
1416 setEditFocus(true);
1417 return;
1418 }
1419 }
1420 } else if (event->key() == Qt::Key_Back) {
1421 if (QApplicationPrivate::keypadNavigationEnabled() && hasEditFocus()) {
1422 if (qobject_cast<QCalendarModel *>(model())) {
1423 emit changeDate(origDate, true); //changes selection back to origDate, but doesn't activate
1424 setEditFocus(false);
1425 return;
1426 }
1427 }
1428 }
1429#endif
1430
1431 if (!readOnly) {
1432 switch (event->key()) {
1433 case Qt::Key_Return:
1434 case Qt::Key_Enter:
1435 case Qt::Key_Select:
1436 emit editingFinished();
1437 return;
1438 default:
1439 break;
1440 }
1441 }
1442 QTableView::keyPressEvent(event);
1443}
1444
1445#if QT_CONFIG(wheelevent)
1446void QCalendarView::wheelEvent(QWheelEvent *event)
1447{
1448 const int numDegrees = event->angleDelta().y() / 8;
1449 const int numSteps = numDegrees / 15;
1450 const QModelIndex index = currentIndex();
1451 QCalendarModel *calendarModel = static_cast<QCalendarModel*>(model());
1452 QDate currentDate = calendarModel->dateForCell(index.row(), index.column());
1453 currentDate = currentDate.addMonths(-numSteps, calendarModel->calendar());
1454 emit showDate(currentDate);
1455}
1456#endif
1457
1458bool QCalendarView::event(QEvent *event)
1459{
1460#ifdef QT_KEYPAD_NAVIGATION
1461 if (event->type() == QEvent::FocusIn) {
1462 if (QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model())) {
1463 origDate = calendarModel->m_date;
1464 }
1465 }
1466#endif
1467
1468 return QTableView::event(event);
1469}
1470
1471QDate QCalendarView::handleMouseEvent(QMouseEvent *event)
1472{
1473 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1474 if (!calendarModel)
1475 return QDate();
1476
1477 QPoint pos = event->pos();
1478 QModelIndex index = indexAt(pos);
1479 QDate date = calendarModel->dateForCell(index.row(), index.column());
1480 if (date.isValid() && date >= calendarModel->m_minimumDate
1481 && date <= calendarModel->m_maximumDate) {
1482 return date;
1483 }
1484 return QDate();
1485}
1486
1487void QCalendarView::mouseDoubleClickEvent(QMouseEvent *event)
1488{
1489 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1490 if (!calendarModel) {
1491 QTableView::mouseDoubleClickEvent(event);
1492 return;
1493 }
1494
1495 if (readOnly)
1496 return;
1497
1498 QDate date = handleMouseEvent(event);
1499 validDateClicked = false;
1500 if (date == calendarModel->m_date && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
1501 emit editingFinished();
1502 }
1503}
1504
1505void QCalendarView::mousePressEvent(QMouseEvent *event)
1506{
1507 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1508 if (!calendarModel) {
1509 QTableView::mousePressEvent(event);
1510 return;
1511 }
1512
1513 if (readOnly)
1514 return;
1515
1516 if (event->button() != Qt::LeftButton)
1517 return;
1518
1519 QDate date = handleMouseEvent(event);
1520 if (date.isValid()) {
1521 validDateClicked = true;
1522 int row = -1, col = -1;
1523 static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col);
1524 if (row != -1 && col != -1) {
1525 selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate);
1526 }
1527 } else {
1528 validDateClicked = false;
1529 event->ignore();
1530 }
1531}
1532
1533void QCalendarView::mouseMoveEvent(QMouseEvent *event)
1534{
1535 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1536 if (!calendarModel) {
1537 QTableView::mouseMoveEvent(event);
1538 return;
1539 }
1540
1541 if (readOnly)
1542 return;
1543
1544 if (validDateClicked) {
1545 QDate date = handleMouseEvent(event);
1546 if (date.isValid()) {
1547 int row = -1, col = -1;
1548 static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col);
1549 if (row != -1 && col != -1) {
1550 selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate);
1551 }
1552 }
1553 } else {
1554 event->ignore();
1555 }
1556}
1557
1558void QCalendarView::mouseReleaseEvent(QMouseEvent *event)
1559{
1560 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model());
1561 if (!calendarModel) {
1562 QTableView::mouseReleaseEvent(event);
1563 return;
1564 }
1565
1566 if (event->button() != Qt::LeftButton)
1567 return;
1568
1569 if (readOnly)
1570 return;
1571
1572 if (validDateClicked) {
1573 QDate date = handleMouseEvent(event);
1574 if (date.isValid()) {
1575 emit changeDate(date, true);
1576 emit clicked(date);
1577 if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick))
1578 emit editingFinished();
1579 }
1580 validDateClicked = false;
1581 } else {
1582 event->ignore();
1583 }
1584}
1585
1586// ### Qt6: QStyledItemDelegate
1587class QCalendarDelegate : public QItemDelegate
1588{
1589 Q_OBJECT
1590public:
1591 QCalendarDelegate(QCalendarWidgetPrivate *w, QObject *parent = nullptr)
1592 : QItemDelegate(parent), calendarWidgetPrivate(w)
1593 { }
1594 virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
1595 const QModelIndex &index) const override;
1596 void paintCell(QPainter *painter, const QRect &rect, QDate date) const;
1597
1598private:
1599 QCalendarWidgetPrivate *calendarWidgetPrivate;
1600 mutable QStyleOptionViewItem storedOption;
1601};
1602
1603//Private tool button class
1604class QCalToolButton: public QToolButton
1605{
1606public:
1607 QCalToolButton(QWidget * parent)
1608 : QToolButton(parent)
1609 { }
1610protected:
1611 void paintEvent(QPaintEvent *e) override
1612 {
1613 Q_UNUSED(e)
1614
1615 QStyleOptionToolButton opt;
1616 initStyleOption(&opt);
1617
1618 if (opt.state & QStyle::State_MouseOver || isDown()) {
1619 //act as normal button
1620 setPalette(QPalette());
1621 } else {
1622 //set the highlight color for button text
1623 QPalette toolPalette = palette();
1624 toolPalette.setColor(QPalette::ButtonText, toolPalette.color(QPalette::HighlightedText));
1625 setPalette(toolPalette);
1626 }
1627
1628 QToolButton::paintEvent(e);
1629 }
1630};
1631
1632class QPrevNextCalButton : public QToolButton
1633{
1634 Q_OBJECT
1635public:
1636 QPrevNextCalButton(QWidget *parent) : QToolButton(parent) {}
1637protected:
1638 void paintEvent(QPaintEvent *) override {
1639 QStylePainter painter(this);
1640 QStyleOptionToolButton opt;
1641 initStyleOption(&opt);
1642 opt.state &= ~QStyle::State_HasFocus;
1643 painter.drawComplexControl(QStyle::CC_ToolButton, opt);
1644 }
1645};
1646
1647} // unnamed namespace
1648
1649class QCalendarWidgetPrivate : public QWidgetPrivate
1650{
1651 Q_DECLARE_PUBLIC(QCalendarWidget)
1652public:
1653 QCalendarWidgetPrivate();
1654
1655 void showMonth(int year, int month);
1656 void update();
1657 void paintCell(QPainter *painter, const QRect &rect, QDate date) const;
1658
1659 void _q_slotShowDate(QDate date);
1660 void _q_slotChangeDate(QDate date);
1661 void _q_slotChangeDate(QDate date, bool changeMonth);
1662 void _q_editingFinished();
1663 void _q_monthChanged(QAction*);
1664 void _q_prevMonthClicked();
1665 void _q_nextMonthClicked();
1666 void _q_yearEditingFinished();
1667 void _q_yearClicked();
1668
1669 void createNavigationBar(QWidget *widget);
1670 void updateButtonIcons();
1671 void updateMonthMenu();
1672 void updateMonthMenuNames();
1673 void updateNavigationBar();
1674 void updateCurrentPage(QDate newDate);
1675 inline QDate getCurrentDate();
1676 void setNavigatorEnabled(bool enable);
1677
1678 QCalendarModel *m_model;
1679 QCalendarView *m_view;
1680 QCalendarDelegate *m_delegate;
1681 QItemSelectionModel *m_selection;
1682 QCalendarTextNavigator *m_navigator;
1683 bool m_dateEditEnabled;
1684
1685 QToolButton *nextMonth;
1686 QToolButton *prevMonth;
1687 QCalToolButton *monthButton;
1688 QMenu *monthMenu;
1689 QMap<int, QAction *> monthToAction;
1690 QCalToolButton *yearButton;
1691 QSpinBox *yearEdit;
1692 QWidget *navBarBackground;
1693 QSpacerItem *spaceHolder;
1694
1695 bool navBarVisible;
1696 mutable QSize cachedSizeHint;
1697 Qt::FocusPolicy oldFocusPolicy;
1698};
1699
1700void QCalendarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
1701 const QModelIndex &index) const
1702{
1703 QDate date = calendarWidgetPrivate->m_model->dateForCell(index.row(), index.column());
1704 if (date.isValid()) {
1705 storedOption = option;
1706 QRect rect = option.rect;
1707 calendarWidgetPrivate->paintCell(painter, rect, date);
1708 } else {
1709 QItemDelegate::paint(painter, option, index);
1710 }
1711}
1712
1713void QCalendarDelegate::paintCell(QPainter *painter, const QRect &rect, QDate date) const
1714{
1715 storedOption.rect = rect;
1716 int row = -1;
1717 int col = -1;
1718 calendarWidgetPrivate->m_model->cellForDate(date, &row, &col);
1719 QModelIndex idx = calendarWidgetPrivate->m_model->index(row, col);
1720 QItemDelegate::paint(painter, storedOption, idx);
1721}
1722
1723QCalendarWidgetPrivate::QCalendarWidgetPrivate()
1724 : QWidgetPrivate()
1725{
1726 m_model = nullptr;
1727 m_view = nullptr;
1728 m_delegate = nullptr;
1729 m_selection = nullptr;
1730 m_navigator = nullptr;
1731 m_dateEditEnabled = false;
1732 navBarVisible = true;
1733 oldFocusPolicy = Qt::StrongFocus;
1734}
1735
1736void QCalendarWidgetPrivate::setNavigatorEnabled(bool enable)
1737{
1738 Q_Q(QCalendarWidget);
1739
1740 bool navigatorEnabled = (m_navigator->widget() != nullptr);
1741 if (enable == navigatorEnabled)
1742 return;
1743
1744 if (enable) {
1745 m_navigator->setWidget(q);
1746 q->connect(m_navigator, SIGNAL(dateChanged(QDate)),
1747 q, SLOT(_q_slotChangeDate(QDate)));
1748 q->connect(m_navigator, SIGNAL(editingFinished()),
1749 q, SLOT(_q_editingFinished()));
1750 m_view->installEventFilter(m_navigator);
1751 } else {
1752 m_navigator->setWidget(nullptr);
1753 q->disconnect(m_navigator, SIGNAL(dateChanged(QDate)),
1754 q, SLOT(_q_slotChangeDate(QDate)));
1755 q->disconnect(m_navigator, SIGNAL(editingFinished()),
1756 q, SLOT(_q_editingFinished()));
1757 m_view->removeEventFilter(m_navigator);
1758 }
1759}
1760
1761void QCalendarWidgetPrivate::createNavigationBar(QWidget *widget)
1762{
1763 Q_Q(QCalendarWidget);
1764 navBarBackground = new QWidget(widget);
1765 navBarBackground->setObjectName(QLatin1String("qt_calendar_navigationbar"));
1766 navBarBackground->setAutoFillBackground(true);
1767 navBarBackground->setBackgroundRole(QPalette::Highlight);
1768
1769 prevMonth = new QPrevNextCalButton(navBarBackground);
1770 nextMonth = new QPrevNextCalButton(navBarBackground);
1771 prevMonth->setAutoRaise(true);
1772 nextMonth->setAutoRaise(true);
1773 prevMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1774 nextMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1775 nextMonth->setAutoRaise(true);
1776 updateButtonIcons();
1777 prevMonth->setAutoRepeat(true);
1778 nextMonth->setAutoRepeat(true);
1779
1780 monthButton = new QCalToolButton(navBarBackground);
1781 monthButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1782 monthButton->setAutoRaise(true);
1783 monthButton->setPopupMode(QToolButton::InstantPopup);
1784 monthMenu = new QMenu(monthButton);
1785 for (int i = 1, e = m_model->m_calendar.maximumMonthsInYear(); i <= e; i++) {
1786 QString monthName(m_model->monthName(q->locale(), i));
1787 QAction *act = monthMenu->addAction(monthName);
1788 act->setData(i);
1789 monthToAction[i] = act;
1790 }
1791 monthButton->setMenu(monthMenu);
1792 yearButton = new QCalToolButton(navBarBackground);
1793 yearButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1794 yearButton->setAutoRaise(true);
1795 yearEdit = new QSpinBox(navBarBackground);
1796
1797 QFont font = q->font();
1798 font.setBold(true);
1799 monthButton->setFont(font);
1800 yearButton->setFont(font);
1801 yearEdit->setFrame(false);
1802 yearEdit->setMinimum(m_model->m_minimumDate.year(m_model->m_calendar));
1803 yearEdit->setMaximum(m_model->m_maximumDate.year(m_model->m_calendar));
1804 yearEdit->hide();
1805 spaceHolder = new QSpacerItem(0,0);
1806
1807 QHBoxLayout *headerLayout = new QHBoxLayout;
1808 headerLayout->setContentsMargins(QMargins());
1809 headerLayout->setSpacing(0);
1810 headerLayout->addWidget(prevMonth);
1811 headerLayout->insertStretch(headerLayout->count());
1812 headerLayout->addWidget(monthButton);
1813 headerLayout->addItem(spaceHolder);
1814 headerLayout->addWidget(yearButton);
1815 headerLayout->insertStretch(headerLayout->count());
1816 headerLayout->addWidget(nextMonth);
1817 navBarBackground->setLayout(headerLayout);
1818
1819 yearEdit->setFocusPolicy(Qt::StrongFocus);
1820 prevMonth->setFocusPolicy(Qt::NoFocus);
1821 nextMonth->setFocusPolicy(Qt::NoFocus);
1822 yearButton->setFocusPolicy(Qt::NoFocus);
1823 monthButton->setFocusPolicy(Qt::NoFocus);
1824
1825 //set names for the header controls.
1826 prevMonth->setObjectName(QLatin1String("qt_calendar_prevmonth"));
1827 nextMonth->setObjectName(QLatin1String("qt_calendar_nextmonth"));
1828 monthButton->setObjectName(QLatin1String("qt_calendar_monthbutton"));
1829 yearButton->setObjectName(QLatin1String("qt_calendar_yearbutton"));
1830 yearEdit->setObjectName(QLatin1String("qt_calendar_yearedit"));
1831
1832 updateMonthMenu();
1833 showMonth(m_model->m_date.year(m_model->m_calendar), m_model->m_date.month(m_model->m_calendar));
1834}
1835
1836void QCalendarWidgetPrivate::updateButtonIcons()
1837{
1838 Q_Q(QCalendarWidget);
1839 prevMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowRight : QStyle::SP_ArrowLeft, nullptr, q));
1840 nextMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowLeft : QStyle::SP_ArrowRight, nullptr, q));
1841}
1842
1843void QCalendarWidgetPrivate::updateMonthMenu()
1844{
1845 int maxMonths = m_model->m_calendar.monthsInYear(m_model->m_shownYear);
1846 int beg = 1, end = maxMonths;
1847 bool prevEnabled = true;
1848 bool nextEnabled = true;
1849 QCalendar cal = m_model->calendar();
1850 if (m_model->m_shownYear == m_model->m_minimumDate.year(cal)) {
1851 beg = m_model->m_minimumDate.month(cal);
1852 if (m_model->m_shownMonth == m_model->m_minimumDate.month(cal))
1853 prevEnabled = false;
1854 }
1855 if (m_model->m_shownYear == m_model->m_maximumDate.year(cal)) {
1856 end = m_model->m_maximumDate.month(cal);
1857 if (m_model->m_shownMonth == m_model->m_maximumDate.month(cal))
1858 nextEnabled = false;
1859 }
1860 prevMonth->setEnabled(prevEnabled);
1861 nextMonth->setEnabled(nextEnabled);
1862 for (int i = 1; i <= maxMonths; i++) {
1863 bool monthEnabled = true;
1864 if (i < beg || i > end)
1865 monthEnabled = false;
1866 monthToAction[i]->setEnabled(monthEnabled);
1867 }
1868}
1869
1870void QCalendarWidgetPrivate::updateMonthMenuNames()
1871{
1872 Q_Q(QCalendarWidget);
1873
1874 for (int i = 1; i <= 12; i++) {
1875 QString monthName(m_model->monthName(q->locale(), i));
1876 monthToAction[i]->setText(monthName);
1877 }
1878}
1879
1880void QCalendarWidgetPrivate::updateCurrentPage(QDate date)
1881{
1882 Q_Q(QCalendarWidget);
1883 QCalendar cal = m_model->calendar();
1884
1885 QDate newDate = date;
1886 QDate minDate = q->minimumDate();
1887 QDate maxDate = q->maximumDate();
1888 if (minDate.isValid()&& minDate.daysTo(newDate) < 0)
1889 newDate = minDate;
1890 if (maxDate.isValid()&& maxDate.daysTo(newDate) > 0)
1891 newDate = maxDate;
1892 showMonth(newDate.year(cal), newDate.month(cal));
1893 int row = -1, col = -1;
1894 m_model->cellForDate(newDate, &row, &col);
1895 if (row != -1 && col != -1)
1896 {
1897 m_view->selectionModel()->setCurrentIndex(m_model->index(row, col),
1898 QItemSelectionModel::NoUpdate);
1899 }
1900}
1901
1902void QCalendarWidgetPrivate::_q_monthChanged(QAction *act)
1903{
1904 monthButton->setText(act->text());
1905 QDate currentDate = getCurrentDate();
1906 QDate newDate = currentDate.addMonths(act->data().toInt() - currentDate.month(m_model->m_calendar), m_model->m_calendar);
1907 updateCurrentPage(newDate);
1908}
1909
1910QDate QCalendarWidgetPrivate::getCurrentDate()
1911{
1912 QModelIndex index = m_view->currentIndex();
1913 return m_model->dateForCell(index.row(), index.column());
1914}
1915
1916void QCalendarWidgetPrivate::_q_prevMonthClicked()
1917{
1918 QDate currentDate = getCurrentDate().addMonths(-1, m_model->m_calendar);
1919 updateCurrentPage(currentDate);
1920}
1921
1922void QCalendarWidgetPrivate::_q_nextMonthClicked()
1923{
1924 QDate currentDate = getCurrentDate().addMonths(1, m_model->m_calendar);
1925 updateCurrentPage(currentDate);
1926}
1927
1928void QCalendarWidgetPrivate::_q_yearEditingFinished()
1929{
1930 Q_Q(QCalendarWidget);
1931 yearEdit->hide();
1932 q->setFocusPolicy(oldFocusPolicy);
1933 qApp->removeEventFilter(q);
1934 spaceHolder->changeSize(0, 0);
1935 yearButton->show();
1936 QDate currentDate = getCurrentDate();
1937 int newYear = q->locale().toInt(yearEdit->text());
1938 currentDate = currentDate.addYears(newYear - currentDate.year(m_model->m_calendar), m_model->m_calendar);
1939 yearButton->setText(q->locale().toString(currentDate, u"yyyy", m_model->m_calendar));
1940 updateCurrentPage(currentDate);
1941}
1942
1943void QCalendarWidgetPrivate::_q_yearClicked()
1944{
1945 Q_Q(QCalendarWidget);
1946 //show the spinbox on top of the button
1947 yearEdit->setGeometry(yearButton->x(), yearButton->y(),
1948 yearEdit->sizeHint().width(), yearButton->height());
1949 spaceHolder->changeSize(yearButton->width(), 0);
1950 yearButton->hide();
1951 oldFocusPolicy = q->focusPolicy();
1952 q->setFocusPolicy(Qt::NoFocus);
1953 yearEdit->show();
1954 qApp->installEventFilter(q);
1955 yearEdit->raise();
1956 yearEdit->selectAll();
1957 yearEdit->setFocus(Qt::MouseFocusReason);
1958}
1959
1960void QCalendarWidgetPrivate::showMonth(int year, int month)
1961{
1962 if (m_model->m_shownYear == year && m_model->m_shownMonth == month)
1963 return;
1964 Q_Q(QCalendarWidget);
1965 m_model->showMonth(year, month);
1966 updateNavigationBar();
1967 emit q->currentPageChanged(year, month);
1968 m_view->internalUpdate();
1969 cachedSizeHint = QSize();
1970 update();
1971 updateMonthMenu();
1972}
1973
1974void QCalendarWidgetPrivate::updateNavigationBar()
1975{
1976 Q_Q(QCalendarWidget);
1977
1978 QString monthName = m_model->monthName(q->locale(), m_model->m_shownMonth);
1979
1980 monthButton->setText(monthName);
1981 yearEdit->setValue(m_model->m_shownYear);
1982 yearButton->setText(yearEdit->text());
1983}
1984
1985void QCalendarWidgetPrivate::update()
1986{
1987 QDate currentDate = m_model->m_date;
1988 int row, column;
1989 m_model->cellForDate(currentDate, &row, &column);
1990 QModelIndex idx;
1991 m_selection->clear();
1992 if (row != -1 && column != -1) {
1993 idx = m_model->index(row, column);
1994 m_selection->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent);
1995 }
1996}
1997
1998void QCalendarWidgetPrivate::paintCell(QPainter *painter, const QRect &rect, QDate date) const
1999{
2000 Q_Q(const QCalendarWidget);
2001 q->paintCell(painter, rect, date);
2002}
2003
2004void QCalendarWidgetPrivate::_q_slotShowDate(QDate date)
2005{
2006 updateCurrentPage(date);
2007}
2008
2009void QCalendarWidgetPrivate::_q_slotChangeDate(QDate date)
2010{
2011 _q_slotChangeDate(date, true);
2012}
2013
2014void QCalendarWidgetPrivate::_q_slotChangeDate(QDate date, bool changeMonth)
2015{
2016 QDate oldDate = m_model->m_date;
2017 m_model->setDate(date);
2018 QDate newDate = m_model->m_date;
2019 if (changeMonth)
2020 showMonth(newDate.year(m_model->m_calendar), newDate.month(m_model->m_calendar));
2021 if (oldDate != newDate) {
2022 update();
2023 Q_Q(QCalendarWidget);
2024 m_navigator->setDate(newDate);
2025 emit q->selectionChanged();
2026 }
2027}
2028
2029void QCalendarWidgetPrivate::_q_editingFinished()
2030{
2031 Q_Q(QCalendarWidget);
2032 emit q->activated(m_model->m_date);
2033}
2034
2035/*!
2036 \class QCalendarWidget
2037 \brief The QCalendarWidget class provides a monthly based
2038 calendar widget allowing the user to select a date.
2039 \since 4.2
2040
2041 \ingroup advanced
2042 \inmodule QtWidgets
2043
2044 \image fusion-calendarwidget.png
2045
2046 The widget is initialized with the current month and year, but
2047 QCalendarWidget provides several public slots to change the year
2048 and month that is shown.
2049
2050 By default, today's date is selected, and the user can select a
2051 date using both mouse and keyboard. The currently selected date
2052 can be retrieved using the selectedDate() function. It is
2053 possible to constrain the user selection to a given date range by
2054 setting the minimumDate and maximumDate properties.
2055 Alternatively, both properties can be set in one go using the
2056 setDateRange() convenience slot. Set the \l selectionMode
2057 property to NoSelection to prohibit the user from selecting at
2058 all. Note that a date also can be selected programmatically using
2059 the setSelectedDate() slot.
2060
2061 The currently displayed month and year can be retrieved using the
2062 monthShown() and yearShown() functions, respectively.
2063
2064 A newly created calendar widget uses abbreviated day names, and
2065 both Saturdays and Sundays are marked in red. The calendar grid is
2066 not visible. The week numbers are displayed, and the first column
2067 day is the first day of the week for the calendar's locale.
2068
2069 The notation of the days can be altered to a single letter
2070 abbreviations ("M" for "Monday") by setting the
2071 horizontalHeaderFormat property to
2072 QCalendarWidget::SingleLetterDayNames. Setting the same property
2073 to QCalendarWidget::LongDayNames makes the header display the
2074 complete day names. The week numbers can be removed by setting
2075 the verticalHeaderFormat property to
2076 QCalendarWidget::NoVerticalHeader. The calendar grid can be
2077 turned on by setting the gridVisible property to true using the
2078 setGridVisible() function:
2079
2080 \table
2081 \row \li
2082 \image qcalendarwidget-grid.png
2083 \row \li
2084 \snippet code/src_gui_widgets_qcalendarwidget.cpp 0
2085 \endtable
2086
2087 Finally, the day in the first column can be altered using the
2088 setFirstDayOfWeek() function.
2089
2090 The QCalendarWidget class also provides three signals,
2091 selectionChanged(), activated() and currentPageChanged() making it
2092 possible to respond to user interaction.
2093
2094 The rendering of the headers, weekdays or single days can be
2095 largely customized by setting QTextCharFormat's for some special
2096 weekday, a special date or for the rendering of the headers.
2097
2098 Only a subset of the properties in QTextCharFormat are used by the
2099 calendar widget. Currently, the foreground, background and font
2100 properties are used to determine the rendering of individual cells
2101 in the widget.
2102
2103 \sa QDate, QDateEdit, QTextCharFormat
2104*/
2105
2106/*!
2107 \enum QCalendarWidget::SelectionMode
2108
2109 This enum describes the types of selection offered to the user for
2110 selecting dates in the calendar.
2111
2112 \value NoSelection Dates cannot be selected.
2113 \value SingleSelection Single dates can be selected.
2114
2115 \sa selectionMode
2116*/
2117
2118/*!
2119 Constructs a calendar widget with the given \a parent.
2120
2121 The widget is initialized with the current month and year, and the
2122 currently selected date is today.
2123
2124 \sa setCurrentPage()
2125*/
2126QCalendarWidget::QCalendarWidget(QWidget *parent)
2127 : QWidget(*new QCalendarWidgetPrivate, parent, { })
2128{
2129 Q_D(QCalendarWidget);
2130
2131 setAutoFillBackground(true);
2132 setBackgroundRole(QPalette::Window);
2133
2134 QVBoxLayout *layoutV = new QVBoxLayout(this);
2135 layoutV->setContentsMargins(QMargins());
2136 d->m_model = new QCalendarModel(this);
2137 QTextCharFormat fmt;
2138 fmt.setForeground(QBrush(Qt::red));
2139 d->m_model->m_dayFormats.insert(Qt::Saturday, fmt);
2140 d->m_model->m_dayFormats.insert(Qt::Sunday, fmt);
2141 d->m_view = new QCalendarView(this);
2142 d->m_view->setObjectName(QLatin1String("qt_calendar_calendarview"));
2143 d->m_view->setModel(d->m_model);
2144 d->m_model->setView(d->m_view);
2145 d->m_view->setSelectionBehavior(QAbstractItemView::SelectItems);
2146 d->m_view->setSelectionMode(QAbstractItemView::SingleSelection);
2147 d->m_view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
2148 d->m_view->horizontalHeader()->setSectionsClickable(false);
2149 d->m_view->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
2150 d->m_view->verticalHeader()->setSectionsClickable(false);
2151 d->m_selection = d->m_view->selectionModel();
2152 d->createNavigationBar(this);
2153 d->m_view->setFrameStyle(QFrame::NoFrame);
2154 d->m_delegate = new QCalendarDelegate(d, this);
2155 d->m_view->setItemDelegate(d->m_delegate);
2156 d->update();
2157 d->updateNavigationBar();
2158 setFocusPolicy(Qt::StrongFocus);
2159 setFocusProxy(d->m_view);
2160 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
2161
2162 connect(d->m_view, SIGNAL(showDate(QDate)),
2163 this, SLOT(_q_slotShowDate(QDate)));
2164 connect(d->m_view, SIGNAL(changeDate(QDate,bool)),
2165 this, SLOT(_q_slotChangeDate(QDate,bool)));
2166 connect(d->m_view, SIGNAL(clicked(QDate)),
2167 this, SIGNAL(clicked(QDate)));
2168 connect(d->m_view, SIGNAL(editingFinished()),
2169 this, SLOT(_q_editingFinished()));
2170
2171 connect(d->prevMonth, SIGNAL(clicked(bool)),
2172 this, SLOT(_q_prevMonthClicked()));
2173 connect(d->nextMonth, SIGNAL(clicked(bool)),
2174 this, SLOT(_q_nextMonthClicked()));
2175 connect(d->yearButton, SIGNAL(clicked(bool)),
2176 this, SLOT(_q_yearClicked()));
2177 connect(d->monthMenu, SIGNAL(triggered(QAction*)),
2178 this, SLOT(_q_monthChanged(QAction*)));
2179 connect(d->yearEdit, SIGNAL(editingFinished()),
2180 this, SLOT(_q_yearEditingFinished()));
2181
2182 layoutV->setContentsMargins(QMargins());
2183 layoutV->setSpacing(0);
2184 layoutV->addWidget(d->navBarBackground);
2185 layoutV->addWidget(d->m_view);
2186
2187 d->m_navigator = new QCalendarTextNavigator(this);
2188 setDateEditEnabled(true);
2189}
2190
2191/*!
2192 Destroys the calendar widget.
2193*/
2194QCalendarWidget::~QCalendarWidget()
2195{
2196}
2197
2198/*!
2199 \reimp
2200*/
2201QSize QCalendarWidget::sizeHint() const
2202{
2203 return minimumSizeHint();
2204}
2205
2206/*!
2207 \reimp
2208*/
2209QSize QCalendarWidget::minimumSizeHint() const
2210{
2211 Q_D(const QCalendarWidget);
2212 if (d->cachedSizeHint.isValid())
2213 return d->cachedSizeHint;
2214
2215 ensurePolished();
2216
2217 int w = 0;
2218 int h = 0;
2219
2220 int end = 53;
2221 int rows = 7;
2222 int cols = 8;
2223
2224 QStyleOption option;
2225 option.initFrom(this);
2226 const int marginH = (style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option) + 1) * 2;
2227
2228 if (horizontalHeaderFormat() == QCalendarWidget::NoHorizontalHeader) {
2229 rows = 6;
2230 } else {
2231 for (int i = 1; i <= 7; i++) {
2232 QFontMetrics fm(d->m_model->formatForCell(0, i).font());
2233 w = qMax(w, fm.horizontalAdvance(d->m_model->dayName(d->m_model->dayOfWeekForColumn(i))) + marginH);
2234 h = qMax(h, fm.height());
2235 }
2236 }
2237
2238 if (verticalHeaderFormat() == QCalendarWidget::NoVerticalHeader) {
2239 cols = 7;
2240 } else {
2241 for (int i = 1; i <= 6; i++) {
2242 QFontMetrics fm(d->m_model->formatForCell(i, 0).font());
2243 for (int j = 1; j < end; j++)
2244 w = qMax(w, fm.horizontalAdvance(QString::number(j)) + marginH);
2245 h = qMax(h, fm.height());
2246 }
2247 }
2248
2249 QFontMetrics fm(d->m_model->formatForCell(1, 1).font());
2250 for (int i = 1; i <= end; i++) {
2251 w = qMax(w, fm.horizontalAdvance(QString::number(i)) + marginH);
2252 h = qMax(h, fm.height());
2253 }
2254
2255 if (d->m_view->showGrid()) {
2256 // hardcoded in tableview
2257 w += 1;
2258 h += 1;
2259 }
2260
2261 w += 1; // default column span
2262
2263 h = qMax(h, d->m_view->verticalHeader()->minimumSectionSize());
2264 w = qMax(w, d->m_view->horizontalHeader()->minimumSectionSize());
2265
2266 //add the size of the header.
2267 QSize headerSize(0, 0);
2268 if (d->navBarVisible) {
2269 int headerH = d->navBarBackground->sizeHint().height();
2270 int headerW = 0;
2271
2272 headerW += d->prevMonth->sizeHint().width();
2273 headerW += d->nextMonth->sizeHint().width();
2274
2275 QFontMetrics fm = d->monthButton->fontMetrics();
2276 int monthW = 0;
2277 for (int i = 1; i < 12; i++) {
2278 QString monthName = d->m_model->monthName(locale(), i);
2279 monthW = qMax(monthW, fm.boundingRect(monthName).width());
2280 }
2281 const int buttonDecoMargin = d->monthButton->sizeHint().width() - fm.boundingRect(d->monthButton->text()).width();
2282 headerW += monthW + buttonDecoMargin;
2283
2284 fm = d->yearButton->fontMetrics();
2285 headerW += fm.boundingRect(QLatin1String("5555")).width() + buttonDecoMargin;
2286
2287 headerSize = QSize(headerW, headerH);
2288 }
2289 w *= cols;
2290 w = qMax(headerSize.width(), w);
2291 h = (h * rows) + headerSize.h