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(width: fieldWidth, fill: 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(position: 0, n: startPos) + QLatin1String("<b>") + str.midRef(position: startPos, n: 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(a: qMax(a: 1, b: m_day), b: cal.daysInMonth(month: parts.month, year: 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(str: formatNumber(number: m_day, fieldWidth: 2), pos: 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(number: date.day(cal), fieldWidth: 2);
210 } else if (repeat == 3) {
211 return m_locale.dayName(date.dayOfWeek(cal), format: QLocale::ShortFormat);
212 } else /* repeat >= 4 */ {
213 return m_locale.dayName(date.dayOfWeek(cal), format: 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(a: qMax(a: 1, b: m_month), b: cal.monthsInYear(year: parts.year));
295 parts.day = qMin(a: parts.day, b: cal.daysInMonth(month: m_month, year: 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(str: formatNumber(number: m_month, fieldWidth: 2), pos: 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(number: parts.month, fieldWidth: 2);
318 // Text forms:
319 if (repeat == 3)
320 return cal.standaloneMonthName(locale: m_locale, month: parts.month, year: parts.year, format: QLocale::ShortFormat);
321 /* repeat >= 4 */
322 return cal.standaloneMonthName(locale: m_locale, month: parts.month, year: parts.year, format: 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(n: 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(n: 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(a: 1, b: m_year);
404 parts.day = qMin(a: parts.day, b: cal.daysInMonth(month: parts.month, year: 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(str: formatNumber(number: m_year, fieldWidth: 4), pos: m_pos);
417}
418
419QString QCalendarYearValidator::text(QDate date, QCalendar cal, int repeat) const
420{
421 if (repeat < 4)
422 return formatNumber(number: date.year(cal) % 100, fieldWidth: 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(i: index);
501 while (index + count < str.size() && str.at(i: 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(date: m_currentDate, cal, repeat: 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(position: pos);
552 int offset = 1;
553
554 if (mid.startsWith(c: quote)) {
555 quoting = !quoting;
556 } else {
557 const QChar nextChar = format.at(i: pos);
558 if (quoting) {
559 separator += nextChar;
560 quoting = false;
561 } else {
562 QCalendarDateSectionValidator *validator = nullptr;
563 if (nextChar == QLatin1Char('d')) {
564 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
565 validator = &m_dayValidator;
566 } else if (nextChar == QLatin1Char('M')) {
567 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
568 validator = &m_monthValidator;
569 } else if (nextChar == QLatin1Char('y')) {
570 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
571 validator = &m_yearValidator;
572 } else {
573 separator += nextChar;
574 }
575 if (validator) {
576 m_tokens.push_back(x: SectionToken(validator, offset));
577 m_separators.append(t: 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(date: m_currentDate, cal);
593 m_currentDate = m_monthValidator.applyToDate(date: m_currentDate, cal);
594 m_currentDate = m_dayValidator.applyToDate(date: 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(msec: m_editDelay, obj: this);
700
701 m_dateText->setText(m_dateValidator->currentText(cal: 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(acr: QPalette::Window, abrush: m_dateFrame->window()->palette().brush(cr: 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(format: QLocale::ShortFormat));
741 m_dateValidator->setInitialDate(date: m_date, cal: 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(i: 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(key: QKeySequence::Cancel)) {
772 removeDateLabel();
773#endif
774 } else if (e->type() == QEvent::KeyPress) {
775 createDateLabel();
776 m_dateValidator->handleKeyEvent(keyEvent: ke, cal: 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(watched: o,event: 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, year: m_shownYear, format: 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(jd_: 1)),
978 m_maximumDate(9999, 12, 31),
979 m_shownYear(m_date.year(cal: m_calendar)),
980 m_shownMonth(m_date.month(cal: 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(day: static_cast<Qt::DayOfWeek>(m_calendar.dayOfWeek(date)))
1037 - (date.day(cal: 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(date: refDate);
1050 if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset)
1051 row -= 1;
1052
1053 const int requestedDay =
1054 7 * (row - m_firstRow) + column - columnForFirstOfShownMonth - refDate.day(cal: m_calendar) + 1;
1055 return refDate.addDays(days: 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(date: refDate);
1073 const int requestedPosition = (refDate.daysTo(date) - m_firstColumn +
1074 columnForFirstOfShownMonth + refDate.day(cal: 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, format: QLocale::NarrowFormat);
1100 if (standaloneDayName == m_view->locale().dayName(day, format: QLocale::NarrowFormat))
1101 return standaloneDayName.left(n: 1);
1102 return standaloneDayName;
1103 }
1104 case QCalendarWidget::ShortDayNames:
1105 return m_view->locale().dayName(day, format: QLocale::ShortFormat);
1106 case QCalendarWidget::LongDayNames:
1107 return m_view->locale().dayName(day, format: 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, cr: header ? QPalette::AlternateBase : QPalette::Base));
1131 format.setForeground(pal.brush(cg, cr: QPalette::Text));
1132 if (header) {
1133 format.merge(other: m_headerFormat);
1134 }
1135
1136 if (col >= m_firstColumn && col < m_firstColumn + ColumnCount) {
1137 Qt::DayOfWeek dayOfWeek = dayOfWeekForColumn(column: col);
1138 if (m_dayFormats.contains(day: dayOfWeek))
1139 format.merge(other: m_dayFormats.value(day: dayOfWeek));
1140 }
1141
1142 if (!header) {
1143 QDate date = dateForCell(row, column: col);
1144 format.merge(other: m_dateFormats.value(akey: date));
1145 if(date < m_minimumDate || date > m_maximumDate)
1146 format.setBackground(pal.brush(cg, cr: QPalette::Window));
1147 if (m_shownMonth != date.month(cal: m_calendar))
1148 format.setForeground(pal.brush(cg: QPalette::Disabled, cr: 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, column: columnForDayOfWeek(day: 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(day: dayOfWeekForColumn(column));
1171 QDate date = dateForCell(row, column);
1172 if (date.isValid())
1173 return date.day(cal: m_calendar);
1174 return QString();
1175 }
1176
1177 QTextCharFormat fmt = formatForCell(row, col: 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(row: index.row(), column: 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(cal: c);
1214 m_shownMonth = m_date.month(cal: 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(value1&: m_minimumDate, value2&: 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(row: 0, column: 0);
1277 QModelIndex end = index(row: m_firstRow + RowCount - 1, column: m_firstColumn + ColumnCount - 1);
1278 emit dataChanged(topLeft: begin, bottomRight: end);
1279 emit headerDataChanged(orientation: Qt::Vertical, first: 0, last: m_firstRow + RowCount - 1);
1280 emit headerDataChanged(orientation: Qt::Horizontal, first: 0, last: 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(parent: QModelIndex(), first: 0, last: 0);
1292 m_firstRow = 1;
1293 endInsertRows();
1294 } else if (m_horizontalHeaderFormat == QCalendarWidget::NoHorizontalHeader) {
1295 beginRemoveRows(parent: QModelIndex(), first: 0, last: 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(parent: QModelIndex(), first: 0, last: 0);
1329 m_firstColumn = 1;
1330 endInsertColumns();
1331 } else {
1332 beginRemoveColumns(parent: QModelIndex(), first: 0, last: 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 *>(object: 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(row: index.row(), column: index.column());
1365 switch (cursorAction) {
1366 case QAbstractItemView::MoveUp:
1367 currentDate = currentDate.addDays(days: -7);
1368 break;
1369 case QAbstractItemView::MoveDown:
1370 currentDate = currentDate.addDays(days: 7);
1371 break;
1372 case QAbstractItemView::MoveLeft:
1373 currentDate = currentDate.addDays(days: isRightToLeft() ? 1 : -1);
1374 break;
1375 case QAbstractItemView::MoveRight:
1376 currentDate = currentDate.addDays(days: isRightToLeft() ? -1 : 1);
1377 break;
1378 case QAbstractItemView::MoveHome: {
1379 auto parts = cal.partsFromDate(date: 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(date: currentDate);
1388 if (parts.isValid()) {
1389 parts.day = cal.daysInMonth(month: parts.month, year: parts.year);
1390 currentDate = cal.dateFromParts(parts);
1391 }
1392 }
1393 break;
1394 case QAbstractItemView::MovePageUp:
1395 currentDate = currentDate.addMonths(months: -1, cal);
1396 break;
1397 case QAbstractItemView::MovePageDown:
1398 currentDate = currentDate.addMonths(months: 1, cal);
1399 break;
1400 case QAbstractItemView::MoveNext:
1401 case QAbstractItemView::MovePrevious:
1402 return currentIndex();
1403 default:
1404 break;
1405 }
1406 emit changeDate(date: currentDate, changeMonth: 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(row: index.row(), column: index.column());
1453 currentDate = currentDate.addMonths(months: -numSteps, cal: calendarModel->calendar());
1454 emit showDate(date: 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 *>(object: model());
1474 if (!calendarModel)
1475 return QDate();
1476
1477 QPoint pos = event->pos();
1478 QModelIndex index = indexAt(p: pos);
1479 QDate date = calendarModel->dateForCell(row: index.row(), column: 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 *>(object: 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(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
1501 emit editingFinished();
1502 }
1503}
1504
1505void QCalendarView::mousePressEvent(QMouseEvent *event)
1506{
1507 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: 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: &row, column: &col);
1524 if (row != -1 && col != -1) {
1525 selectionModel()->setCurrentIndex(index: model()->index(row, column: col), command: 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 *>(object: 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: &row, column: &col);
1549 if (row != -1 && col != -1) {
1550 selectionModel()->setCurrentIndex(index: model()->index(row, column: col), command: QItemSelectionModel::NoUpdate);
1551 }
1552 }
1553 } else {
1554 event->ignore();
1555 }
1556}
1557
1558void QCalendarView::mouseReleaseEvent(QMouseEvent *event)
1559{
1560 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: 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, changeMonth: true);
1576 emit clicked(date);
1577 if (style()->styleHint(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(option: &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(acr: QPalette::ButtonText, acolor: toolPalette.color(cr: 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(option: &opt);
1642 opt.state &= ~QStyle::State_HasFocus;
1643 painter.drawComplexControl(cc: 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(row: index.row(), column: 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: &row, column: &col);
1719 QModelIndex idx = calendarWidgetPrivate->m_model->index(row, column: col);
1720 QItemDelegate::paint(painter, option: storedOption, index: 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(sender: m_navigator, SIGNAL(dateChanged(QDate)),
1747 receiver: q, SLOT(_q_slotChangeDate(QDate)));
1748 q->connect(sender: m_navigator, SIGNAL(editingFinished()),
1749 receiver: q, SLOT(_q_editingFinished()));
1750 m_view->installEventFilter(filterObj: m_navigator);
1751 } else {
1752 m_navigator->setWidget(nullptr);
1753 q->disconnect(sender: m_navigator, SIGNAL(dateChanged(QDate)),
1754 receiver: q, SLOT(_q_slotChangeDate(QDate)));
1755 q->disconnect(sender: m_navigator, SIGNAL(editingFinished()),
1756 receiver: q, SLOT(_q_editingFinished()));
1757 m_view->removeEventFilter(obj: 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(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1774 nextMonth->setSizePolicy(hor: QSizePolicy::Fixed, ver: 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(hor: QSizePolicy::Fixed, ver: 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(locale: q->locale(), month: i));
1787 QAction *act = monthMenu->addAction(text: monthName);
1788 act->setData(i);
1789 monthToAction[i] = act;
1790 }
1791 monthButton->setMenu(monthMenu);
1792 yearButton = new QCalToolButton(navBarBackground);
1793 yearButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: 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(cal: m_model->m_calendar));
1803 yearEdit->setMaximum(m_model->m_maximumDate.year(cal: 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(index: headerLayout->count());
1812 headerLayout->addWidget(monthButton);
1813 headerLayout->addItem(spaceHolder);
1814 headerLayout->addWidget(yearButton);
1815 headerLayout->insertStretch(index: 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(year: m_model->m_date.year(cal: m_model->m_calendar), month: m_model->m_date.month(cal: m_model->m_calendar));
1834}
1835
1836void QCalendarWidgetPrivate::updateButtonIcons()
1837{
1838 Q_Q(QCalendarWidget);
1839 prevMonth->setIcon(q->style()->standardIcon(standardIcon: q->isRightToLeft() ? QStyle::SP_ArrowRight : QStyle::SP_ArrowLeft, option: nullptr, widget: q));
1840 nextMonth->setIcon(q->style()->standardIcon(standardIcon: q->isRightToLeft() ? QStyle::SP_ArrowLeft : QStyle::SP_ArrowRight, option: nullptr, widget: q));
1841}
1842
1843void QCalendarWidgetPrivate::updateMonthMenu()
1844{
1845 int maxMonths = m_model->m_calendar.monthsInYear(year: 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(locale: q->locale(), month: 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(year: newDate.year(cal), month: newDate.month(cal));
1893 int row = -1, col = -1;
1894 m_model->cellForDate(date: newDate, row: &row, column: &col);
1895 if (row != -1 && col != -1)
1896 {
1897 m_view->selectionModel()->setCurrentIndex(index: m_model->index(row, column: col),
1898 command: 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(months: act->data().toInt() - currentDate.month(cal: m_model->m_calendar), cal: m_model->m_calendar);
1907 updateCurrentPage(date: newDate);
1908}
1909
1910QDate QCalendarWidgetPrivate::getCurrentDate()
1911{
1912 QModelIndex index = m_view->currentIndex();
1913 return m_model->dateForCell(row: index.row(), column: index.column());
1914}
1915
1916void QCalendarWidgetPrivate::_q_prevMonthClicked()
1917{
1918 QDate currentDate = getCurrentDate().addMonths(months: -1, cal: m_model->m_calendar);
1919 updateCurrentPage(date: currentDate);
1920}
1921
1922void QCalendarWidgetPrivate::_q_nextMonthClicked()
1923{
1924 QDate currentDate = getCurrentDate().addMonths(months: 1, cal: m_model->m_calendar);
1925 updateCurrentPage(date: currentDate);
1926}
1927
1928void QCalendarWidgetPrivate::_q_yearEditingFinished()
1929{
1930 Q_Q(QCalendarWidget);
1931 yearEdit->hide();
1932 q->setFocusPolicy(oldFocusPolicy);
1933 qApp->removeEventFilter(obj: q);
1934 spaceHolder->changeSize(w: 0, h: 0);
1935 yearButton->show();
1936 QDate currentDate = getCurrentDate();
1937 int newYear = q->locale().toInt(s: yearEdit->text());
1938 currentDate = currentDate.addYears(years: newYear - currentDate.year(cal: m_model->m_calendar), cal: m_model->m_calendar);
1939 yearButton->setText(q->locale().toString(date: currentDate, formatStr: u"yyyy", cal: m_model->m_calendar));
1940 updateCurrentPage(date: currentDate);
1941}
1942
1943void QCalendarWidgetPrivate::_q_yearClicked()
1944{
1945 Q_Q(QCalendarWidget);
1946 //show the spinbox on top of the button
1947 yearEdit->setGeometry(ax: yearButton->x(), ay: yearButton->y(),
1948 aw: yearEdit->sizeHint().width(), ah: yearButton->height());
1949 spaceHolder->changeSize(w: yearButton->width(), h: 0);
1950 yearButton->hide();
1951 oldFocusPolicy = q->focusPolicy();
1952 q->setFocusPolicy(Qt::NoFocus);
1953 yearEdit->show();
1954 qApp->installEventFilter(filterObj: 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(locale: q->locale(), month: 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(date: currentDate, row: &row, column: &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(index: idx, command: 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, changeMonth: 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(year: newDate.year(cal: m_model->m_calendar), month: newDate.month(cal: 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(date: 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(day: Qt::Saturday, v: fmt);
2140 d->m_model->m_dayFormats.insert(day: Qt::Sunday, v: 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(widget: 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(hor: QSizePolicy::Preferred, ver: QSizePolicy::Preferred);
2161
2162 connect(sender: d->m_view, SIGNAL(showDate(QDate)),
2163 receiver: this, SLOT(_q_slotShowDate(QDate)));
2164 connect(sender: d->m_view, SIGNAL(changeDate(QDate,bool)),
2165 receiver: this, SLOT(_q_slotChangeDate(QDate,bool)));
2166 connect(sender: d->m_view, SIGNAL(clicked(QDate)),
2167 receiver: this, SIGNAL(clicked(QDate)));
2168 connect(sender: d->m_view, SIGNAL(editingFinished()),
2169 receiver: this, SLOT(_q_editingFinished()));
2170
2171 connect(sender: d->prevMonth, SIGNAL(clicked(bool)),
2172 receiver: this, SLOT(_q_prevMonthClicked()));
2173 connect(sender: d->nextMonth, SIGNAL(clicked(bool)),
2174 receiver: this, SLOT(_q_nextMonthClicked()));
2175 connect(sender: d->yearButton, SIGNAL(clicked(bool)),
2176 receiver: this, SLOT(_q_yearClicked()));
2177 connect(sender: d->monthMenu, SIGNAL(triggered(QAction*)),
2178 receiver: this, SLOT(_q_monthChanged(QAction*)));
2179 connect(sender: d->yearEdit, SIGNAL(editingFinished()),
2180 receiver: 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(w: this);
2226 const int marginH = (style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &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(row: 0, col: i).font());
2233 w = qMax(a: w, b: fm.horizontalAdvance(d->m_model->dayName(day: d->m_model->dayOfWeekForColumn(column: i))) + marginH);
2234 h = qMax(a: h, b: 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(row: i, col: 0).font());
2243 for (int j = 1; j < end; j++)
2244 w = qMax(a: w, b: fm.horizontalAdvance(QString::number(j)) + marginH);
2245 h = qMax(a: h, b: fm.height());
2246 }
2247 }
2248
2249 QFontMetrics fm(d->m_model->formatForCell(row: 1, col: 1).font());
2250 for (int i = 1; i <= end; i++) {
2251 w = qMax(a: w, b: fm.horizontalAdvance(QString::number(i)) + marginH);
2252 h = qMax(a: h, b: 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(a: h, b: d->m_view->verticalHeader()->minimumSectionSize());
2264 w = qMax(a: w, b: 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: locale(), month: i);
2279 monthW = qMax(a: monthW, b: fm.boundingRect(text: monthName).width());
2280 }
2281 const int buttonDecoMargin = d->monthButton->sizeHint().width() - fm.boundingRect(text: d->monthButton->text()).width();
2282 headerW += monthW + buttonDecoMargin;
2283
2284 fm = d->yearButton->fontMetrics();
2285 headerW += fm.boundingRect(text: QLatin1String("5555")).width() + buttonDecoMargin;
2286
2287 headerSize = QSize(headerW, headerH);
2288 }
2289 w *= cols;
2290 w = qMax(a: headerSize.width(), b: w);
2291 h = (h * rows) + headerSize.height();
2292 QMargins cm = contentsMargins();
2293 w += cm.left() + cm.right();
2294 h += cm.top() + cm.bottom();
2295 d->cachedSizeHint = QSize(w, h);
2296 return d->cachedSizeHint;
2297}
2298
2299/*!
2300 Paints the cell specified by the given \a date, using the given \a painter and \a rect.
2301*/
2302
2303void QCalendarWidget::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const
2304{
2305 Q_D(const QCalendarWidget);
2306 d->m_delegate->paintCell(painter, rect, date);
2307}
2308
2309/*!
2310 \property QCalendarWidget::selectedDate
2311 \brief the currently selected date.
2312
2313 The selected date must be within the date range specified by the
2314 minimumDate and maximumDate properties. By default, the selected
2315 date is the current date.
2316
2317 \sa setDateRange()
2318*/
2319
2320QDate QCalendarWidget::selectedDate() const
2321{
2322 Q_D(const QCalendarWidget);
2323 return d->m_model->m_date;
2324}
2325
2326void QCalendarWidget::setSelectedDate(const QDate &date)
2327{
2328 Q_D(QCalendarWidget);
2329 if (d->m_model->m_date == date && date == d->getCurrentDate())
2330 return;
2331
2332 if (!date.isValid())
2333 return;
2334
2335 d->m_model->setDate(date);
2336 d->update();
2337 QDate newDate = d->m_model->m_date;
2338 QCalendar cal = d->m_model->m_calendar;
2339 d->showMonth(year: newDate.year(cal), month: newDate.month(cal));
2340 emit selectionChanged();
2341}
2342
2343/*!
2344 Returns the year of the currently displayed month. Months are
2345 numbered from 1 to 12.
2346
2347 \sa monthShown(), setCurrentPage()
2348*/
2349
2350int QCalendarWidget::yearShown() const
2351{
2352 Q_D(const QCalendarWidget);
2353 return d->m_model->m_shownYear;
2354}
2355
2356/*!
2357 Returns the currently displayed month. Months are numbered from 1 to
2358 12.
2359
2360 \sa yearShown(), setCurrentPage()
2361*/
2362
2363int QCalendarWidget::monthShown() const
2364{
2365 Q_D(const QCalendarWidget);
2366 return d->m_model->m_shownMonth;
2367}
2368
2369/*!
2370 Displays the given \a month of the given \a year without changing
2371 the selected date. Use the setSelectedDate() function to alter the
2372 selected date.
2373
2374 The currently displayed month and year can be retrieved using the
2375 monthShown() and yearShown() functions respectively.
2376
2377 \sa yearShown(), monthShown(), showPreviousMonth(), showNextMonth(),
2378 showPreviousYear(), showNextYear()
2379*/
2380
2381void QCalendarWidget::setCurrentPage(int year, int month)
2382{
2383 Q_D(QCalendarWidget);
2384 QDate currentDate = d->getCurrentDate();
2385 QCalendar cal = d->m_model->m_calendar;
2386 int day = currentDate.day(cal);
2387 int daysInMonths = cal.daysInMonth(month, year);
2388 if (day > daysInMonths)
2389 day = daysInMonths;
2390
2391 d->showMonth(year, month);
2392
2393 QDate newDate(year, month, day, d->m_model->m_calendar);
2394 int row = -1, col = -1;
2395 d->m_model->cellForDate(date: newDate, row: &row, column: &col);
2396 if (row != -1 && col != -1) {
2397 d->m_view->selectionModel()->setCurrentIndex(index: d->m_model->index(row, column: col),
2398 command: QItemSelectionModel::NoUpdate);
2399 }
2400}
2401
2402/*!
2403 Shows the next month relative to the currently displayed
2404 month. Note that the selected date is not changed.
2405
2406 \sa showPreviousMonth(), setCurrentPage(), setSelectedDate()
2407*/
2408
2409void QCalendarWidget::showNextMonth()
2410{
2411 Q_D(const QCalendarWidget);
2412 int year = yearShown();
2413 int month = monthShown();
2414 if (month == d->m_model->m_calendar.maximumMonthsInYear()) {
2415 ++year;
2416 month = 1;
2417 } else {
2418 ++month;
2419 }
2420 setCurrentPage(year, month);
2421}
2422
2423/*!
2424 Shows the previous month relative to the currently displayed
2425 month. Note that the selected date is not changed.
2426
2427 \sa showNextMonth(), setCurrentPage(), setSelectedDate()
2428*/
2429
2430void QCalendarWidget::showPreviousMonth()
2431{
2432 Q_D(const QCalendarWidget);
2433
2434 int year = yearShown();
2435 int month = monthShown();
2436 if (month == 1) {
2437 --year;
2438 month = d->m_model->m_calendar.maximumMonthsInYear();
2439 } else {
2440 --month;
2441 }
2442 setCurrentPage(year, month);
2443}
2444
2445/*!
2446 Shows the currently displayed month in the \e next year relative
2447 to the currently displayed year. Note that the selected date is
2448 not changed.
2449
2450 \sa showPreviousYear(), setCurrentPage(), setSelectedDate()
2451*/
2452
2453void QCalendarWidget::showNextYear()
2454{
2455 int year = yearShown();
2456 int month = monthShown();
2457 ++year;
2458 setCurrentPage(year, month);
2459}
2460
2461/*!
2462 Shows the currently displayed month in the \e previous year
2463 relative to the currently displayed year. Note that the selected
2464 date is not changed.
2465
2466 \sa showNextYear(), setCurrentPage(), setSelectedDate()
2467*/
2468
2469void QCalendarWidget::showPreviousYear()
2470{
2471 int year = yearShown();
2472 int month = monthShown();
2473 --year;
2474 setCurrentPage(year, month);
2475}
2476
2477/*!
2478 Shows the month of the selected date.
2479
2480 \sa selectedDate(), setCurrentPage()
2481*/
2482void QCalendarWidget::showSelectedDate()
2483{
2484 Q_D(const QCalendarWidget);
2485
2486 QDate currentDate = selectedDate();
2487 setCurrentPage(year: currentDate.year(cal: d->m_model->m_calendar), month: currentDate.month(cal: d->m_model->m_calendar));
2488}
2489
2490/*!
2491 Shows the month of the today's date.
2492
2493 \sa selectedDate(), setCurrentPage()
2494*/
2495void QCalendarWidget::showToday()
2496{
2497 Q_D(const QCalendarWidget);
2498
2499 QDate currentDate = QDate::currentDate();
2500 setCurrentPage(year: currentDate.year(cal: d->m_model->m_calendar), month: currentDate.month(cal: d->m_model->m_calendar));
2501}
2502
2503/*!
2504 \property QCalendarWidget::minimumDate
2505 \brief the minimum date of the currently specified date range.
2506
2507 The user will not be able to select a date that is before the
2508 currently set minimum date.
2509
2510 \table
2511 \row
2512 \li \image qcalendarwidget-minimum.png
2513 \row
2514 \li
2515 \snippet code/src_gui_widgets_qcalendarwidget.cpp 1
2516 \endtable
2517
2518 By default, the minimum date is the earliest date that the QDate
2519 class can handle.
2520
2521 When setting a minimum date, the maximumDate and selectedDate
2522 properties are adjusted if the selection range becomes invalid. If
2523 the provided date is not a valid QDate object, the
2524 setMinimumDate() function does nothing.
2525
2526 \sa setDateRange()
2527*/
2528
2529QDate QCalendarWidget::minimumDate() const
2530{
2531 Q_D(const QCalendarWidget);
2532 return d->m_model->m_minimumDate;
2533}
2534
2535void QCalendarWidget::setMinimumDate(const QDate &date)
2536{
2537 Q_D(QCalendarWidget);
2538 if (!date.isValid() || d->m_model->m_minimumDate == date)
2539 return;
2540
2541 QDate oldDate = d->m_model->m_date;
2542 d->m_model->setMinimumDate(date);
2543 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2544 d->updateMonthMenu();
2545 QDate newDate = d->m_model->m_date;
2546 if (oldDate != newDate) {
2547 d->update();
2548 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2549 d->m_navigator->setDate(newDate);
2550 emit selectionChanged();
2551 }
2552}
2553
2554/*!
2555 \property QCalendarWidget::maximumDate
2556 \brief the maximum date of the currently specified date range.
2557
2558 The user will not be able to select a date which is after the
2559 currently set maximum date.
2560
2561 \table
2562 \row
2563 \li \image qcalendarwidget-maximum.png
2564 \row
2565 \li
2566 \snippet code/src_gui_widgets_qcalendarwidget.cpp 2
2567 \endtable
2568
2569 By default, the maximum date is the last day the QDate class can
2570 handle.
2571
2572 When setting a maximum date, the minimumDate and selectedDate
2573 properties are adjusted if the selection range becomes invalid. If
2574 the provided date is not a valid QDate object, the
2575 setMaximumDate() function does nothing.
2576
2577 \sa setDateRange()
2578*/
2579
2580QDate QCalendarWidget::maximumDate() const
2581{
2582 Q_D(const QCalendarWidget);
2583 return d->m_model->m_maximumDate;
2584}
2585
2586void QCalendarWidget::setMaximumDate(const QDate &date)
2587{
2588 Q_D(QCalendarWidget);
2589 if (!date.isValid() || d->m_model->m_maximumDate == date)
2590 return;
2591
2592 QDate oldDate = d->m_model->m_date;
2593 d->m_model->setMaximumDate(date);
2594 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2595 d->updateMonthMenu();
2596 QDate newDate = d->m_model->m_date;
2597 if (oldDate != newDate) {
2598 d->update();
2599 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2600 d->m_navigator->setDate(newDate);
2601 emit selectionChanged();
2602 }
2603}
2604
2605/*!
2606 Defines a date range by setting the minimumDate and maximumDate
2607 properties.
2608
2609 The date range restricts the user selection, i.e. the user can
2610 only select dates within the specified date range. Note that
2611
2612 \snippet code/src_gui_widgets_qcalendarwidget.cpp 3
2613
2614 is analogous to
2615
2616 \snippet code/src_gui_widgets_qcalendarwidget.cpp 4
2617
2618 If either the \a min or \a max parameters are not valid QDate
2619 objects, this function does nothing.
2620
2621 \sa setMinimumDate(), setMaximumDate()
2622*/
2623
2624void QCalendarWidget::setDateRange(const QDate &min, const QDate &max)
2625{
2626 Q_D(QCalendarWidget);
2627 if (d->m_model->m_minimumDate == min && d->m_model->m_maximumDate == max)
2628 return;
2629 if (!min.isValid() || !max.isValid())
2630 return;
2631
2632 QDate oldDate = d->m_model->m_date;
2633 d->m_model->setRange(min, max);
2634 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2635 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2636 d->updateMonthMenu();
2637 QDate newDate = d->m_model->m_date;
2638 if (oldDate != newDate) {
2639 d->update();
2640 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2641 d->m_navigator->setDate(newDate);
2642 emit selectionChanged();
2643 }
2644}
2645
2646
2647/*! \enum QCalendarWidget::HorizontalHeaderFormat
2648
2649 This enum type defines the various formats the horizontal header can display.
2650
2651 \value SingleLetterDayNames The header displays a single letter abbreviation for day names (e.g. M for Monday).
2652 \value ShortDayNames The header displays a short abbreviation for day names (e.g. Mon for Monday).
2653 \value LongDayNames The header displays complete day names (e.g. Monday).
2654 \value NoHorizontalHeader The header is hidden.
2655
2656 \sa horizontalHeaderFormat(), VerticalHeaderFormat
2657*/
2658
2659/*!
2660 \property QCalendarWidget::horizontalHeaderFormat
2661 \brief the format of the horizontal header.
2662
2663 The default value is QCalendarWidget::ShortDayNames.
2664*/
2665
2666void QCalendarWidget::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format)
2667{
2668 Q_D(QCalendarWidget);
2669 if (d->m_model->m_horizontalHeaderFormat == format)
2670 return;
2671
2672 d->m_model->setHorizontalHeaderFormat(format);
2673 d->cachedSizeHint = QSize();
2674 d->m_view->viewport()->update();
2675 d->m_view->updateGeometry();
2676}
2677
2678QCalendarWidget::HorizontalHeaderFormat QCalendarWidget::horizontalHeaderFormat() const
2679{
2680 Q_D(const QCalendarWidget);
2681 return d->m_model->m_horizontalHeaderFormat;
2682}
2683
2684
2685/*!
2686 \enum QCalendarWidget::VerticalHeaderFormat
2687
2688 This enum type defines the various formats the vertical header can display.
2689
2690 \value ISOWeekNumbers The header displays ISO week numbers as described by \l QDate::weekNumber().
2691 \value NoVerticalHeader The header is hidden.
2692
2693 \sa verticalHeaderFormat(), HorizontalHeaderFormat
2694*/
2695
2696/*!
2697 \property QCalendarWidget::verticalHeaderFormat
2698 \brief the format of the vertical header.
2699
2700 The default value is QCalendarWidget::ISOWeekNumber.
2701*/
2702
2703QCalendarWidget::VerticalHeaderFormat QCalendarWidget::verticalHeaderFormat() const
2704{
2705 Q_D(const QCalendarWidget);
2706 bool shown = d->m_model->weekNumbersShown();
2707 if (shown)
2708 return QCalendarWidget::ISOWeekNumbers;
2709 return QCalendarWidget::NoVerticalHeader;
2710}
2711
2712void QCalendarWidget::setVerticalHeaderFormat(QCalendarWidget::VerticalHeaderFormat format)
2713{
2714 Q_D(QCalendarWidget);
2715 bool show = false;
2716 if (format == QCalendarWidget::ISOWeekNumbers)
2717 show = true;
2718 if (d->m_model->weekNumbersShown() == show)
2719 return;
2720 d->m_model->setWeekNumbersShown(show);
2721 d->cachedSizeHint = QSize();
2722 d->m_view->viewport()->update();
2723 d->m_view->updateGeometry();
2724}
2725
2726/*!
2727 \property QCalendarWidget::gridVisible
2728 \brief whether the table grid is displayed.
2729
2730 \table
2731 \row
2732 \li \inlineimage qcalendarwidget-grid.png
2733 \row
2734 \li
2735 \snippet code/src_gui_widgets_qcalendarwidget.cpp 5
2736 \endtable
2737
2738 The default value is false.
2739*/
2740
2741bool QCalendarWidget::isGridVisible() const
2742{
2743 Q_D(const QCalendarWidget);
2744 return d->m_view->showGrid();
2745}
2746
2747QCalendar QCalendarWidget::calendar() const
2748{
2749 Q_D(const QCalendarWidget);
2750 return d->m_model->m_calendar;
2751}
2752
2753void QCalendarWidget::setCalendar(QCalendar c)
2754{
2755 Q_D(QCalendarWidget);
2756 d->m_model->setCalendar(c);
2757 d->updateMonthMenuNames();
2758 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2759 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2760 d->updateNavigationBar();
2761}
2762
2763void QCalendarWidget::setGridVisible(bool show)
2764{
2765 Q_D(QCalendarWidget);
2766 d->m_view->setShowGrid(show);
2767 d->cachedSizeHint = QSize();
2768 d->m_view->viewport()->update();
2769 d->m_view->updateGeometry();
2770}
2771
2772/*!
2773 \property QCalendarWidget::selectionMode
2774 \brief the type of selection the user can make in the calendar
2775
2776 When this property is set to SingleSelection, the user can select a date
2777 within the minimum and maximum allowed dates, using either the mouse or
2778 the keyboard.
2779
2780 When the property is set to NoSelection, the user will be unable to select
2781 dates, but they can still be selected programmatically. Note that the date
2782 that is selected when the property is set to NoSelection will still be
2783 the selected date of the calendar.
2784
2785 The default value is SingleSelection.
2786*/
2787
2788QCalendarWidget::SelectionMode QCalendarWidget::selectionMode() const
2789{
2790 Q_D(const QCalendarWidget);
2791 return d->m_view->readOnly ? QCalendarWidget::NoSelection : QCalendarWidget::SingleSelection;
2792}
2793
2794void QCalendarWidget::setSelectionMode(SelectionMode mode)
2795{
2796 Q_D(QCalendarWidget);
2797 d->m_view->readOnly = (mode == QCalendarWidget::NoSelection);
2798 d->setNavigatorEnabled(isDateEditEnabled() && (selectionMode() != QCalendarWidget::NoSelection));
2799 d->update();
2800}
2801
2802/*!
2803 \property QCalendarWidget::firstDayOfWeek
2804 \brief a value identifying the day displayed in the first column.
2805
2806 By default, the day displayed in the first column
2807 is the first day of the week for the calendar's locale.
2808*/
2809
2810void QCalendarWidget::setFirstDayOfWeek(Qt::DayOfWeek dayOfWeek)
2811{
2812 Q_D(QCalendarWidget);
2813 if ((Qt::DayOfWeek)d->m_model->firstColumnDay() == dayOfWeek)
2814 return;
2815
2816 d->m_model->setFirstColumnDay(dayOfWeek);
2817 d->update();
2818}
2819
2820Qt::DayOfWeek QCalendarWidget::firstDayOfWeek() const
2821{
2822 Q_D(const QCalendarWidget);
2823 return (Qt::DayOfWeek)d->m_model->firstColumnDay();
2824}
2825
2826/*!
2827 Returns the text char format for rendering the header.
2828*/
2829QTextCharFormat QCalendarWidget::headerTextFormat() const
2830{
2831 Q_D(const QCalendarWidget);
2832 return d->m_model->m_headerFormat;
2833}
2834
2835/*!
2836 Sets the text char format for rendering the header to \a format.
2837 If you also set a weekday text format, this format's foreground and
2838 background color will take precedence over the header's format.
2839 The other formatting information will still be decided by
2840 the header's format.
2841*/
2842void QCalendarWidget::setHeaderTextFormat(const QTextCharFormat &format)
2843{
2844 Q_D(QCalendarWidget);
2845 d->m_model->m_headerFormat = format;
2846 d->cachedSizeHint = QSize();
2847 d->m_view->viewport()->update();
2848 d->m_view->updateGeometry();
2849}
2850
2851/*!
2852 Returns the text char format for rendering of day in the week \a dayOfWeek.
2853
2854 \sa headerTextFormat()
2855*/
2856QTextCharFormat QCalendarWidget::weekdayTextFormat(Qt::DayOfWeek dayOfWeek) const
2857{
2858 Q_D(const QCalendarWidget);
2859 return d->m_model->m_dayFormats.value(day: dayOfWeek);
2860}
2861
2862/*!
2863 Sets the text char format for rendering of day in the week \a dayOfWeek to \a format.
2864 The format will take precedence over the header format in case of foreground
2865 and background color. Other text formatting information is taken from the headers format.
2866
2867 \sa setHeaderTextFormat()
2868*/
2869void QCalendarWidget::setWeekdayTextFormat(Qt::DayOfWeek dayOfWeek, const QTextCharFormat &format)
2870{
2871 Q_D(QCalendarWidget);
2872 d->m_model->m_dayFormats[dayOfWeek] = format;
2873 d->cachedSizeHint = QSize();
2874 d->m_view->viewport()->update();
2875 d->m_view->updateGeometry();
2876}
2877
2878/*!
2879 Returns a QMap from QDate to QTextCharFormat showing all dates
2880 that use a special format that alters their rendering.
2881*/
2882QMap<QDate, QTextCharFormat> QCalendarWidget::dateTextFormat() const
2883{
2884 Q_D(const QCalendarWidget);
2885 return d->m_model->m_dateFormats;
2886}
2887
2888/*!
2889 Returns a QTextCharFormat for \a date. The char format can be be
2890 empty if the date is not renderd specially.
2891*/
2892QTextCharFormat QCalendarWidget::dateTextFormat(const QDate &date) const
2893{
2894 Q_D(const QCalendarWidget);
2895 return d->m_model->m_dateFormats.value(akey: date);
2896}
2897
2898/*!
2899 Sets the format used to render the given \a date to that specified by \a format.
2900
2901 If \a date is null, all date formats are cleared.
2902*/
2903void QCalendarWidget::setDateTextFormat(const QDate &date, const QTextCharFormat &format)
2904{
2905 Q_D(QCalendarWidget);
2906 if (date.isNull())
2907 d->m_model->m_dateFormats.clear();
2908 else
2909 d->m_model->m_dateFormats[date] = format;
2910 d->m_view->viewport()->update();
2911 d->m_view->updateGeometry();
2912}
2913
2914/*!
2915 \property QCalendarWidget::dateEditEnabled
2916 \brief whether the date edit popup is enabled
2917 \since 4.3
2918
2919 If this property is enabled, pressing a non-modifier key will cause a
2920 date edit to popup if the calendar widget has focus, allowing the user
2921 to specify a date in the form specified by the current locale.
2922
2923 By default, this property is enabled.
2924
2925 The date edit is simpler in appearance than QDateEdit, but allows the
2926 user to navigate between fields using the left and right cursor keys,
2927 increment and decrement individual fields using the up and down cursor
2928 keys, and enter values directly using the number keys.
2929
2930 \sa QCalendarWidget::dateEditAcceptDelay
2931*/
2932bool QCalendarWidget::isDateEditEnabled() const
2933{
2934 Q_D(const QCalendarWidget);
2935 return d->m_dateEditEnabled;
2936}
2937
2938void QCalendarWidget::setDateEditEnabled(bool enable)
2939{
2940 Q_D(QCalendarWidget);
2941 if (isDateEditEnabled() == enable)
2942 return;
2943
2944 d->m_dateEditEnabled = enable;
2945
2946 d->setNavigatorEnabled(enable && (selectionMode() != QCalendarWidget::NoSelection));
2947}
2948
2949/*!
2950 \property QCalendarWidget::dateEditAcceptDelay
2951 \brief the time an inactive date edit is shown before its contents are accepted
2952 \since 4.3
2953
2954 If the calendar widget's \l{dateEditEnabled}{date edit is enabled}, this
2955 property specifies the amount of time (in millseconds) that the date edit
2956 remains open after the most recent user input. Once this time has elapsed,
2957 the date specified in the date edit is accepted and the popup is closed.
2958
2959 By default, the delay is defined to be 1500 milliseconds (1.5 seconds).
2960*/
2961int QCalendarWidget::dateEditAcceptDelay() const
2962{
2963 Q_D(const QCalendarWidget);
2964 return d->m_navigator->dateEditAcceptDelay();
2965}
2966
2967void QCalendarWidget::setDateEditAcceptDelay(int delay)
2968{
2969 Q_D(QCalendarWidget);
2970 d->m_navigator->setDateEditAcceptDelay(delay);
2971}
2972
2973/*!
2974 \since 4.4
2975
2976 Updates the cell specified by the given \a date unless updates
2977 are disabled or the cell is hidden.
2978
2979 \sa updateCells(), yearShown(), monthShown()
2980*/
2981void QCalendarWidget::updateCell(const QDate &date)
2982{
2983 if (Q_UNLIKELY(!date.isValid())) {
2984 qWarning(msg: "QCalendarWidget::updateCell: Invalid date");
2985 return;
2986 }
2987
2988 if (!isVisible())
2989 return;
2990
2991 Q_D(QCalendarWidget);
2992 int row, column;
2993 d->m_model->cellForDate(date, row: &row, column: &column);
2994 if (row == -1 || column == -1)
2995 return;
2996
2997 QModelIndex modelIndex = d->m_model->index(row, column);
2998 if (!modelIndex.isValid())
2999 return;
3000
3001 d->m_view->viewport()->update(d->m_view->visualRect(index: modelIndex));
3002}
3003
3004/*!
3005 \since 4.4
3006
3007 Updates all visible cells unless updates are disabled.
3008
3009 \sa updateCell()
3010*/
3011void QCalendarWidget::updateCells()
3012{
3013 Q_D(QCalendarWidget);
3014 if (isVisible())
3015 d->m_view->viewport()->update();
3016}
3017
3018/*!
3019 \fn void QCalendarWidget::selectionChanged()
3020
3021 This signal is emitted when the currently selected date is
3022 changed.
3023
3024 The currently selected date can be changed by the user using the
3025 mouse or keyboard, or by the programmer using setSelectedDate().
3026
3027 \sa selectedDate()
3028*/
3029
3030/*!
3031 \fn void QCalendarWidget::currentPageChanged(int year, int month)
3032
3033 This signal is emitted when the currently shown month is changed.
3034 The new \a year and \a month are passed as parameters.
3035
3036 \sa setCurrentPage()
3037*/
3038
3039/*!
3040 \fn void QCalendarWidget::activated(const QDate &date)
3041
3042 This signal is emitted whenever the user presses the Return or
3043 Enter key or double-clicks a \a date in the calendar
3044 widget.
3045*/
3046
3047/*!
3048 \fn void QCalendarWidget::clicked(const QDate &date)
3049
3050 This signal is emitted when a mouse button is clicked. The date
3051 the mouse was clicked on is specified by \a date. The signal is
3052 only emitted when clicked on a valid date, e.g., dates are not
3053 outside the minimumDate() and maximumDate(). If the selection mode
3054 is NoSelection, this signal will not be emitted.
3055
3056*/
3057
3058/*!
3059 \property QCalendarWidget::navigationBarVisible
3060 \brief whether the navigation bar is shown or not
3061
3062 \since 4.3
3063
3064 When this property is \c true (the default), the next month,
3065 previous month, month selection, year selection controls are
3066 shown on top.
3067
3068 When the property is set to false, these controls are hidden.
3069*/
3070
3071bool QCalendarWidget::isNavigationBarVisible() const
3072{
3073 Q_D(const QCalendarWidget);
3074 return d->navBarVisible;
3075}
3076
3077
3078void QCalendarWidget::setNavigationBarVisible(bool visible)
3079{
3080 Q_D(QCalendarWidget);
3081 d->navBarVisible = visible;
3082 d->cachedSizeHint = QSize();
3083 d->navBarBackground->setVisible(visible);
3084 updateGeometry();
3085}
3086
3087/*!
3088 \reimp
3089*/
3090bool QCalendarWidget::event(QEvent *event)
3091{
3092 Q_D(QCalendarWidget);
3093 switch (event->type()) {
3094 case QEvent::LayoutDirectionChange:
3095 d->updateButtonIcons();
3096 break;
3097 case QEvent::LocaleChange:
3098 d->m_model->setFirstColumnDay(locale().firstDayOfWeek());
3099 d->cachedSizeHint = QSize();
3100 d->updateMonthMenuNames();
3101 d->updateNavigationBar();
3102 d->m_view->updateGeometry();
3103 // TODO: fix this known bug of calendaring API:
3104 // Changing locale before calendar works, but reverse order causes
3105 // invalid month names (in C Locale apparently).
3106 break;
3107 case QEvent::FontChange:
3108 case QEvent::ApplicationFontChange:
3109 d->cachedSizeHint = QSize();
3110 d->m_view->updateGeometry();
3111 break;
3112 case QEvent::StyleChange:
3113 d->cachedSizeHint = QSize();
3114 d->m_view->updateGeometry();
3115 default:
3116 break;
3117 }
3118 return QWidget::event(event);
3119}
3120
3121/*!
3122 \reimp
3123*/
3124bool QCalendarWidget::eventFilter(QObject *watched, QEvent *event)
3125{
3126 Q_D(QCalendarWidget);
3127 if (event->type() == QEvent::MouseButtonPress && d->yearEdit->hasFocus()) {
3128 // We can get filtered press events that were intended for Qt Virtual Keyboard's
3129 // input panel (QQuickView), so we have to make sure that the window is indeed a QWidget - no static_cast.
3130 // In addition, as we have a event filter on the whole application we first make sure that the top level widget
3131 // of both this and the watched widget are the same to decide if we should finish the year edition.
3132 QWidget *tlw = window();
3133 QWidget *widget = qobject_cast<QWidget *>(o: watched);
3134 if (!widget || widget->window() != tlw)
3135 return QWidget::eventFilter(watched, event);
3136
3137 QPoint mousePos = widget->mapTo(tlw, static_cast<QMouseEvent *>(event)->pos());
3138 QRect geom = QRect(d->yearEdit->mapTo(tlw, QPoint(0, 0)), d->yearEdit->size());
3139 if (!geom.contains(p: mousePos)) {
3140 event->accept();
3141 d->_q_yearEditingFinished();
3142 setFocus();
3143 return true;
3144 }
3145 }
3146 return QWidget::eventFilter(watched, event);
3147}
3148
3149/*!
3150 \reimp
3151*/
3152void QCalendarWidget::mousePressEvent(QMouseEvent *event)
3153{
3154 setAttribute(Qt::WA_NoMouseReplay);
3155 QWidget::mousePressEvent(event);
3156 setFocus();
3157}
3158
3159/*!
3160 \reimp
3161*/
3162void QCalendarWidget::resizeEvent(QResizeEvent * event)
3163{
3164 Q_D(QCalendarWidget);
3165
3166 // XXX Should really use a QWidgetStack for yearEdit and yearButton,
3167 // XXX here we hide the year edit when the layout is likely to break
3168 // XXX the manual positioning of the yearEdit over the yearButton.
3169 if(d->yearEdit->isVisible() && event->size().width() != event->oldSize().width())
3170 d->_q_yearEditingFinished();
3171
3172 QWidget::resizeEvent(event);
3173}
3174
3175/*!
3176 \reimp
3177*/
3178void QCalendarWidget::keyPressEvent(QKeyEvent * event)
3179{
3180#if QT_CONFIG(shortcut)
3181 Q_D(QCalendarWidget);
3182 if (d->yearEdit->isVisible()&& event->matches(key: QKeySequence::Cancel)) {
3183 d->yearEdit->setValue(yearShown());
3184 d->_q_yearEditingFinished();
3185 return;
3186 }
3187#endif
3188 QWidget::keyPressEvent(event);
3189}
3190
3191QT_END_NAMESPACE
3192
3193#include "qcalendarwidget.moc"
3194#include "moc_qcalendarwidget.cpp"
3195

source code of qtbase/src/widgets/widgets/qcalendarwidget.cpp