1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qcalendarwidget.h"
5
6#include <qabstractitemmodel.h>
7#include <qstyleditemdelegate.h>
8#include <qdatetime.h>
9#include <qtableview.h>
10#include <qlayout.h>
11#include <qevent.h>
12#include <qtextformat.h>
13#include <qheaderview.h>
14#include <private/qwidget_p.h>
15#include <qpushbutton.h>
16#include <qtoolbutton.h>
17#include <qlabel.h>
18#include <qspinbox.h>
19#include <qmenu.h>
20#include <qapplication.h>
21#include <private/qapplication_p.h>
22#include <qbasictimer.h>
23#include <qstylepainter.h>
24#include <qcalendar.h>
25
26#include <vector>
27
28QT_BEGIN_NAMESPACE
29
30using namespace Qt::StringLiterals;
31
32enum {
33 RowCount = 6,
34 ColumnCount = 7,
35 HeaderColumn = 0,
36 HeaderRow = 0,
37 MinimumDayOffset = 1
38};
39
40static QString formatNumber(int number, int fieldWidth)
41{
42 return QString::number(number).rightJustified(width: fieldWidth, fill: u'0');
43}
44
45namespace QtPrivate {
46
47class QCalendarDateSectionValidator
48{
49public:
50
51 enum Section {
52 NextSection,
53 ThisSection,
54 PrevSection
55 };
56
57 QCalendarDateSectionValidator() {}
58 virtual ~QCalendarDateSectionValidator() {}
59 virtual Section handleKey(int key) = 0;
60 virtual QDate applyToDate(QDate date, QCalendar cal = QCalendar()) const = 0;
61 virtual void setDate(QDate date, QCalendar cal = QCalendar()) = 0;
62 virtual QString text() const = 0;
63 virtual QString text(QDate date, QCalendar cal, int repeat) const = 0;
64
65 QLocale m_locale;
66
67protected:
68 static QString highlightString(const QString &str, int pos);
69};
70
71QString QCalendarDateSectionValidator::highlightString(const QString &str, int pos)
72{
73 if (pos == 0)
74 return "<b>"_L1 + str + "</b>"_L1;
75 int startPos = str.size() - pos;
76 return QStringView{str}.mid(pos: 0, n: startPos) + "<b>"_L1 + QStringView{str}.mid(pos: startPos, n: pos) + "</b>"_L1;
77
78}
79
80class QCalendarDayValidator : public QCalendarDateSectionValidator
81{
82
83public:
84 QCalendarDayValidator();
85 virtual Section handleKey(int key) override;
86 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
87 virtual void setDate(QDate date, QCalendar cal) override;
88 virtual QString text() const override;
89 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
90private:
91 int m_pos;
92 int m_day;
93 int m_oldDay;
94};
95
96QCalendarDayValidator::QCalendarDayValidator()
97 : QCalendarDateSectionValidator(), m_pos(0), m_day(1), m_oldDay(1)
98{
99}
100
101QCalendarDateSectionValidator::Section QCalendarDayValidator::handleKey(int key)
102{
103 if (key == Qt::Key_Right || key == Qt::Key_Left) {
104 m_pos = 0;
105 return QCalendarDateSectionValidator::ThisSection;
106 } else if (key == Qt::Key_Up) {
107 m_pos = 0;
108 ++m_day;
109 if (m_day > 31)
110 m_day = 1;
111 return QCalendarDateSectionValidator::ThisSection;
112 } else if (key == Qt::Key_Down) {
113 m_pos = 0;
114 --m_day;
115 if (m_day < 1)
116 m_day = 31;
117 return QCalendarDateSectionValidator::ThisSection;
118 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
119 --m_pos;
120 if (m_pos < 0)
121 m_pos = 1;
122
123 if (m_pos == 0)
124 m_day = m_oldDay;
125 else
126 m_day = m_day / 10;
127 //m_day = m_oldDay / 10 * 10 + m_day / 10;
128
129 if (m_pos == 0)
130 return QCalendarDateSectionValidator::PrevSection;
131 return QCalendarDateSectionValidator::ThisSection;
132 }
133 if (key < Qt::Key_0 || key > Qt::Key_9)
134 return QCalendarDateSectionValidator::ThisSection;
135 int pressedKey = key - Qt::Key_0;
136 if (m_pos == 0)
137 m_day = pressedKey;
138 else
139 m_day = m_day % 10 * 10 + pressedKey;
140 if (m_day > 31)
141 m_day = 31;
142 ++m_pos;
143 if (m_pos > 1) {
144 m_pos = 0;
145 return QCalendarDateSectionValidator::NextSection;
146 }
147 return QCalendarDateSectionValidator::ThisSection;
148}
149
150QDate QCalendarDayValidator::applyToDate(QDate date, QCalendar cal) const
151{
152 auto parts = cal.partsFromDate(date);
153 if (!parts.isValid())
154 return QDate();
155 parts.day = qMin(a: qMax(a: 1, b: m_day), b: cal.daysInMonth(month: parts.month, year: parts.year));
156 return cal.dateFromParts(parts);
157}
158
159void QCalendarDayValidator::setDate(QDate date, QCalendar cal)
160{
161 m_day = m_oldDay = date.day(cal);
162 m_pos = 0;
163}
164
165QString QCalendarDayValidator::text() const
166{
167 return highlightString(str: formatNumber(number: m_day, fieldWidth: 2), pos: m_pos);
168}
169
170QString QCalendarDayValidator::text(QDate date, QCalendar cal, int repeat) const
171{
172 if (repeat <= 1) {
173 return QString::number(date.day(cal));
174 } else if (repeat == 2) {
175 return formatNumber(number: date.day(cal), fieldWidth: 2);
176 } else if (repeat == 3) {
177 return m_locale.dayName(date.dayOfWeek(cal), format: QLocale::ShortFormat);
178 } else /* repeat >= 4 */ {
179 return m_locale.dayName(date.dayOfWeek(cal), format: QLocale::LongFormat);
180 }
181}
182
183//////////////////////////////////
184
185class QCalendarMonthValidator : public QCalendarDateSectionValidator
186{
187
188public:
189 QCalendarMonthValidator();
190 virtual Section handleKey(int key) override;
191 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
192 virtual void setDate(QDate date, QCalendar cal) override;
193 virtual QString text() const override;
194 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
195private:
196 int m_pos;
197 int m_month;
198 int m_oldMonth;
199};
200
201QCalendarMonthValidator::QCalendarMonthValidator()
202 : QCalendarDateSectionValidator(), m_pos(0), m_month(1), m_oldMonth(1)
203{
204}
205
206QCalendarDateSectionValidator::Section QCalendarMonthValidator::handleKey(int key)
207{
208 if (key == Qt::Key_Right || key == Qt::Key_Left) {
209 m_pos = 0;
210 return QCalendarDateSectionValidator::ThisSection;
211 } else if (key == Qt::Key_Up) {
212 m_pos = 0;
213 ++m_month;
214 if (m_month > 12)
215 m_month = 1;
216 return QCalendarDateSectionValidator::ThisSection;
217 } else if (key == Qt::Key_Down) {
218 m_pos = 0;
219 --m_month;
220 if (m_month < 1)
221 m_month = 12;
222 return QCalendarDateSectionValidator::ThisSection;
223 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
224 --m_pos;
225 if (m_pos < 0)
226 m_pos = 1;
227
228 if (m_pos == 0)
229 m_month = m_oldMonth;
230 else
231 m_month = m_month / 10;
232 //m_month = m_oldMonth / 10 * 10 + m_month / 10;
233
234 if (m_pos == 0)
235 return QCalendarDateSectionValidator::PrevSection;
236 return QCalendarDateSectionValidator::ThisSection;
237 }
238 if (key < Qt::Key_0 || key > Qt::Key_9)
239 return QCalendarDateSectionValidator::ThisSection;
240 int pressedKey = key - Qt::Key_0;
241 if (m_pos == 0)
242 m_month = pressedKey;
243 else
244 m_month = m_month % 10 * 10 + pressedKey;
245 if (m_month > 12)
246 m_month = 12;
247 ++m_pos;
248 if (m_pos > 1) {
249 m_pos = 0;
250 return QCalendarDateSectionValidator::NextSection;
251 }
252 return QCalendarDateSectionValidator::ThisSection;
253}
254
255QDate QCalendarMonthValidator::applyToDate(QDate date, QCalendar cal) const
256{
257 auto parts = cal.partsFromDate(date);
258 if (!parts.isValid())
259 return QDate();
260 parts.month = qMin(a: qMax(a: 1, b: m_month), b: cal.monthsInYear(year: parts.year));
261 parts.day = qMin(a: parts.day, b: cal.daysInMonth(month: m_month, year: parts.year)); // m_month or parts.month ?
262 return cal.dateFromParts(parts);
263}
264
265void QCalendarMonthValidator::setDate(QDate date, QCalendar cal)
266{
267 m_month = m_oldMonth = date.month(cal);
268 m_pos = 0;
269}
270
271QString QCalendarMonthValidator::text() const
272{
273 return highlightString(str: formatNumber(number: m_month, fieldWidth: 2), pos: m_pos);
274}
275
276QString QCalendarMonthValidator::text(QDate date, QCalendar cal, int repeat) const
277{
278 const auto parts = cal.partsFromDate(date);
279 // Numeric forms:
280 if (repeat <= 1)
281 return QString::number(parts.month);
282 if (repeat == 2)
283 return formatNumber(number: parts.month, fieldWidth: 2);
284 // Text forms:
285 if (repeat == 3)
286 return cal.standaloneMonthName(locale: m_locale, month: parts.month, year: parts.year, format: QLocale::ShortFormat);
287 /* repeat >= 4 */
288 return cal.standaloneMonthName(locale: m_locale, month: parts.month, year: parts.year, format: QLocale::LongFormat);
289}
290
291//////////////////////////////////
292
293class QCalendarYearValidator : public QCalendarDateSectionValidator
294{
295
296public:
297 QCalendarYearValidator();
298 virtual Section handleKey(int key) override;
299 virtual QDate applyToDate(QDate date, QCalendar cal) const override;
300 virtual void setDate(QDate date, QCalendar cal) override;
301 virtual QString text() const override;
302 virtual QString text(QDate date, QCalendar cal, int repeat) const override;
303private:
304 int pow10(int n);
305 int m_pos;
306 int m_year;
307 int m_oldYear;
308};
309
310QCalendarYearValidator::QCalendarYearValidator()
311 : QCalendarDateSectionValidator(), m_pos(0), m_year(2000), m_oldYear(2000)
312{
313 // TODO: What to use (for non-Gregorian calendars) as default year?
314 // Maybe 1360 for Jalali, 1420 for Islamic, etc.
315}
316
317int QCalendarYearValidator::pow10(int n)
318{
319 int power = 1;
320 for (int i = 0; i < n; i++)
321 power *= 10;
322 return power;
323}
324
325QCalendarDateSectionValidator::Section QCalendarYearValidator::handleKey(int key)
326{
327 if (key == Qt::Key_Right || key == Qt::Key_Left) {
328 m_pos = 0;
329 return QCalendarDateSectionValidator::ThisSection;
330 } else if (key == Qt::Key_Up) {
331 m_pos = 0;
332 ++m_year;
333 return QCalendarDateSectionValidator::ThisSection;
334 } else if (key == Qt::Key_Down) {
335 m_pos = 0;
336 --m_year;
337 return QCalendarDateSectionValidator::ThisSection;
338 } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) {
339 --m_pos;
340 if (m_pos < 0)
341 m_pos = 3;
342
343 int pow = pow10(n: m_pos);
344 m_year = m_oldYear / pow * pow + m_year % (pow * 10) / 10;
345
346 if (m_pos == 0)
347 return QCalendarDateSectionValidator::PrevSection;
348 return QCalendarDateSectionValidator::ThisSection;
349 }
350 if (key < Qt::Key_0 || key > Qt::Key_9)
351 return QCalendarDateSectionValidator::ThisSection;
352 int pressedKey = key - Qt::Key_0;
353 int pow = pow10(n: m_pos);
354 m_year = m_year / (pow * 10) * (pow * 10) + m_year % pow * 10 + pressedKey;
355 ++m_pos;
356 if (m_pos > 3) {
357 m_pos = 0;
358 return QCalendarDateSectionValidator::NextSection;
359 }
360 return QCalendarDateSectionValidator::ThisSection;
361}
362
363QDate QCalendarYearValidator::applyToDate(QDate date, QCalendar cal) const
364{
365 auto parts = cal.partsFromDate(date);
366 if (!parts.isValid())
367 return QDate();
368 // This widget does not support negative years (some calendars may support)
369 parts.year = qMax(a: 1, b: m_year);
370 parts.day = qMin(a: parts.day, b: cal.daysInMonth(month: parts.month, year: parts.year));
371 return cal.dateFromParts(parts);
372}
373
374void QCalendarYearValidator::setDate(QDate date, QCalendar cal)
375{
376 m_year = m_oldYear = date.year(cal);
377 m_pos = 0;
378}
379
380QString QCalendarYearValidator::text() const
381{
382 return highlightString(str: formatNumber(number: m_year, fieldWidth: 4), pos: m_pos);
383}
384
385QString QCalendarYearValidator::text(QDate date, QCalendar cal, int repeat) const
386{
387 if (repeat < 4)
388 return formatNumber(number: date.year(cal) % 100, fieldWidth: 2);
389 return QString::number(date.year(cal));
390}
391
392///////////////////////////////////
393
394struct SectionToken {
395 constexpr SectionToken(QCalendarDateSectionValidator *v, int rep)
396 : validator(v), repeat(rep) {}
397
398 QCalendarDateSectionValidator *validator;
399 int repeat;
400};
401} // namespace QtPrivate
402
403Q_DECLARE_TYPEINFO(QtPrivate::SectionToken, Q_PRIMITIVE_TYPE);
404
405namespace QtPrivate {
406
407class QCalendarDateValidator
408{
409public:
410 QCalendarDateValidator();
411 ~QCalendarDateValidator();
412
413 void handleKeyEvent(QKeyEvent *keyEvent, QCalendar cal);
414 QString currentText(QCalendar cal) const;
415 QDate currentDate() const { return m_currentDate; }
416 void setFormat(const QString &format);
417 void setInitialDate(QDate date, QCalendar cal);
418
419 void setLocale(const QLocale &locale);
420
421private:
422 void toNextToken();
423 void toPreviousToken();
424 void applyToDate(QCalendar cal);
425
426 int countRepeat(const QString &str, int index) const;
427 void clear();
428
429 QStringList m_separators;
430 std::vector<SectionToken> m_tokens;
431 QCalendarYearValidator m_yearValidator;
432 QCalendarMonthValidator m_monthValidator;
433 QCalendarDayValidator m_dayValidator;
434
435 int m_currentToken;
436
437 QDate m_initialDate;
438 QDate m_currentDate;
439
440 QCalendarDateSectionValidator::Section m_lastSectionMove;
441};
442
443QCalendarDateValidator::QCalendarDateValidator()
444 : m_currentToken(-1),
445 m_initialDate(QDate::currentDate()),
446 m_currentDate(m_initialDate),
447 m_lastSectionMove(QCalendarDateSectionValidator::ThisSection)
448{
449}
450
451void QCalendarDateValidator::setLocale(const QLocale &locale)
452{
453 m_yearValidator.m_locale = locale;
454 m_monthValidator.m_locale = locale;
455 m_dayValidator.m_locale = locale;
456}
457
458QCalendarDateValidator::~QCalendarDateValidator()
459{
460 clear();
461}
462
463// from qdatetime.cpp
464int QCalendarDateValidator::countRepeat(const QString &str, int index) const
465{
466 Q_ASSERT(index >= 0 && index < str.size());
467 int count = 1;
468 const QChar ch = str.at(i: index);
469 while (index + count < str.size() && str.at(i: index + count) == ch)
470 ++count;
471 return count;
472}
473
474void QCalendarDateValidator::setInitialDate(QDate date, QCalendar cal)
475{
476 m_yearValidator.setDate(date, cal);
477 m_monthValidator.setDate(date, cal);
478 m_dayValidator.setDate(date, cal);
479 m_initialDate = date;
480 m_currentDate = date;
481 m_lastSectionMove = QCalendarDateSectionValidator::ThisSection;
482}
483
484QString QCalendarDateValidator::currentText(QCalendar cal) const
485{
486 QString str;
487 const int numSeps = m_separators.size();
488 const int numTokens = int(m_tokens.size());
489 for (int i = 0; i < numSeps; ++i) {
490 str += m_separators.at(i);
491 if (i < numTokens) {
492 const SectionToken &token = m_tokens[i];
493 if (i == m_currentToken)
494 str += token.validator->text();
495 else
496 str += token.validator->text(date: m_currentDate, cal, repeat: token.repeat);
497 }
498 }
499 return str;
500}
501
502void QCalendarDateValidator::clear()
503{
504 m_tokens.clear();
505 m_separators.clear();
506
507 m_currentToken = -1;
508}
509
510void QCalendarDateValidator::setFormat(const QString &format)
511{
512 clear();
513
514 int pos = 0;
515 const auto quote = u'\'';
516 bool quoting = false;
517 QString separator;
518 while (pos < format.size()) {
519 const QStringView mid = QStringView{format}.mid(pos);
520 int offset = 1;
521
522 if (mid.startsWith(c: quote)) {
523 quoting = !quoting;
524 } else {
525 const QChar nextChar = format.at(i: pos);
526 if (quoting) {
527 separator += nextChar;
528 quoting = false;
529 } else {
530 QCalendarDateSectionValidator *validator = nullptr;
531 if (nextChar == u'd') {
532 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
533 validator = &m_dayValidator;
534 } else if (nextChar == u'M') {
535 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
536 validator = &m_monthValidator;
537 } else if (nextChar == u'y') {
538 offset = qMin(a: 4, b: countRepeat(str: format, index: pos));
539 validator = &m_yearValidator;
540 } else {
541 separator += nextChar;
542 }
543 if (validator) {
544 m_tokens.push_back(x: SectionToken(validator, offset));
545 m_separators.append(t: separator);
546 separator = QString();
547 if (m_currentToken < 0)
548 m_currentToken = int(m_tokens.size()) - 1;
549
550 }
551 }
552 }
553 pos += offset;
554 }
555 m_separators += separator;
556}
557
558void QCalendarDateValidator::applyToDate(QCalendar cal)
559{
560 m_currentDate = m_yearValidator.applyToDate(date: m_currentDate, cal);
561 m_currentDate = m_monthValidator.applyToDate(date: m_currentDate, cal);
562 m_currentDate = m_dayValidator.applyToDate(date: m_currentDate, cal);
563}
564
565void QCalendarDateValidator::toNextToken()
566{
567 if (m_currentToken < 0)
568 return;
569 ++m_currentToken;
570 m_currentToken %= m_tokens.size();
571}
572
573void QCalendarDateValidator::toPreviousToken()
574{
575 if (m_currentToken < 0)
576 return;
577 --m_currentToken;
578 m_currentToken %= m_tokens.size();
579}
580
581void QCalendarDateValidator::handleKeyEvent(QKeyEvent *keyEvent,QCalendar cal)
582{
583 if (m_currentToken < 0)
584 return;
585
586 int key = keyEvent->key();
587 if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) {
588 if (key == Qt::Key_Back || key == Qt::Key_Backspace)
589 toPreviousToken();
590 }
591 if (key == Qt::Key_Right)
592 toNextToken();
593 else if (key == Qt::Key_Left)
594 toPreviousToken();
595
596 m_lastSectionMove = m_tokens[m_currentToken].validator->handleKey(key);
597
598 applyToDate(cal);
599 if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection)
600 toNextToken();
601 else if (m_lastSectionMove == QCalendarDateSectionValidator::PrevSection)
602 toPreviousToken();
603}
604
605//////////////////////////////////
606
607class QCalendarTextNavigator: public QObject
608{
609 Q_OBJECT
610public:
611 QCalendarTextNavigator(QObject *parent = nullptr)
612 : QObject(parent), m_dateText(nullptr), m_dateFrame(nullptr), m_dateValidator(nullptr),
613 m_widget(nullptr), m_editDelay(1500), m_date(QDate::currentDate()) {}
614
615 QWidget *widget() const;
616 void setWidget(QWidget *widget);
617
618 int dateEditAcceptDelay() const;
619 void setDateEditAcceptDelay(int delay);
620
621 void setDate(QDate date);
622
623 bool eventFilter(QObject *o, QEvent *e) override;
624 void timerEvent(QTimerEvent *e) override;
625
626signals:
627 void dateChanged(QDate date);
628 void editingFinished();
629
630private:
631 void applyDate();
632 void updateDateLabel();
633 void createDateLabel();
634 void removeDateLabel();
635
636 QLabel *m_dateText;
637 QFrame *m_dateFrame;
638 QBasicTimer m_acceptTimer;
639 QCalendarDateValidator *m_dateValidator;
640 QWidget *m_widget;
641 int m_editDelay;
642
643 QDate m_date;
644 const QCalendar m_calendar;
645};
646
647QWidget *QCalendarTextNavigator::widget() const
648{
649 return m_widget;
650}
651
652void QCalendarTextNavigator::setWidget(QWidget *widget)
653{
654 m_widget = widget;
655}
656
657void QCalendarTextNavigator::setDate(QDate date)
658{
659 m_date = date;
660}
661
662void QCalendarTextNavigator::updateDateLabel()
663{
664 if (!m_widget)
665 return;
666
667 m_acceptTimer.start(msec: m_editDelay, obj: this);
668
669 m_dateText->setText(m_dateValidator->currentText(cal: m_calendar));
670
671 QSize s = m_dateFrame->sizeHint();
672 QRect r = m_widget->geometry(); // later, just the table section
673 QRect newRect((r.width() - s.width()) / 2, (r.height() - s.height()) / 2, s.width(), s.height());
674 m_dateFrame->setGeometry(newRect);
675 // need to set palette after geometry update as phonestyle sets transparency
676 // effect in move event.
677 QPalette p = m_dateFrame->palette();
678 p.setBrush(acr: QPalette::Window, abrush: m_dateFrame->window()->palette().brush(cr: QPalette::Window));
679 m_dateFrame->setPalette(p);
680
681 m_dateFrame->raise();
682 m_dateFrame->show();
683}
684
685void QCalendarTextNavigator::applyDate()
686{
687 QDate date = m_dateValidator->currentDate();
688 if (m_date == date)
689 return;
690
691 m_date = date;
692 emit dateChanged(date);
693}
694
695void QCalendarTextNavigator::createDateLabel()
696{
697 if (m_dateFrame)
698 return;
699 m_dateFrame = new QFrame(m_widget);
700 QVBoxLayout *vl = new QVBoxLayout;
701 m_dateText = new QLabel;
702 vl->addWidget(m_dateText);
703 m_dateFrame->setLayout(vl);
704 m_dateFrame->setFrameShadow(QFrame::Plain);
705 m_dateFrame->setFrameShape(QFrame::Box);
706 m_dateValidator = new QCalendarDateValidator();
707 m_dateValidator->setLocale(m_widget->locale());
708 m_dateValidator->setFormat(m_widget->locale().dateFormat(format: QLocale::ShortFormat));
709 m_dateValidator->setInitialDate(date: m_date, cal: m_calendar);
710
711 m_dateFrame->setAutoFillBackground(true);
712 m_dateFrame->setBackgroundRole(QPalette::Window);
713}
714
715void QCalendarTextNavigator::removeDateLabel()
716{
717 if (!m_dateFrame)
718 return;
719 m_acceptTimer.stop();
720 m_dateFrame->hide();
721 m_dateFrame->deleteLater();
722 delete m_dateValidator;
723 m_dateFrame = nullptr;
724 m_dateText = nullptr;
725 m_dateValidator = nullptr;
726}
727
728bool QCalendarTextNavigator::eventFilter(QObject *o, QEvent *e)
729{
730 if (m_widget) {
731 if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) {
732 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
733 if ((ke->text().size() > 0 && ke->text().at(i: 0).isPrint()) || m_dateFrame) {
734 if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Select) {
735 applyDate();
736 emit editingFinished();
737 removeDateLabel();
738#if QT_CONFIG(shortcut)
739 } else if (ke->matches(key: QKeySequence::Cancel)) {
740 removeDateLabel();
741#endif
742 } else if (e->type() == QEvent::KeyPress) {
743 createDateLabel();
744 m_dateValidator->handleKeyEvent(keyEvent: ke, cal: m_calendar);
745 updateDateLabel();
746 }
747 ke->accept();
748 return true;
749 }
750 // If we are navigating let the user finish his date in old locate.
751 // If we change our mind and want it to update immediately simply uncomment below
752 /*
753 } else if (e->type() == QEvent::LocaleChange) {
754 if (m_dateValidator) {
755 m_dateValidator->setLocale(m_widget->locale());
756 m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat));
757 updateDateLabel();
758 }
759 */
760 }
761 }
762 return QObject::eventFilter(watched: o,event: e);
763}
764
765void QCalendarTextNavigator::timerEvent(QTimerEvent *e)
766{
767 if (e->timerId() == m_acceptTimer.timerId()) {
768 applyDate();
769 removeDateLabel();
770 }
771}
772
773int QCalendarTextNavigator::dateEditAcceptDelay() const
774{
775 return m_editDelay;
776}
777
778void QCalendarTextNavigator::setDateEditAcceptDelay(int delay)
779{
780 m_editDelay = delay;
781}
782
783class QCalendarView;
784
785// a small helper class that replaces a QMap<Qt::DayOfWeek, T>,
786// but requires T to have a member-swap and a default constructor
787// which should be cheap (no memory allocations)
788
789QT_WARNING_PUSH
790QT_WARNING_DISABLE_MSVC(4351) // "new behavior: elements of array ... will be default initialized"
791
792template <typename T>
793class StaticDayOfWeekAssociativeArray {
794 bool contained[7];
795 T data[7];
796
797 static constexpr int day2idx(Qt::DayOfWeek day) noexcept { return int(day) - 1; } // alt: day % 7
798public:
799 constexpr StaticDayOfWeekAssociativeArray() noexcept(noexcept(T()))
800 : contained{}, data{} // arrays require uniform initialization
801 {}
802
803 constexpr bool contains(Qt::DayOfWeek day) const noexcept { return contained[day2idx(day)]; }
804 constexpr const T &value(Qt::DayOfWeek day) const noexcept { return data[day2idx(day)]; }
805
806 constexpr T &operator[](Qt::DayOfWeek day) noexcept
807 {
808 const int idx = day2idx(day);
809 contained[idx] = true;
810 return data[idx];
811 }
812
813 constexpr void insert(Qt::DayOfWeek day, T v) noexcept
814 {
815 operator[](day).swap(v);
816 }
817};
818
819QT_WARNING_POP
820
821class QCalendarModel : public QAbstractTableModel
822{
823 Q_OBJECT
824public:
825 QCalendarModel(QObject *parent = nullptr);
826
827 int rowCount(const QModelIndex &parent) const override
828 {
829 if (parent.isValid())
830 return 0;
831 return RowCount + m_firstRow;
832 }
833
834 int columnCount(const QModelIndex &parent) const override
835 {
836 if (parent.isValid())
837 return 0;
838 return ColumnCount + m_firstColumn;
839 }
840
841 QVariant data(const QModelIndex &index, int role) const override;
842 Qt::ItemFlags flags(const QModelIndex &index) const override;
843
844 void showMonth(int year, int month);
845 void setDate(QDate d);
846
847 void setCalendar(QCalendar c);
848 QCalendar calendar() const;
849
850 void setMinimumDate(QDate date);
851 void setMaximumDate(QDate date);
852
853 void setRange(QDate min, QDate max);
854
855 void setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format);
856
857 void setFirstColumnDay(Qt::DayOfWeek dayOfWeek);
858 Qt::DayOfWeek firstColumnDay() const;
859
860 bool weekNumbersShown() const;
861 void setWeekNumbersShown(bool show);
862
863 QTextCharFormat formatForCell(int row, int col) const;
864 Qt::DayOfWeek dayOfWeekForColumn(int section) const;
865 int columnForDayOfWeek(Qt::DayOfWeek day) const;
866 QDate dateForCell(int row, int column) const;
867 void cellForDate(QDate date, int *row, int *column) const;
868 QString dayName(Qt::DayOfWeek day) const;
869
870 void setView(QCalendarView *view)
871 { m_view = view; }
872
873 void internalUpdate();
874 QDate referenceDate() const;
875 int columnForFirstOfMonth(QDate date) const;
876
877 QString monthName(const QLocale &locale, int month)
878 {
879 return m_calendar.standaloneMonthName(locale, month, year: m_shownYear, format: QLocale::LongFormat);
880 }
881
882 int m_firstColumn;
883 int m_firstRow;
884 QCalendar m_calendar;
885 QDate m_date;
886 QDate m_minimumDate;
887 QDate m_maximumDate;
888 int m_shownYear;
889 int m_shownMonth;
890 Qt::DayOfWeek m_firstDay;
891 QCalendarWidget::HorizontalHeaderFormat m_horizontalHeaderFormat;
892 bool m_weekNumbersShown;
893 StaticDayOfWeekAssociativeArray<QTextCharFormat> m_dayFormats;
894 QMap<QDate, QTextCharFormat> m_dateFormats;
895 QTextCharFormat m_headerFormat;
896 QCalendarView *m_view;
897};
898
899class QCalendarView : public QTableView
900{
901 Q_OBJECT
902public:
903 QCalendarView(QWidget *parent = nullptr);
904
905 void internalUpdate() { updateGeometries(); }
906 void setReadOnly(bool enable);
907 virtual void keyboardSearch(const QString &) override {}
908
909signals:
910 void showDate(QDate date);
911 void changeDate(QDate date, bool changeMonth);
912 void clicked(QDate date);
913 void editingFinished();
914protected:
915 QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
916 void mouseDoubleClickEvent(QMouseEvent *event) override;
917 void mousePressEvent(QMouseEvent *event) override;
918 void mouseMoveEvent(QMouseEvent *event) override;
919 void mouseReleaseEvent(QMouseEvent *event) override;
920#if QT_CONFIG(wheelevent)
921 void wheelEvent(QWheelEvent *event) override;
922#endif
923 void keyPressEvent(QKeyEvent *event) override;
924 bool event(QEvent *event) override;
925
926 QDate handleMouseEvent(QMouseEvent *event);
927public:
928 bool readOnly;
929private:
930 bool validDateClicked;
931#ifdef QT_KEYPAD_NAVIGATION
932 QDate origDate;
933#endif
934};
935
936QCalendarModel::QCalendarModel(QObject *parent)
937 : QAbstractTableModel(parent),
938 m_firstColumn(1),
939 m_firstRow(1),
940 m_date(QDate::currentDate()),
941 m_minimumDate(QDate::fromJulianDay(jd_: 1)),
942 m_maximumDate(9999, 12, 31),
943 m_shownYear(m_date.year(cal: m_calendar)),
944 m_shownMonth(m_date.month(cal: m_calendar)),
945 m_firstDay(QLocale().firstDayOfWeek()),
946 m_horizontalHeaderFormat(QCalendarWidget::ShortDayNames),
947 m_weekNumbersShown(true),
948 m_view(nullptr)
949{
950}
951
952Qt::DayOfWeek QCalendarModel::dayOfWeekForColumn(int column) const
953{
954 int col = column - m_firstColumn;
955 if (col < 0 || col > 6)
956 return Qt::Sunday;
957 int day = m_firstDay + col;
958 if (day > 7)
959 day -= 7;
960 return Qt::DayOfWeek(day);
961}
962
963int QCalendarModel::columnForDayOfWeek(Qt::DayOfWeek day) const
964{
965 if (day < 1 || unsigned(day) > unsigned(7))
966 return -1;
967 int column = (int)day - (int)m_firstDay;
968 if (column < 0)
969 column += 7;
970 return column + m_firstColumn;
971}
972
973/*
974This simple algorithm tries to generate a valid date from the month shown.
975Some months don't contain a first day (e.g. Jan of -4713 year,
976so QDate (-4713, 1, 1) would be invalid). In that case we try to generate
977another valid date for that month. Later, returned date's day is the number of cells
978calendar widget will reserve for days before referenceDate. (E.g. if returned date's
979day is 16, that day will be placed in 3rd or 4th row, not in the 1st or 2nd row).
980Depending on referenceData we can change behaviour of Oct 1582. If referenceDate is 1st
981of Oct we render 1 Oct in 1st or 2nd row. If referenceDate is 17 of Oct we show always 16
982dates before 17 of Oct, and since this month contains the hole 5-14 Oct, the first of Oct
983will be rendered in 2nd or 3rd row, showing more dates from previous month.
984*/
985QDate QCalendarModel::referenceDate() const
986{
987 // TODO: Check this
988 int refDay = 1;
989 while (refDay <= 31) {
990 QDate refDate(m_shownYear, m_shownMonth, refDay, m_calendar);
991 if (refDate.isValid())
992 return refDate;
993 refDay += 1;
994 }
995 return QDate();
996}
997
998int QCalendarModel::columnForFirstOfMonth(QDate date) const
999{
1000 return (columnForDayOfWeek(day: static_cast<Qt::DayOfWeek>(m_calendar.dayOfWeek(date)))
1001 - (date.day(cal: m_calendar) % 7) + 8) % 7;
1002}
1003
1004QDate QCalendarModel::dateForCell(int row, int column) const
1005{
1006 if (row < m_firstRow || row > m_firstRow + RowCount - 1 ||
1007 column < m_firstColumn || column > m_firstColumn + ColumnCount - 1)
1008 return QDate();
1009 const QDate refDate = referenceDate();
1010 if (!refDate.isValid())
1011 return QDate();
1012
1013 const int columnForFirstOfShownMonth = columnForFirstOfMonth(date: refDate);
1014 if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset)
1015 row -= 1;
1016
1017 const int requestedDay =
1018 7 * (row - m_firstRow) + column - columnForFirstOfShownMonth - refDate.day(cal: m_calendar) + 1;
1019 return refDate.addDays(days: requestedDay);
1020}
1021
1022void QCalendarModel::cellForDate(QDate date, int *row, int *column) const
1023{
1024 if (!row && !column)
1025 return;
1026
1027 if (row)
1028 *row = -1;
1029 if (column)
1030 *column = -1;
1031
1032 const QDate refDate = referenceDate();
1033 if (!refDate.isValid())
1034 return;
1035
1036 const int columnForFirstOfShownMonth = columnForFirstOfMonth(date: refDate);
1037 const int requestedPosition = (refDate.daysTo(d: date) - m_firstColumn +
1038 columnForFirstOfShownMonth + refDate.day(cal: m_calendar) - 1);
1039
1040 int c = requestedPosition % 7;
1041 int r = requestedPosition / 7;
1042 if (c < 0) {
1043 c += 7;
1044 r -= 1;
1045 }
1046
1047 if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset)
1048 r += 1;
1049
1050 if (r < 0 || r > RowCount - 1 || c < 0 || c > ColumnCount - 1)
1051 return;
1052
1053 if (row)
1054 *row = r + m_firstRow;
1055 if (column)
1056 *column = c + m_firstColumn;
1057}
1058
1059QString QCalendarModel::dayName(Qt::DayOfWeek day) const
1060{
1061 switch (m_horizontalHeaderFormat) {
1062 case QCalendarWidget::SingleLetterDayNames: {
1063 QString standaloneDayName = m_view->locale().standaloneDayName(day, format: QLocale::NarrowFormat);
1064 if (standaloneDayName == m_view->locale().dayName(day, format: QLocale::NarrowFormat))
1065 return standaloneDayName.left(n: 1);
1066 return standaloneDayName;
1067 }
1068 case QCalendarWidget::ShortDayNames:
1069 return m_view->locale().dayName(day, format: QLocale::ShortFormat);
1070 case QCalendarWidget::LongDayNames:
1071 return m_view->locale().dayName(day, format: QLocale::LongFormat);
1072 default:
1073 break;
1074 }
1075 return QString();
1076}
1077
1078QTextCharFormat QCalendarModel::formatForCell(int row, int col) const
1079{
1080 QPalette pal;
1081 QPalette::ColorGroup cg = QPalette::Active;
1082 QTextCharFormat format;
1083
1084 if (m_view) {
1085 pal = m_view->palette();
1086 if (!m_view->isEnabled())
1087 cg = QPalette::Disabled;
1088 else if (!m_view->isActiveWindow())
1089 cg = QPalette::Inactive;
1090 format.setFont(font: m_view->font());
1091 }
1092
1093 bool header = (m_weekNumbersShown && col == HeaderColumn)
1094 || (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow);
1095 format.setBackground(pal.brush(cg, cr: header ? QPalette::AlternateBase : QPalette::Base));
1096 format.setForeground(pal.brush(cg, cr: QPalette::Text));
1097 if (header) {
1098 format.merge(other: m_headerFormat);
1099 }
1100
1101 if (col >= m_firstColumn && col < m_firstColumn + ColumnCount) {
1102 Qt::DayOfWeek dayOfWeek = dayOfWeekForColumn(column: col);
1103 if (m_dayFormats.contains(day: dayOfWeek))
1104 format.merge(other: m_dayFormats.value(day: dayOfWeek));
1105 }
1106
1107 if (!header) {
1108 QDate date = dateForCell(row, column: col);
1109 format.merge(other: m_dateFormats.value(key: date));
1110 if (date < m_minimumDate || date > m_maximumDate)
1111 format.setBackground(pal.brush(cg, cr: QPalette::Window));
1112 if (m_shownMonth != date.month(cal: m_calendar))
1113 format.setForeground(pal.brush(cg: QPalette::Disabled, cr: QPalette::Text));
1114 }
1115 return format;
1116}
1117
1118QVariant QCalendarModel::data(const QModelIndex &index, int role) const
1119{
1120 if (role == Qt::TextAlignmentRole)
1121 return (int) Qt::AlignCenter;
1122
1123 int row = index.row();
1124 int column = index.column();
1125
1126 if (role == Qt::DisplayRole) {
1127 if (m_weekNumbersShown && column == HeaderColumn
1128 && row >= m_firstRow && row < m_firstRow + RowCount) {
1129 QDate date = dateForCell(row, column: columnForDayOfWeek(day: Qt::Monday));
1130 if (date.isValid())
1131 return date.weekNumber();
1132 }
1133 if (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow
1134 && column >= m_firstColumn && column < m_firstColumn + ColumnCount)
1135 return dayName(day: dayOfWeekForColumn(column));
1136 QDate date = dateForCell(row, column);
1137 if (date.isValid())
1138 return date.day(cal: m_calendar);
1139 return QString();
1140 }
1141
1142 QTextCharFormat fmt = formatForCell(row, col: column);
1143 if (role == Qt::BackgroundRole)
1144 return fmt.background().color();
1145 if (role == Qt::ForegroundRole)
1146 return fmt.foreground().color();
1147 if (role == Qt::FontRole)
1148 return fmt.font();
1149 if (role == Qt::ToolTipRole)
1150 return fmt.toolTip();
1151 return QVariant();
1152}
1153
1154Qt::ItemFlags QCalendarModel::flags(const QModelIndex &index) const
1155{
1156 QDate date = dateForCell(row: index.row(), column: index.column());
1157 if (!date.isValid())
1158 return QAbstractTableModel::flags(index);
1159 if (date < m_minimumDate)
1160 return { };
1161 if (date > m_maximumDate)
1162 return { };
1163 return QAbstractTableModel::flags(index);
1164}
1165
1166void QCalendarModel::setDate(QDate d)
1167{
1168 m_date = d;
1169 if (m_date < m_minimumDate)
1170 m_date = m_minimumDate;
1171 else if (m_date > m_maximumDate)
1172 m_date = m_maximumDate;
1173}
1174
1175void QCalendarModel::setCalendar(QCalendar c)
1176{
1177 m_calendar = c;
1178 m_shownYear = m_date.year(cal: c);
1179 m_shownMonth = m_date.month(cal: c);
1180 internalUpdate();
1181 m_view->internalUpdate();
1182}
1183
1184QCalendar QCalendarModel::calendar() const
1185{
1186 return m_calendar;
1187}
1188
1189void QCalendarModel::showMonth(int year, int month)
1190{
1191 if (m_shownYear == year && m_shownMonth == month)
1192 return;
1193
1194 m_shownYear = year;
1195 m_shownMonth = month;
1196
1197 internalUpdate();
1198}
1199
1200void QCalendarModel::setMinimumDate(QDate d)
1201{
1202 if (!d.isValid() || d == m_minimumDate)
1203 return;
1204
1205 m_minimumDate = d;
1206 if (m_maximumDate < m_minimumDate)
1207 m_maximumDate = m_minimumDate;
1208 if (m_date < m_minimumDate)
1209 m_date = m_minimumDate;
1210 internalUpdate();
1211}
1212
1213void QCalendarModel::setMaximumDate(QDate d)
1214{
1215 if (!d.isValid() || d == m_maximumDate)
1216 return;
1217
1218 m_maximumDate = d;
1219 if (m_minimumDate > m_maximumDate)
1220 m_minimumDate = m_maximumDate;
1221 if (m_date > m_maximumDate)
1222 m_date = m_maximumDate;
1223 internalUpdate();
1224}
1225
1226void QCalendarModel::setRange(QDate min, QDate max)
1227{
1228 m_minimumDate = min;
1229 m_maximumDate = max;
1230 if (m_minimumDate > m_maximumDate)
1231 qSwap(value1&: m_minimumDate, value2&: m_maximumDate);
1232 if (m_date < m_minimumDate)
1233 m_date = m_minimumDate;
1234 if (m_date > m_maximumDate)
1235 m_date = m_maximumDate;
1236 internalUpdate();
1237}
1238
1239void QCalendarModel::internalUpdate()
1240{
1241 QModelIndex begin = index(row: 0, column: 0);
1242 QModelIndex end = index(row: m_firstRow + RowCount - 1, column: m_firstColumn + ColumnCount - 1);
1243 emit dataChanged(topLeft: begin, bottomRight: end);
1244 emit headerDataChanged(orientation: Qt::Vertical, first: 0, last: m_firstRow + RowCount - 1);
1245 emit headerDataChanged(orientation: Qt::Horizontal, first: 0, last: m_firstColumn + ColumnCount - 1);
1246}
1247
1248void QCalendarModel::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format)
1249{
1250 if (m_horizontalHeaderFormat == format)
1251 return;
1252
1253 int oldFormat = m_horizontalHeaderFormat;
1254 m_horizontalHeaderFormat = format;
1255 if (oldFormat == QCalendarWidget::NoHorizontalHeader) {
1256 beginInsertRows(parent: QModelIndex(), first: 0, last: 0);
1257 m_firstRow = 1;
1258 endInsertRows();
1259 } else if (m_horizontalHeaderFormat == QCalendarWidget::NoHorizontalHeader) {
1260 beginRemoveRows(parent: QModelIndex(), first: 0, last: 0);
1261 m_firstRow = 0;
1262 endRemoveRows();
1263 }
1264 internalUpdate();
1265}
1266
1267void QCalendarModel::setFirstColumnDay(Qt::DayOfWeek dayOfWeek)
1268{
1269 if (m_firstDay == dayOfWeek)
1270 return;
1271
1272 m_firstDay = dayOfWeek;
1273 internalUpdate();
1274}
1275
1276Qt::DayOfWeek QCalendarModel::firstColumnDay() const
1277{
1278 return m_firstDay;
1279}
1280
1281bool QCalendarModel::weekNumbersShown() const
1282{
1283 return m_weekNumbersShown;
1284}
1285
1286void QCalendarModel::setWeekNumbersShown(bool show)
1287{
1288 if (m_weekNumbersShown == show)
1289 return;
1290
1291 m_weekNumbersShown = show;
1292 if (show) {
1293 beginInsertColumns(parent: QModelIndex(), first: 0, last: 0);
1294 m_firstColumn = 1;
1295 endInsertColumns();
1296 } else {
1297 beginRemoveColumns(parent: QModelIndex(), first: 0, last: 0);
1298 m_firstColumn = 0;
1299 endRemoveColumns();
1300 }
1301 internalUpdate();
1302}
1303
1304QCalendarView::QCalendarView(QWidget *parent)
1305 : QTableView(parent),
1306 readOnly(false),
1307 validDateClicked(false)
1308{
1309 setTabKeyNavigation(false);
1310 setShowGrid(false);
1311 verticalHeader()->setVisible(false);
1312 horizontalHeader()->setVisible(false);
1313 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1314 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1315}
1316
1317QModelIndex QCalendarView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1318{
1319 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1320 if (!calendarModel)
1321 return QTableView::moveCursor(cursorAction, modifiers);
1322
1323 QCalendar cal = calendarModel->calendar();
1324
1325 if (readOnly)
1326 return currentIndex();
1327
1328 QModelIndex index = currentIndex();
1329 QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(row: index.row(), column: index.column());
1330 switch (cursorAction) {
1331 case QAbstractItemView::MoveUp:
1332 currentDate = currentDate.addDays(days: -7);
1333 break;
1334 case QAbstractItemView::MoveDown:
1335 currentDate = currentDate.addDays(days: 7);
1336 break;
1337 case QAbstractItemView::MoveLeft:
1338 currentDate = currentDate.addDays(days: isRightToLeft() ? 1 : -1);
1339 break;
1340 case QAbstractItemView::MoveRight:
1341 currentDate = currentDate.addDays(days: isRightToLeft() ? -1 : 1);
1342 break;
1343 case QAbstractItemView::MoveHome: {
1344 auto parts = cal.partsFromDate(date: currentDate);
1345 if (parts.isValid()) {
1346 parts.day = 1;
1347 currentDate = cal.dateFromParts(parts);
1348 }
1349 }
1350 break;
1351 case QAbstractItemView::MoveEnd: {
1352 auto parts = cal.partsFromDate(date: currentDate);
1353 if (parts.isValid()) {
1354 parts.day = cal.daysInMonth(month: parts.month, year: parts.year);
1355 currentDate = cal.dateFromParts(parts);
1356 }
1357 }
1358 break;
1359 case QAbstractItemView::MovePageUp:
1360 currentDate = currentDate.addMonths(months: -1, cal);
1361 break;
1362 case QAbstractItemView::MovePageDown:
1363 currentDate = currentDate.addMonths(months: 1, cal);
1364 break;
1365 case QAbstractItemView::MoveNext:
1366 case QAbstractItemView::MovePrevious:
1367 return currentIndex();
1368 default:
1369 break;
1370 }
1371 emit changeDate(date: currentDate, changeMonth: true);
1372 return currentIndex();
1373}
1374
1375void QCalendarView::keyPressEvent(QKeyEvent *event)
1376{
1377#ifdef QT_KEYPAD_NAVIGATION
1378 if (event->key() == Qt::Key_Select) {
1379 if (QApplicationPrivate::keypadNavigationEnabled()) {
1380 if (!hasEditFocus()) {
1381 setEditFocus(true);
1382 return;
1383 }
1384 }
1385 } else if (event->key() == Qt::Key_Back) {
1386 if (QApplicationPrivate::keypadNavigationEnabled() && hasEditFocus()) {
1387 if (qobject_cast<QCalendarModel *>(model())) {
1388 emit changeDate(origDate, true); //changes selection back to origDate, but doesn't activate
1389 setEditFocus(false);
1390 return;
1391 }
1392 }
1393 }
1394#endif
1395
1396 if (!readOnly) {
1397 switch (event->key()) {
1398 case Qt::Key_Return:
1399 case Qt::Key_Enter:
1400 case Qt::Key_Select:
1401 emit editingFinished();
1402 return;
1403 default:
1404 break;
1405 }
1406 }
1407 QTableView::keyPressEvent(event);
1408}
1409
1410#if QT_CONFIG(wheelevent)
1411void QCalendarView::wheelEvent(QWheelEvent *event)
1412{
1413 const int numDegrees = event->angleDelta().y() / 8;
1414 const int numSteps = numDegrees / 15;
1415 const QModelIndex index = currentIndex();
1416 QCalendarModel *calendarModel = static_cast<QCalendarModel*>(model());
1417 QDate currentDate = calendarModel->dateForCell(row: index.row(), column: index.column());
1418 currentDate = currentDate.addMonths(months: -numSteps, cal: calendarModel->calendar());
1419 emit showDate(date: currentDate);
1420}
1421#endif
1422
1423bool QCalendarView::event(QEvent *event)
1424{
1425#ifdef QT_KEYPAD_NAVIGATION
1426 if (event->type() == QEvent::FocusIn) {
1427 if (QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model())) {
1428 origDate = calendarModel->m_date;
1429 }
1430 }
1431#endif
1432
1433 return QTableView::event(event);
1434}
1435
1436QDate QCalendarView::handleMouseEvent(QMouseEvent *event)
1437{
1438 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1439 if (!calendarModel)
1440 return QDate();
1441
1442 QPoint pos = event->position().toPoint();
1443 QModelIndex index = indexAt(p: pos);
1444 QDate date = calendarModel->dateForCell(row: index.row(), column: index.column());
1445 if (date.isValid() && date >= calendarModel->m_minimumDate
1446 && date <= calendarModel->m_maximumDate) {
1447 return date;
1448 }
1449 return QDate();
1450}
1451
1452void QCalendarView::mouseDoubleClickEvent(QMouseEvent *event)
1453{
1454 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1455 if (!calendarModel) {
1456 QTableView::mouseDoubleClickEvent(event);
1457 return;
1458 }
1459
1460 if (readOnly)
1461 return;
1462
1463 QDate date = handleMouseEvent(event);
1464 validDateClicked = false;
1465 if (date == calendarModel->m_date && !style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
1466 emit editingFinished();
1467 }
1468}
1469
1470void QCalendarView::mousePressEvent(QMouseEvent *event)
1471{
1472 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1473 if (!calendarModel) {
1474 QTableView::mousePressEvent(event);
1475 return;
1476 }
1477
1478 if (readOnly)
1479 return;
1480
1481 if (event->button() != Qt::LeftButton)
1482 return;
1483
1484 QDate date = handleMouseEvent(event);
1485 if (date.isValid()) {
1486 validDateClicked = true;
1487 int row = -1, col = -1;
1488 static_cast<QCalendarModel *>(model())->cellForDate(date, row: &row, column: &col);
1489 if (row != -1 && col != -1) {
1490 selectionModel()->setCurrentIndex(index: model()->index(row, column: col), command: QItemSelectionModel::NoUpdate);
1491 }
1492 } else {
1493 validDateClicked = false;
1494 event->ignore();
1495 }
1496}
1497
1498void QCalendarView::mouseMoveEvent(QMouseEvent *event)
1499{
1500 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1501 if (!calendarModel) {
1502 QTableView::mouseMoveEvent(event);
1503 return;
1504 }
1505
1506 if (readOnly)
1507 return;
1508
1509 if (validDateClicked) {
1510 QDate date = handleMouseEvent(event);
1511 if (date.isValid()) {
1512 int row = -1, col = -1;
1513 static_cast<QCalendarModel *>(model())->cellForDate(date, row: &row, column: &col);
1514 if (row != -1 && col != -1) {
1515 selectionModel()->setCurrentIndex(index: model()->index(row, column: col), command: QItemSelectionModel::NoUpdate);
1516 }
1517 }
1518 } else {
1519 event->ignore();
1520 }
1521}
1522
1523void QCalendarView::mouseReleaseEvent(QMouseEvent *event)
1524{
1525 QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(object: model());
1526 if (!calendarModel) {
1527 QTableView::mouseReleaseEvent(event);
1528 return;
1529 }
1530
1531 if (event->button() != Qt::LeftButton)
1532 return;
1533
1534 if (readOnly)
1535 return;
1536
1537 if (validDateClicked) {
1538 QDate date = handleMouseEvent(event);
1539 if (date.isValid()) {
1540 emit changeDate(date, changeMonth: true);
1541 emit clicked(date);
1542 if (style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick))
1543 emit editingFinished();
1544 }
1545 validDateClicked = false;
1546 } else {
1547 event->ignore();
1548 }
1549}
1550
1551class QCalendarDelegate : public QStyledItemDelegate
1552{
1553 Q_OBJECT
1554public:
1555 QCalendarDelegate(QCalendarWidgetPrivate *w, QObject *parent = nullptr)
1556 : QStyledItemDelegate(parent), calendarWidgetPrivate(w)
1557 { }
1558 virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
1559 const QModelIndex &index) const override;
1560 void paintCell(QPainter *painter, const QRect &rect, QDate date) const;
1561
1562private:
1563 QCalendarWidgetPrivate *calendarWidgetPrivate;
1564 mutable QStyleOptionViewItem storedOption;
1565};
1566
1567//Private tool button class
1568class QCalToolButton: public QToolButton
1569{
1570public:
1571 QCalToolButton(QWidget * parent)
1572 : QToolButton(parent)
1573 { }
1574protected:
1575 void paintEvent(QPaintEvent *e) override
1576 {
1577 Q_UNUSED(e);
1578
1579 QStyleOptionToolButton opt;
1580 initStyleOption(option: &opt);
1581
1582 if (opt.state & QStyle::State_MouseOver || isDown()) {
1583 //act as normal button
1584 setPalette(QPalette());
1585 } else {
1586 //set the highlight color for button text
1587 QPalette toolPalette = palette();
1588 toolPalette.setColor(acr: QPalette::ButtonText, acolor: toolPalette.color(cr: QPalette::HighlightedText));
1589 setPalette(toolPalette);
1590 }
1591
1592 QToolButton::paintEvent(e);
1593 }
1594};
1595
1596class QPrevNextCalButton : public QToolButton
1597{
1598 Q_OBJECT
1599public:
1600 QPrevNextCalButton(QWidget *parent) : QToolButton(parent) {}
1601protected:
1602 void paintEvent(QPaintEvent *) override {
1603 QStylePainter painter(this);
1604 QStyleOptionToolButton opt;
1605 initStyleOption(option: &opt);
1606 opt.state &= ~QStyle::State_HasFocus;
1607 painter.drawComplexControl(cc: QStyle::CC_ToolButton, opt);
1608 }
1609};
1610
1611} // namespace QtPrivate
1612
1613using QCalendarDateSectionValidator = QtPrivate::QCalendarDateSectionValidator;
1614using QCalendarDayValidator = QtPrivate::QCalendarDayValidator;
1615using QCalendarMonthValidator = QtPrivate::QCalendarMonthValidator;
1616using QCalendarYearValidator = QtPrivate::QCalendarYearValidator;
1617using QCalendarDateValidator = QtPrivate::QCalendarDateValidator;
1618using QPrevNextCalButton = QtPrivate::QPrevNextCalButton;
1619using QCalendarDelegate = QtPrivate::QCalendarDelegate;
1620using QCalToolButton = QtPrivate::QCalToolButton;
1621using QCalendarDelegate = QtPrivate::QCalendarDelegate;
1622using QCalendarModel = QtPrivate::QCalendarModel;
1623using QCalendarView = QtPrivate::QCalendarView;
1624using QCalendarTextNavigator = QtPrivate::QCalendarTextNavigator;
1625
1626class QCalendarWidgetPrivate : public QWidgetPrivate
1627{
1628 Q_DECLARE_PUBLIC(QCalendarWidget)
1629public:
1630 QCalendarWidgetPrivate();
1631
1632 void showMonth(int year, int month);
1633 void update();
1634 void paintCell(QPainter *painter, const QRect &rect, QDate date) const;
1635
1636 void _q_slotShowDate(QDate date);
1637 void _q_slotChangeDate(QDate date);
1638 void _q_slotChangeDate(QDate date, bool changeMonth);
1639 void _q_editingFinished();
1640 void _q_monthChanged(QAction*);
1641 void _q_prevMonthClicked();
1642 void _q_nextMonthClicked();
1643 void _q_yearEditingFinished();
1644 void _q_yearClicked();
1645
1646 void createNavigationBar(QWidget *widget);
1647 void updateButtonIcons();
1648 void updateMonthMenu();
1649 void updateMonthMenuNames();
1650 void updateNavigationBar();
1651 void updateCurrentPage(QDate newDate);
1652 inline QDate getCurrentDate();
1653 void setNavigatorEnabled(bool enable);
1654
1655 QCalendarModel *m_model;
1656 QCalendarView *m_view;
1657 QCalendarDelegate *m_delegate;
1658 QItemSelectionModel *m_selection;
1659 QCalendarTextNavigator *m_navigator;
1660 bool m_dateEditEnabled;
1661
1662 QToolButton *nextMonth;
1663 QToolButton *prevMonth;
1664 QCalToolButton *monthButton;
1665 QMenu *monthMenu;
1666 QMap<int, QAction *> monthToAction;
1667 QCalToolButton *yearButton;
1668 QSpinBox *yearEdit;
1669 QWidget *navBarBackground;
1670 QSpacerItem *spaceHolder;
1671
1672 bool navBarVisible;
1673 mutable QSize cachedSizeHint;
1674 Qt::FocusPolicy oldFocusPolicy;
1675};
1676
1677void QCalendarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
1678 const QModelIndex &index) const
1679{
1680 QDate date = calendarWidgetPrivate->m_model->dateForCell(row: index.row(), column: index.column());
1681 if (date.isValid()) {
1682 storedOption = option;
1683 QRect rect = option.rect;
1684 calendarWidgetPrivate->paintCell(painter, rect, date);
1685 } else {
1686 QStyledItemDelegate::paint(painter, option, index);
1687 }
1688}
1689
1690void QCalendarDelegate::paintCell(QPainter *painter, const QRect &rect, QDate date) const
1691{
1692 storedOption.rect = rect;
1693 int row = -1;
1694 int col = -1;
1695 calendarWidgetPrivate->m_model->cellForDate(date, row: &row, column: &col);
1696 QModelIndex idx = calendarWidgetPrivate->m_model->index(row, column: col);
1697 QStyledItemDelegate::paint(painter, option: storedOption, index: idx);
1698}
1699
1700QCalendarWidgetPrivate::QCalendarWidgetPrivate()
1701 : QWidgetPrivate()
1702{
1703 m_model = nullptr;
1704 m_view = nullptr;
1705 m_delegate = nullptr;
1706 m_selection = nullptr;
1707 m_navigator = nullptr;
1708 m_dateEditEnabled = false;
1709 navBarVisible = true;
1710 oldFocusPolicy = Qt::StrongFocus;
1711}
1712
1713void QCalendarWidgetPrivate::setNavigatorEnabled(bool enable)
1714{
1715 Q_Q(QCalendarWidget);
1716
1717 bool navigatorEnabled = (m_navigator->widget() != nullptr);
1718 if (enable == navigatorEnabled)
1719 return;
1720
1721 if (enable) {
1722 m_navigator->setWidget(q);
1723 q->connect(sender: m_navigator, SIGNAL(dateChanged(QDate)),
1724 receiver: q, SLOT(_q_slotChangeDate(QDate)));
1725 q->connect(sender: m_navigator, SIGNAL(editingFinished()),
1726 receiver: q, SLOT(_q_editingFinished()));
1727 m_view->installEventFilter(filterObj: m_navigator);
1728 } else {
1729 m_navigator->setWidget(nullptr);
1730 q->disconnect(sender: m_navigator, SIGNAL(dateChanged(QDate)),
1731 receiver: q, SLOT(_q_slotChangeDate(QDate)));
1732 q->disconnect(sender: m_navigator, SIGNAL(editingFinished()),
1733 receiver: q, SLOT(_q_editingFinished()));
1734 m_view->removeEventFilter(obj: m_navigator);
1735 }
1736}
1737
1738void QCalendarWidgetPrivate::createNavigationBar(QWidget *widget)
1739{
1740 Q_Q(QCalendarWidget);
1741 navBarBackground = new QWidget(widget);
1742 navBarBackground->setObjectName("qt_calendar_navigationbar"_L1);
1743 navBarBackground->setAutoFillBackground(true);
1744 navBarBackground->setBackgroundRole(QPalette::Highlight);
1745
1746 prevMonth = new QPrevNextCalButton(navBarBackground);
1747 nextMonth = new QPrevNextCalButton(navBarBackground);
1748 prevMonth->setAutoRaise(true);
1749 nextMonth->setAutoRaise(true);
1750 prevMonth->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1751 nextMonth->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1752 nextMonth->setAutoRaise(true);
1753 updateButtonIcons();
1754 prevMonth->setAutoRepeat(true);
1755 nextMonth->setAutoRepeat(true);
1756
1757 monthButton = new QCalToolButton(navBarBackground);
1758 monthButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1759 monthButton->setAutoRaise(true);
1760 monthButton->setPopupMode(QToolButton::InstantPopup);
1761 monthMenu = new QMenu(monthButton);
1762 for (int i = 1, e = m_model->m_calendar.maximumMonthsInYear(); i <= e; i++) {
1763 QString monthName(m_model->monthName(locale: q->locale(), month: i));
1764 QAction *act = monthMenu->addAction(text: monthName);
1765 act->setData(i);
1766 monthToAction[i] = act;
1767 }
1768 monthButton->setMenu(monthMenu);
1769 yearButton = new QCalToolButton(navBarBackground);
1770 yearButton->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1771 yearButton->setAutoRaise(true);
1772 yearEdit = new QSpinBox(navBarBackground);
1773
1774 QFont font = q->font();
1775 font.setBold(true);
1776 monthButton->setFont(font);
1777 yearButton->setFont(font);
1778 yearEdit->setFrame(false);
1779 yearEdit->setMinimum(m_model->m_minimumDate.year(cal: m_model->m_calendar));
1780 yearEdit->setMaximum(m_model->m_maximumDate.year(cal: m_model->m_calendar));
1781 yearEdit->hide();
1782 spaceHolder = new QSpacerItem(0,0);
1783
1784 QHBoxLayout *headerLayout = new QHBoxLayout;
1785 headerLayout->setContentsMargins(QMargins());
1786 headerLayout->setSpacing(0);
1787 headerLayout->addWidget(prevMonth);
1788 headerLayout->insertStretch(index: headerLayout->count());
1789 headerLayout->addWidget(monthButton);
1790 headerLayout->addItem(spaceHolder);
1791 headerLayout->addWidget(yearButton);
1792 headerLayout->insertStretch(index: headerLayout->count());
1793 headerLayout->addWidget(nextMonth);
1794 navBarBackground->setLayout(headerLayout);
1795
1796 yearEdit->setFocusPolicy(Qt::StrongFocus);
1797 prevMonth->setFocusPolicy(Qt::NoFocus);
1798 nextMonth->setFocusPolicy(Qt::NoFocus);
1799 yearButton->setFocusPolicy(Qt::NoFocus);
1800 monthButton->setFocusPolicy(Qt::NoFocus);
1801
1802 //set names for the header controls.
1803 prevMonth->setObjectName("qt_calendar_prevmonth"_L1);
1804 nextMonth->setObjectName("qt_calendar_nextmonth"_L1);
1805 monthButton->setObjectName("qt_calendar_monthbutton"_L1);
1806 yearButton->setObjectName("qt_calendar_yearbutton"_L1);
1807 yearEdit->setObjectName("qt_calendar_yearedit"_L1);
1808
1809 updateMonthMenu();
1810 showMonth(year: m_model->m_date.year(cal: m_model->m_calendar), month: m_model->m_date.month(cal: m_model->m_calendar));
1811}
1812
1813void QCalendarWidgetPrivate::updateButtonIcons()
1814{
1815 Q_Q(QCalendarWidget);
1816 prevMonth->setIcon(q->style()->standardIcon(standardIcon: q->isRightToLeft() ? QStyle::SP_ArrowRight : QStyle::SP_ArrowLeft, option: nullptr, widget: q));
1817 nextMonth->setIcon(q->style()->standardIcon(standardIcon: q->isRightToLeft() ? QStyle::SP_ArrowLeft : QStyle::SP_ArrowRight, option: nullptr, widget: q));
1818}
1819
1820void QCalendarWidgetPrivate::updateMonthMenu()
1821{
1822 int maxMonths = m_model->m_calendar.monthsInYear(year: m_model->m_shownYear);
1823 int beg = 1, end = maxMonths;
1824 bool prevEnabled = true;
1825 bool nextEnabled = true;
1826 QCalendar cal = m_model->calendar();
1827 if (m_model->m_shownYear == m_model->m_minimumDate.year(cal)) {
1828 beg = m_model->m_minimumDate.month(cal);
1829 if (m_model->m_shownMonth == m_model->m_minimumDate.month(cal))
1830 prevEnabled = false;
1831 }
1832 if (m_model->m_shownYear == m_model->m_maximumDate.year(cal)) {
1833 end = m_model->m_maximumDate.month(cal);
1834 if (m_model->m_shownMonth == m_model->m_maximumDate.month(cal))
1835 nextEnabled = false;
1836 }
1837 prevMonth->setEnabled(prevEnabled);
1838 nextMonth->setEnabled(nextEnabled);
1839 for (int i = 1; i <= maxMonths; i++) {
1840 bool monthEnabled = true;
1841 if (i < beg || i > end)
1842 monthEnabled = false;
1843 monthToAction[i]->setEnabled(monthEnabled);
1844 }
1845}
1846
1847void QCalendarWidgetPrivate::updateMonthMenuNames()
1848{
1849 Q_Q(QCalendarWidget);
1850
1851 for (int i = 1; i <= 12; i++) {
1852 QString monthName(m_model->monthName(locale: q->locale(), month: i));
1853 monthToAction[i]->setText(monthName);
1854 }
1855}
1856
1857void QCalendarWidgetPrivate::updateCurrentPage(QDate date)
1858{
1859 Q_Q(QCalendarWidget);
1860 QCalendar cal = m_model->calendar();
1861
1862 QDate newDate = date;
1863 QDate minDate = q->minimumDate();
1864 QDate maxDate = q->maximumDate();
1865 if (minDate.isValid()&& minDate.daysTo(d: newDate) < 0)
1866 newDate = minDate;
1867 if (maxDate.isValid()&& maxDate.daysTo(d: newDate) > 0)
1868 newDate = maxDate;
1869 showMonth(year: newDate.year(cal), month: newDate.month(cal));
1870 int row = -1, col = -1;
1871 m_model->cellForDate(date: newDate, row: &row, column: &col);
1872 if (row != -1 && col != -1)
1873 {
1874 m_view->selectionModel()->setCurrentIndex(index: m_model->index(row, column: col),
1875 command: QItemSelectionModel::NoUpdate);
1876 }
1877}
1878
1879void QCalendarWidgetPrivate::_q_monthChanged(QAction *act)
1880{
1881 monthButton->setText(act->text());
1882 QDate currentDate = getCurrentDate();
1883 QDate newDate = currentDate.addMonths(months: act->data().toInt() - currentDate.month(cal: m_model->m_calendar), cal: m_model->m_calendar);
1884 updateCurrentPage(date: newDate);
1885}
1886
1887QDate QCalendarWidgetPrivate::getCurrentDate()
1888{
1889 QModelIndex index = m_view->currentIndex();
1890 return m_model->dateForCell(row: index.row(), column: index.column());
1891}
1892
1893void QCalendarWidgetPrivate::_q_prevMonthClicked()
1894{
1895 QDate currentDate = getCurrentDate().addMonths(months: -1, cal: m_model->m_calendar);
1896 updateCurrentPage(date: currentDate);
1897}
1898
1899void QCalendarWidgetPrivate::_q_nextMonthClicked()
1900{
1901 QDate currentDate = getCurrentDate().addMonths(months: 1, cal: m_model->m_calendar);
1902 updateCurrentPage(date: currentDate);
1903}
1904
1905void QCalendarWidgetPrivate::_q_yearEditingFinished()
1906{
1907 Q_Q(QCalendarWidget);
1908 yearEdit->hide();
1909 q->setFocusPolicy(oldFocusPolicy);
1910 qApp->removeEventFilter(obj: q);
1911 spaceHolder->changeSize(w: 0, h: 0);
1912 yearButton->show();
1913 QDate currentDate = getCurrentDate();
1914 int newYear = q->locale().toInt(s: yearEdit->text());
1915 currentDate = currentDate.addYears(years: newYear - currentDate.year(cal: m_model->m_calendar), cal: m_model->m_calendar);
1916 yearButton->setText(q->locale().toString(date: currentDate, format: u"yyyy", cal: m_model->m_calendar));
1917 updateCurrentPage(date: currentDate);
1918}
1919
1920void QCalendarWidgetPrivate::_q_yearClicked()
1921{
1922 Q_Q(QCalendarWidget);
1923 //show the spinbox on top of the button
1924 yearEdit->setGeometry(ax: yearButton->x(), ay: yearButton->y(),
1925 aw: yearEdit->sizeHint().width(), ah: yearButton->height());
1926 spaceHolder->changeSize(w: yearButton->width(), h: 0);
1927 yearButton->hide();
1928 oldFocusPolicy = q->focusPolicy();
1929 q->setFocusPolicy(Qt::NoFocus);
1930 yearEdit->show();
1931 qApp->installEventFilter(filterObj: q);
1932 yearEdit->raise();
1933 yearEdit->selectAll();
1934 yearEdit->setFocus(Qt::MouseFocusReason);
1935}
1936
1937void QCalendarWidgetPrivate::showMonth(int year, int month)
1938{
1939 if (m_model->m_shownYear == year && m_model->m_shownMonth == month)
1940 return;
1941 Q_Q(QCalendarWidget);
1942 m_model->showMonth(year, month);
1943 updateNavigationBar();
1944 emit q->currentPageChanged(year, month);
1945 m_view->internalUpdate();
1946 cachedSizeHint = QSize();
1947 update();
1948 updateMonthMenu();
1949}
1950
1951void QCalendarWidgetPrivate::updateNavigationBar()
1952{
1953 Q_Q(QCalendarWidget);
1954
1955 QString monthName = m_model->monthName(locale: q->locale(), month: m_model->m_shownMonth);
1956
1957 monthButton->setText(monthName);
1958 yearEdit->setValue(m_model->m_shownYear);
1959 yearButton->setText(yearEdit->text());
1960}
1961
1962void QCalendarWidgetPrivate::update()
1963{
1964 QDate currentDate = m_model->m_date;
1965 int row, column;
1966 m_model->cellForDate(date: currentDate, row: &row, column: &column);
1967 QModelIndex idx;
1968 m_selection->clear();
1969 if (row != -1 && column != -1) {
1970 idx = m_model->index(row, column);
1971 m_selection->setCurrentIndex(index: idx, command: QItemSelectionModel::SelectCurrent);
1972 }
1973}
1974
1975void QCalendarWidgetPrivate::paintCell(QPainter *painter, const QRect &rect, QDate date) const
1976{
1977 Q_Q(const QCalendarWidget);
1978 q->paintCell(painter, rect, date);
1979}
1980
1981void QCalendarWidgetPrivate::_q_slotShowDate(QDate date)
1982{
1983 updateCurrentPage(date);
1984}
1985
1986void QCalendarWidgetPrivate::_q_slotChangeDate(QDate date)
1987{
1988 _q_slotChangeDate(date, changeMonth: true);
1989}
1990
1991void QCalendarWidgetPrivate::_q_slotChangeDate(QDate date, bool changeMonth)
1992{
1993 QDate oldDate = m_model->m_date;
1994 m_model->setDate(date);
1995 QDate newDate = m_model->m_date;
1996 if (changeMonth)
1997 showMonth(year: newDate.year(cal: m_model->m_calendar), month: newDate.month(cal: m_model->m_calendar));
1998 if (oldDate != newDate) {
1999 update();
2000 Q_Q(QCalendarWidget);
2001 m_navigator->setDate(newDate);
2002 emit q->selectionChanged();
2003 }
2004}
2005
2006void QCalendarWidgetPrivate::_q_editingFinished()
2007{
2008 Q_Q(QCalendarWidget);
2009 emit q->activated(date: m_model->m_date);
2010}
2011
2012/*!
2013 \class QCalendarWidget
2014 \brief The QCalendarWidget class provides a monthly based
2015 calendar widget allowing the user to select a date.
2016 \since 4.2
2017
2018 \ingroup advanced
2019 \inmodule QtWidgets
2020
2021 \image fusion-calendarwidget.png
2022
2023 The widget is initialized with the current month and year, but
2024 QCalendarWidget provides several public slots to change the year
2025 and month that is shown.
2026
2027 By default, today's date is selected, and the user can select a
2028 date using both mouse and keyboard. The currently selected date
2029 can be retrieved using the selectedDate() function. It is
2030 possible to constrain the user selection to a given date range by
2031 setting the minimumDate and maximumDate properties.
2032 Alternatively, both properties can be set in one go using the
2033 setDateRange() convenience slot. Set the \l selectionMode
2034 property to NoSelection to prohibit the user from selecting at
2035 all. Note that a date also can be selected programmatically using
2036 the setSelectedDate() slot.
2037
2038 The currently displayed month and year can be retrieved using the
2039 monthShown() and yearShown() functions, respectively.
2040
2041 A newly created calendar widget uses abbreviated day names, and
2042 both Saturdays and Sundays are marked in red. The calendar grid is
2043 not visible. The week numbers are displayed, and the first column
2044 day is the first day of the week for the calendar's locale.
2045
2046 The notation of the days can be altered to a single letter
2047 abbreviations ("M" for "Monday") by setting the
2048 horizontalHeaderFormat property to
2049 QCalendarWidget::SingleLetterDayNames. Setting the same property
2050 to QCalendarWidget::LongDayNames makes the header display the
2051 complete day names. The week numbers can be removed by setting
2052 the verticalHeaderFormat property to
2053 QCalendarWidget::NoVerticalHeader. The calendar grid can be
2054 turned on by setting the gridVisible property to true using the
2055 setGridVisible() function:
2056
2057 \table
2058 \row \li
2059 \image qcalendarwidget-grid.png
2060 \row \li
2061 \snippet code/src_gui_widgets_qcalendarwidget.cpp 0
2062 \endtable
2063
2064 Finally, the day in the first column can be altered using the
2065 setFirstDayOfWeek() function.
2066
2067 The QCalendarWidget class also provides three signals,
2068 selectionChanged(), activated() and currentPageChanged() making it
2069 possible to respond to user interaction.
2070
2071 The rendering of the headers, weekdays or single days can be
2072 largely customized by setting QTextCharFormat's for some special
2073 weekday, a special date or for the rendering of the headers.
2074
2075 Only a subset of the properties in QTextCharFormat are used by the
2076 calendar widget. Currently, the foreground, background and font
2077 properties are used to determine the rendering of individual cells
2078 in the widget.
2079
2080 \sa QDate, QDateEdit, QTextCharFormat
2081*/
2082
2083/*!
2084 \enum QCalendarWidget::SelectionMode
2085
2086 This enum describes the types of selection offered to the user for
2087 selecting dates in the calendar.
2088
2089 \value NoSelection Dates cannot be selected.
2090 \value SingleSelection Single dates can be selected.
2091
2092 \sa selectionMode
2093*/
2094
2095/*!
2096 Constructs a calendar widget with the given \a parent.
2097
2098 The widget is initialized with the current month and year, and the
2099 currently selected date is today.
2100
2101 \sa setCurrentPage()
2102*/
2103QCalendarWidget::QCalendarWidget(QWidget *parent)
2104 : QWidget(*new QCalendarWidgetPrivate, parent, { })
2105{
2106 Q_D(QCalendarWidget);
2107
2108 setAutoFillBackground(true);
2109 setBackgroundRole(QPalette::Window);
2110
2111 QVBoxLayout *layoutV = new QVBoxLayout(this);
2112 layoutV->setContentsMargins(QMargins());
2113 d->m_model = new QCalendarModel(this);
2114 QTextCharFormat fmt;
2115 fmt.setForeground(QBrush(Qt::red));
2116 d->m_model->m_dayFormats.insert(day: Qt::Saturday, v: fmt);
2117 d->m_model->m_dayFormats.insert(day: Qt::Sunday, v: fmt);
2118 d->m_view = new QCalendarView(this);
2119 d->m_view->setObjectName("qt_calendar_calendarview"_L1);
2120 d->m_view->setModel(d->m_model);
2121 d->m_model->setView(d->m_view);
2122 d->m_view->setSelectionBehavior(QAbstractItemView::SelectItems);
2123 d->m_view->setSelectionMode(QAbstractItemView::SingleSelection);
2124 d->m_view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
2125 d->m_view->horizontalHeader()->setSectionsClickable(false);
2126 d->m_view->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
2127 d->m_view->verticalHeader()->setSectionsClickable(false);
2128 d->m_selection = d->m_view->selectionModel();
2129 d->createNavigationBar(widget: this);
2130 d->m_view->setFrameStyle(QFrame::NoFrame);
2131 d->m_delegate = new QCalendarDelegate(d, this);
2132 d->m_view->setItemDelegate(d->m_delegate);
2133 d->update();
2134 d->updateNavigationBar();
2135 setFocusPolicy(Qt::StrongFocus);
2136 setFocusProxy(d->m_view);
2137 setSizePolicy(hor: QSizePolicy::Preferred, ver: QSizePolicy::Preferred);
2138
2139 connect(sender: d->m_view, SIGNAL(showDate(QDate)),
2140 receiver: this, SLOT(_q_slotShowDate(QDate)));
2141 connect(sender: d->m_view, SIGNAL(changeDate(QDate,bool)),
2142 receiver: this, SLOT(_q_slotChangeDate(QDate,bool)));
2143 connect(sender: d->m_view, SIGNAL(clicked(QDate)),
2144 receiver: this, SIGNAL(clicked(QDate)));
2145 connect(sender: d->m_view, SIGNAL(editingFinished()),
2146 receiver: this, SLOT(_q_editingFinished()));
2147
2148 connect(sender: d->prevMonth, SIGNAL(clicked(bool)),
2149 receiver: this, SLOT(_q_prevMonthClicked()));
2150 connect(sender: d->nextMonth, SIGNAL(clicked(bool)),
2151 receiver: this, SLOT(_q_nextMonthClicked()));
2152 connect(sender: d->yearButton, SIGNAL(clicked(bool)),
2153 receiver: this, SLOT(_q_yearClicked()));
2154 connect(sender: d->monthMenu, SIGNAL(triggered(QAction*)),
2155 receiver: this, SLOT(_q_monthChanged(QAction*)));
2156 connect(sender: d->yearEdit, SIGNAL(editingFinished()),
2157 receiver: this, SLOT(_q_yearEditingFinished()));
2158
2159 layoutV->setContentsMargins(QMargins());
2160 layoutV->setSpacing(0);
2161 layoutV->addWidget(d->navBarBackground);
2162 layoutV->addWidget(d->m_view);
2163
2164 d->m_navigator = new QCalendarTextNavigator(this);
2165 setDateEditEnabled(true);
2166}
2167
2168/*!
2169 Destroys the calendar widget.
2170*/
2171QCalendarWidget::~QCalendarWidget()
2172{
2173}
2174
2175/*!
2176 \reimp
2177*/
2178QSize QCalendarWidget::sizeHint() const
2179{
2180 return minimumSizeHint();
2181}
2182
2183/*!
2184 \reimp
2185*/
2186QSize QCalendarWidget::minimumSizeHint() const
2187{
2188 Q_D(const QCalendarWidget);
2189 if (d->cachedSizeHint.isValid())
2190 return d->cachedSizeHint;
2191
2192 ensurePolished();
2193
2194 int w = 0;
2195 int h = 0;
2196
2197 int end = 53;
2198 int rows = 7;
2199 int cols = 8;
2200
2201 QStyleOption option;
2202 option.initFrom(w: this);
2203 const int marginH = (style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &option) + 1) * 2;
2204
2205 if (horizontalHeaderFormat() == QCalendarWidget::NoHorizontalHeader) {
2206 rows = 6;
2207 } else {
2208 for (int i = 1; i <= 7; i++) {
2209 QFontMetrics fm(d->m_model->formatForCell(row: 0, col: i).font());
2210 w = qMax(a: w, b: fm.horizontalAdvance(d->m_model->dayName(day: d->m_model->dayOfWeekForColumn(column: i))) + marginH);
2211 h = qMax(a: h, b: fm.height());
2212 }
2213 }
2214
2215 if (verticalHeaderFormat() == QCalendarWidget::NoVerticalHeader) {
2216 cols = 7;
2217 } else {
2218 for (int i = 1; i <= 6; i++) {
2219 QFontMetrics fm(d->m_model->formatForCell(row: i, col: 0).font());
2220 for (int j = 1; j < end; j++)
2221 w = qMax(a: w, b: fm.horizontalAdvance(QString::number(j)) + marginH);
2222 h = qMax(a: h, b: fm.height());
2223 }
2224 }
2225
2226 QFontMetrics fm(d->m_model->formatForCell(row: 1, col: 1).font());
2227 for (int i = 1; i <= end; i++) {
2228 w = qMax(a: w, b: fm.horizontalAdvance(QString::number(i)) + marginH);
2229 h = qMax(a: h, b: fm.height());
2230 }
2231
2232 if (d->m_view->showGrid()) {
2233 // hardcoded in tableview
2234 w += 1;
2235 h += 1;
2236 }
2237
2238 w += 1; // default column span
2239
2240 h = qMax(a: h, b: d->m_view->verticalHeader()->minimumSectionSize());
2241 w = qMax(a: w, b: d->m_view->horizontalHeader()->minimumSectionSize());
2242
2243 //add the size of the header.
2244 QSize headerSize(0, 0);
2245 if (d->navBarVisible) {
2246 int headerH = d->navBarBackground->sizeHint().height();
2247 int headerW = 0;
2248
2249 headerW += d->prevMonth->sizeHint().width();
2250 headerW += d->nextMonth->sizeHint().width();
2251
2252 QFontMetrics fm = d->monthButton->fontMetrics();
2253 int monthW = 0;
2254 for (int i = 1; i < 12; i++) {
2255 QString monthName = d->m_model->monthName(locale: locale(), month: i);
2256 monthW = qMax(a: monthW, b: fm.boundingRect(text: monthName).width());
2257 }
2258 const int buttonDecoMargin = d->monthButton->sizeHint().width() - fm.boundingRect(text: d->monthButton->text()).width();
2259 headerW += monthW + buttonDecoMargin;
2260
2261 fm = d->yearButton->fontMetrics();
2262 headerW += fm.boundingRect(text: "5555"_L1).width() + buttonDecoMargin;
2263
2264 headerSize = QSize(headerW, headerH);
2265 }
2266 w *= cols;
2267 w = qMax(a: headerSize.width(), b: w);
2268 h = (h * rows) + headerSize.height();
2269 QMargins cm = contentsMargins();
2270 w += cm.left() + cm.right();
2271 h += cm.top() + cm.bottom();
2272 d->cachedSizeHint = QSize(w, h);
2273 return d->cachedSizeHint;
2274}
2275
2276/*!
2277 Paints the cell specified by the given \a date, using the given \a painter and \a rect.
2278*/
2279
2280void QCalendarWidget::paintCell(QPainter *painter, const QRect &rect, QDate date) const
2281{
2282 Q_D(const QCalendarWidget);
2283 d->m_delegate->paintCell(painter, rect, date);
2284}
2285
2286/*!
2287 \property QCalendarWidget::selectedDate
2288 \brief the currently selected date.
2289
2290 The selected date must be within the date range specified by the
2291 minimumDate and maximumDate properties. By default, the selected
2292 date is the current date.
2293
2294 \sa setDateRange()
2295*/
2296
2297QDate QCalendarWidget::selectedDate() const
2298{
2299 Q_D(const QCalendarWidget);
2300 return d->m_model->m_date;
2301}
2302
2303void QCalendarWidget::setSelectedDate(QDate date)
2304{
2305 Q_D(QCalendarWidget);
2306 if (d->m_model->m_date == date && date == d->getCurrentDate())
2307 return;
2308
2309 if (!date.isValid())
2310 return;
2311
2312 d->m_model->setDate(date);
2313 d->update();
2314 QDate newDate = d->m_model->m_date;
2315 QCalendar cal = d->m_model->m_calendar;
2316 d->showMonth(year: newDate.year(cal), month: newDate.month(cal));
2317 emit selectionChanged();
2318}
2319
2320/*!
2321 Returns the year of the currently displayed month. Months are
2322 numbered from 1 to 12.
2323
2324 \sa monthShown(), setCurrentPage()
2325*/
2326
2327int QCalendarWidget::yearShown() const
2328{
2329 Q_D(const QCalendarWidget);
2330 return d->m_model->m_shownYear;
2331}
2332
2333/*!
2334 Returns the currently displayed month. Months are numbered from 1 to
2335 12.
2336
2337 \sa yearShown(), setCurrentPage()
2338*/
2339
2340int QCalendarWidget::monthShown() const
2341{
2342 Q_D(const QCalendarWidget);
2343 return d->m_model->m_shownMonth;
2344}
2345
2346/*!
2347 Displays the given \a month of the given \a year without changing
2348 the selected date. Use the setSelectedDate() function to alter the
2349 selected date.
2350
2351 The currently displayed month and year can be retrieved using the
2352 monthShown() and yearShown() functions respectively.
2353
2354 \sa yearShown(), monthShown(), showPreviousMonth(), showNextMonth(),
2355 showPreviousYear(), showNextYear()
2356*/
2357
2358void QCalendarWidget::setCurrentPage(int year, int month)
2359{
2360 Q_D(QCalendarWidget);
2361 QDate currentDate = d->getCurrentDate();
2362 QCalendar cal = d->m_model->m_calendar;
2363 int day = currentDate.day(cal);
2364 int daysInMonths = cal.daysInMonth(month, year);
2365 if (day > daysInMonths)
2366 day = daysInMonths;
2367
2368 d->showMonth(year, month);
2369
2370 QDate newDate(year, month, day, d->m_model->m_calendar);
2371 int row = -1, col = -1;
2372 d->m_model->cellForDate(date: newDate, row: &row, column: &col);
2373 if (row != -1 && col != -1) {
2374 d->m_view->selectionModel()->setCurrentIndex(index: d->m_model->index(row, column: col),
2375 command: QItemSelectionModel::NoUpdate);
2376 }
2377}
2378
2379/*!
2380 Shows the next month relative to the currently displayed
2381 month. Note that the selected date is not changed.
2382
2383 \sa showPreviousMonth(), setCurrentPage(), setSelectedDate()
2384*/
2385
2386void QCalendarWidget::showNextMonth()
2387{
2388 Q_D(const QCalendarWidget);
2389 int year = yearShown();
2390 int month = monthShown();
2391 if (month == d->m_model->m_calendar.maximumMonthsInYear()) {
2392 ++year;
2393 month = 1;
2394 } else {
2395 ++month;
2396 }
2397 setCurrentPage(year, month);
2398}
2399
2400/*!
2401 Shows the previous month relative to the currently displayed
2402 month. Note that the selected date is not changed.
2403
2404 \sa showNextMonth(), setCurrentPage(), setSelectedDate()
2405*/
2406
2407void QCalendarWidget::showPreviousMonth()
2408{
2409 Q_D(const QCalendarWidget);
2410
2411 int year = yearShown();
2412 int month = monthShown();
2413 if (month == 1) {
2414 --year;
2415 month = d->m_model->m_calendar.maximumMonthsInYear();
2416 } else {
2417 --month;
2418 }
2419 setCurrentPage(year, month);
2420}
2421
2422/*!
2423 Shows the currently displayed month in the \e next year relative
2424 to the currently displayed year. Note that the selected date is
2425 not changed.
2426
2427 \sa showPreviousYear(), setCurrentPage(), setSelectedDate()
2428*/
2429
2430void QCalendarWidget::showNextYear()
2431{
2432 int year = yearShown();
2433 int month = monthShown();
2434 ++year;
2435 setCurrentPage(year, month);
2436}
2437
2438/*!
2439 Shows the currently displayed month in the \e previous year
2440 relative to the currently displayed year. Note that the selected
2441 date is not changed.
2442
2443 \sa showNextYear(), setCurrentPage(), setSelectedDate()
2444*/
2445
2446void QCalendarWidget::showPreviousYear()
2447{
2448 int year = yearShown();
2449 int month = monthShown();
2450 --year;
2451 setCurrentPage(year, month);
2452}
2453
2454/*!
2455 Shows the month of the selected date.
2456
2457 \sa selectedDate(), setCurrentPage()
2458*/
2459void QCalendarWidget::showSelectedDate()
2460{
2461 Q_D(const QCalendarWidget);
2462
2463 QDate currentDate = selectedDate();
2464 setCurrentPage(year: currentDate.year(cal: d->m_model->m_calendar), month: currentDate.month(cal: d->m_model->m_calendar));
2465}
2466
2467/*!
2468 Shows the month of the today's date.
2469
2470 \sa selectedDate(), setCurrentPage()
2471*/
2472void QCalendarWidget::showToday()
2473{
2474 Q_D(const QCalendarWidget);
2475
2476 QDate currentDate = QDate::currentDate();
2477 setCurrentPage(year: currentDate.year(cal: d->m_model->m_calendar), month: currentDate.month(cal: d->m_model->m_calendar));
2478}
2479
2480/*!
2481 \property QCalendarWidget::minimumDate
2482 \brief the minimum date of the currently specified date range.
2483
2484 The user will not be able to select a date that is before the
2485 currently set minimum date.
2486
2487 \table
2488 \row
2489 \li \image qcalendarwidget-minimum.png
2490 \row
2491 \li
2492 \snippet code/src_gui_widgets_qcalendarwidget.cpp 1
2493 \endtable
2494
2495 When setting a minimum date, the maximumDate and selectedDate
2496 properties are adjusted if the selection range becomes invalid. If
2497 the provided date is not a valid QDate object, the
2498 setMinimumDate() function does nothing.
2499
2500 The default minimum date is November 25, 4714 BCE.
2501 You can restore this default by calling clearMinimumDate() (since Qt 6.6).
2502
2503 \sa setDateRange()
2504*/
2505
2506QDate QCalendarWidget::minimumDate() const
2507{
2508 Q_D(const QCalendarWidget);
2509 return d->m_model->m_minimumDate;
2510}
2511
2512void QCalendarWidget::setMinimumDate(QDate date)
2513{
2514 Q_D(QCalendarWidget);
2515 if (!date.isValid() || d->m_model->m_minimumDate == date)
2516 return;
2517
2518 QDate oldDate = d->m_model->m_date;
2519 d->m_model->setMinimumDate(date);
2520 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2521 d->updateMonthMenu();
2522 QDate newDate = d->m_model->m_date;
2523 if (oldDate != newDate) {
2524 d->update();
2525 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2526 d->m_navigator->setDate(newDate);
2527 emit selectionChanged();
2528 }
2529}
2530
2531void QCalendarWidget::clearMinimumDate()
2532{
2533 setMinimumDate(QDate::fromJulianDay(jd_: 1));
2534}
2535
2536/*!
2537 \property QCalendarWidget::maximumDate
2538 \brief the maximum date of the currently specified date range.
2539
2540 The user will not be able to select a date which is after the
2541 currently set maximum date.
2542
2543 \table
2544 \row
2545 \li \image qcalendarwidget-maximum.png
2546 \row
2547 \li
2548 \snippet code/src_gui_widgets_qcalendarwidget.cpp 2
2549 \endtable
2550
2551 When setting a maximum date, the minimumDate and selectedDate
2552 properties are adjusted if the selection range becomes invalid. If
2553 the provided date is not a valid QDate object, the
2554 setMaximumDate() function does nothing.
2555
2556 The default maximum date is December 31, 9999 CE.
2557 You can restore this default by calling clearMaximumDate() (since Qt 6.6).
2558
2559 \sa setDateRange()
2560*/
2561
2562QDate QCalendarWidget::maximumDate() const
2563{
2564 Q_D(const QCalendarWidget);
2565 return d->m_model->m_maximumDate;
2566}
2567
2568void QCalendarWidget::setMaximumDate(QDate date)
2569{
2570 Q_D(QCalendarWidget);
2571 if (!date.isValid() || d->m_model->m_maximumDate == date)
2572 return;
2573
2574 QDate oldDate = d->m_model->m_date;
2575 d->m_model->setMaximumDate(date);
2576 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2577 d->updateMonthMenu();
2578 QDate newDate = d->m_model->m_date;
2579 if (oldDate != newDate) {
2580 d->update();
2581 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2582 d->m_navigator->setDate(newDate);
2583 emit selectionChanged();
2584 }
2585}
2586
2587void QCalendarWidget::clearMaximumDate()
2588{
2589 setMaximumDate(QDate(9999, 12, 31));
2590}
2591
2592/*!
2593 Defines a date range by setting the minimumDate and maximumDate
2594 properties.
2595
2596 The date range restricts the user selection, i.e. the user can
2597 only select dates within the specified date range. Note that
2598
2599 \snippet code/src_gui_widgets_qcalendarwidget.cpp 3
2600
2601 is analogous to
2602
2603 \snippet code/src_gui_widgets_qcalendarwidget.cpp 4
2604
2605 If either the \a min or \a max parameters are not valid QDate
2606 objects, this function does nothing.
2607
2608 \sa setMinimumDate(), setMaximumDate()
2609*/
2610
2611void QCalendarWidget::setDateRange(QDate min, QDate max)
2612{
2613 Q_D(QCalendarWidget);
2614 if (d->m_model->m_minimumDate == min && d->m_model->m_maximumDate == max)
2615 return;
2616 if (!min.isValid() || !max.isValid())
2617 return;
2618
2619 QDate oldDate = d->m_model->m_date;
2620 d->m_model->setRange(min, max);
2621 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2622 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2623 d->updateMonthMenu();
2624 QDate newDate = d->m_model->m_date;
2625 if (oldDate != newDate) {
2626 d->update();
2627 d->showMonth(year: newDate.year(cal: d->m_model->m_calendar), month: newDate.month(cal: d->m_model->m_calendar));
2628 d->m_navigator->setDate(newDate);
2629 emit selectionChanged();
2630 }
2631}
2632
2633
2634/*! \enum QCalendarWidget::HorizontalHeaderFormat
2635
2636 This enum type defines the various formats the horizontal header can display.
2637
2638 \value SingleLetterDayNames The header displays a single letter abbreviation for day names (e.g. M for Monday).
2639 \value ShortDayNames The header displays a short abbreviation for day names (e.g. Mon for Monday).
2640 \value LongDayNames The header displays complete day names (e.g. Monday).
2641 \value NoHorizontalHeader The header is hidden.
2642
2643 \sa horizontalHeaderFormat(), VerticalHeaderFormat
2644*/
2645
2646/*!
2647 \property QCalendarWidget::horizontalHeaderFormat
2648 \brief the format of the horizontal header.
2649
2650 The default value is QCalendarWidget::ShortDayNames.
2651*/
2652
2653void QCalendarWidget::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format)
2654{
2655 Q_D(QCalendarWidget);
2656 if (d->m_model->m_horizontalHeaderFormat == format)
2657 return;
2658
2659 d->m_model->setHorizontalHeaderFormat(format);
2660 d->cachedSizeHint = QSize();
2661 d->m_view->viewport()->update();
2662 d->m_view->updateGeometry();
2663}
2664
2665QCalendarWidget::HorizontalHeaderFormat QCalendarWidget::horizontalHeaderFormat() const
2666{
2667 Q_D(const QCalendarWidget);
2668 return d->m_model->m_horizontalHeaderFormat;
2669}
2670
2671
2672/*!
2673 \enum QCalendarWidget::VerticalHeaderFormat
2674
2675 This enum type defines the various formats the vertical header can display.
2676
2677 \value ISOWeekNumbers The header displays ISO week numbers as described by \l QDate::weekNumber().
2678 \value NoVerticalHeader The header is hidden.
2679
2680 \sa verticalHeaderFormat(), HorizontalHeaderFormat
2681*/
2682
2683/*!
2684 \property QCalendarWidget::verticalHeaderFormat
2685 \brief the format of the vertical header.
2686
2687 The default value is QCalendarWidget::ISOWeekNumber.
2688*/
2689
2690QCalendarWidget::VerticalHeaderFormat QCalendarWidget::verticalHeaderFormat() const
2691{
2692 Q_D(const QCalendarWidget);
2693 bool shown = d->m_model->weekNumbersShown();
2694 if (shown)
2695 return QCalendarWidget::ISOWeekNumbers;
2696 return QCalendarWidget::NoVerticalHeader;
2697}
2698
2699void QCalendarWidget::setVerticalHeaderFormat(QCalendarWidget::VerticalHeaderFormat format)
2700{
2701 Q_D(QCalendarWidget);
2702 bool show = false;
2703 if (format == QCalendarWidget::ISOWeekNumbers)
2704 show = true;
2705 if (d->m_model->weekNumbersShown() == show)
2706 return;
2707 d->m_model->setWeekNumbersShown(show);
2708 d->cachedSizeHint = QSize();
2709 d->m_view->viewport()->update();
2710 d->m_view->updateGeometry();
2711}
2712
2713/*!
2714 \property QCalendarWidget::gridVisible
2715 \brief whether the table grid is displayed.
2716
2717 \table
2718 \row
2719 \li \inlineimage qcalendarwidget-grid.png
2720 \row
2721 \li
2722 \snippet code/src_gui_widgets_qcalendarwidget.cpp 5
2723 \endtable
2724
2725 The default value is false.
2726*/
2727
2728bool QCalendarWidget::isGridVisible() const
2729{
2730 Q_D(const QCalendarWidget);
2731 return d->m_view->showGrid();
2732}
2733
2734QCalendar QCalendarWidget::calendar() const
2735{
2736 Q_D(const QCalendarWidget);
2737 return d->m_model->m_calendar;
2738}
2739
2740void QCalendarWidget::setCalendar(QCalendar c)
2741{
2742 Q_D(QCalendarWidget);
2743 d->m_model->setCalendar(c);
2744 d->updateMonthMenuNames();
2745 d->yearEdit->setMinimum(d->m_model->m_minimumDate.year(cal: d->m_model->m_calendar));
2746 d->yearEdit->setMaximum(d->m_model->m_maximumDate.year(cal: d->m_model->m_calendar));
2747 d->updateNavigationBar();
2748}
2749
2750void QCalendarWidget::setGridVisible(bool show)
2751{
2752 Q_D(QCalendarWidget);
2753 d->m_view->setShowGrid(show);
2754 d->cachedSizeHint = QSize();
2755 d->m_view->viewport()->update();
2756 d->m_view->updateGeometry();
2757}
2758
2759/*!
2760 \property QCalendarWidget::selectionMode
2761 \brief the type of selection the user can make in the calendar
2762
2763 When this property is set to SingleSelection, the user can select a date
2764 within the minimum and maximum allowed dates, using either the mouse or
2765 the keyboard.
2766
2767 When the property is set to NoSelection, the user will be unable to select
2768 dates, but they can still be selected programmatically. Note that the date
2769 that is selected when the property is set to NoSelection will still be
2770 the selected date of the calendar.
2771
2772 The default value is SingleSelection.
2773*/
2774
2775QCalendarWidget::SelectionMode QCalendarWidget::selectionMode() const
2776{
2777 Q_D(const QCalendarWidget);
2778 return d->m_view->readOnly ? QCalendarWidget::NoSelection : QCalendarWidget::SingleSelection;
2779}
2780
2781void QCalendarWidget::setSelectionMode(SelectionMode mode)
2782{
2783 Q_D(QCalendarWidget);
2784 d->m_view->readOnly = (mode == QCalendarWidget::NoSelection);
2785 d->setNavigatorEnabled(isDateEditEnabled() && (selectionMode() != QCalendarWidget::NoSelection));
2786 d->update();
2787}
2788
2789/*!
2790 \property QCalendarWidget::firstDayOfWeek
2791 \brief a value identifying the day displayed in the first column.
2792
2793 By default, the day displayed in the first column
2794 is the first day of the week for the calendar's locale.
2795*/
2796
2797void QCalendarWidget::setFirstDayOfWeek(Qt::DayOfWeek dayOfWeek)
2798{
2799 Q_D(QCalendarWidget);
2800 if ((Qt::DayOfWeek)d->m_model->firstColumnDay() == dayOfWeek)
2801 return;
2802
2803 d->m_model->setFirstColumnDay(dayOfWeek);
2804 d->update();
2805}
2806
2807Qt::DayOfWeek QCalendarWidget::firstDayOfWeek() const
2808{
2809 Q_D(const QCalendarWidget);
2810 return (Qt::DayOfWeek)d->m_model->firstColumnDay();
2811}
2812
2813/*!
2814 Returns the text char format for rendering the header.
2815*/
2816QTextCharFormat QCalendarWidget::headerTextFormat() const
2817{
2818 Q_D(const QCalendarWidget);
2819 return d->m_model->m_headerFormat;
2820}
2821
2822/*!
2823 Sets the text char format for rendering the header to \a format.
2824 If you also set a weekday text format, this format's foreground and
2825 background color will take precedence over the header's format.
2826 The other formatting information will still be decided by
2827 the header's format.
2828*/
2829void QCalendarWidget::setHeaderTextFormat(const QTextCharFormat &format)
2830{
2831 Q_D(QCalendarWidget);
2832 d->m_model->m_headerFormat = format;
2833 d->cachedSizeHint = QSize();
2834 d->m_view->viewport()->update();
2835 d->m_view->updateGeometry();
2836}
2837
2838/*!
2839 Returns the text char format for rendering of day in the week \a dayOfWeek.
2840
2841 \sa headerTextFormat()
2842*/
2843QTextCharFormat QCalendarWidget::weekdayTextFormat(Qt::DayOfWeek dayOfWeek) const
2844{
2845 Q_D(const QCalendarWidget);
2846 return d->m_model->m_dayFormats.value(day: dayOfWeek);
2847}
2848
2849/*!
2850 Sets the text char format for rendering of day in the week \a dayOfWeek to \a format.
2851 The format will take precedence over the header format in case of foreground
2852 and background color. Other text formatting information is taken from the headers format.
2853
2854 \sa setHeaderTextFormat()
2855*/
2856void QCalendarWidget::setWeekdayTextFormat(Qt::DayOfWeek dayOfWeek, const QTextCharFormat &format)
2857{
2858 Q_D(QCalendarWidget);
2859 d->m_model->m_dayFormats[dayOfWeek] = format;
2860 d->cachedSizeHint = QSize();
2861 d->m_view->viewport()->update();
2862 d->m_view->updateGeometry();
2863}
2864
2865/*!
2866 Returns a QMap from QDate to QTextCharFormat showing all dates
2867 that use a special format that alters their rendering.
2868*/
2869QMap<QDate, QTextCharFormat> QCalendarWidget::dateTextFormat() const
2870{
2871 Q_D(const QCalendarWidget);
2872 return d->m_model->m_dateFormats;
2873}
2874
2875/*!
2876 Returns a QTextCharFormat for \a date. The char format can be
2877 empty if the date is not renderd specially.
2878*/
2879QTextCharFormat QCalendarWidget::dateTextFormat(QDate date) const
2880{
2881 Q_D(const QCalendarWidget);
2882 return d->m_model->m_dateFormats.value(key: date);
2883}
2884
2885/*!
2886 Sets the format used to render the given \a date to that specified by \a format.
2887
2888 If \a date is null, all date formats are cleared.
2889*/
2890void QCalendarWidget::setDateTextFormat(QDate date, const QTextCharFormat &format)
2891{
2892 Q_D(QCalendarWidget);
2893 if (date.isNull())
2894 d->m_model->m_dateFormats.clear();
2895 else
2896 d->m_model->m_dateFormats[date] = format;
2897 d->m_view->viewport()->update();
2898 d->m_view->updateGeometry();
2899}
2900
2901/*!
2902 \property QCalendarWidget::dateEditEnabled
2903 \brief whether the date edit popup is enabled
2904 \since 4.3
2905
2906 If this property is enabled, pressing a non-modifier key will cause a
2907 date edit to popup if the calendar widget has focus, allowing the user
2908 to specify a date in the form specified by the current locale.
2909
2910 By default, this property is enabled.
2911
2912 The date edit is simpler in appearance than QDateEdit, but allows the
2913 user to navigate between fields using the left and right cursor keys,
2914 increment and decrement individual fields using the up and down cursor
2915 keys, and enter values directly using the number keys.
2916
2917 \sa QCalendarWidget::dateEditAcceptDelay
2918*/
2919bool QCalendarWidget::isDateEditEnabled() const
2920{
2921 Q_D(const QCalendarWidget);
2922 return d->m_dateEditEnabled;
2923}
2924
2925void QCalendarWidget::setDateEditEnabled(bool enable)
2926{
2927 Q_D(QCalendarWidget);
2928 if (isDateEditEnabled() == enable)
2929 return;
2930
2931 d->m_dateEditEnabled = enable;
2932
2933 d->setNavigatorEnabled(enable && (selectionMode() != QCalendarWidget::NoSelection));
2934}
2935
2936/*!
2937 \property QCalendarWidget::dateEditAcceptDelay
2938 \brief the time an inactive date edit is shown before its contents are accepted
2939 \since 4.3
2940
2941 If the calendar widget's \l{dateEditEnabled}{date edit is enabled}, this
2942 property specifies the amount of time (in milliseconds) that the date edit
2943 remains open after the most recent user input. Once this time has elapsed,
2944 the date specified in the date edit is accepted and the popup is closed.
2945
2946 By default, the delay is defined to be 1500 milliseconds (1.5 seconds).
2947*/
2948int QCalendarWidget::dateEditAcceptDelay() const
2949{
2950 Q_D(const QCalendarWidget);
2951 return d->m_navigator->dateEditAcceptDelay();
2952}
2953
2954void QCalendarWidget::setDateEditAcceptDelay(int delay)
2955{
2956 Q_D(QCalendarWidget);
2957 d->m_navigator->setDateEditAcceptDelay(delay);
2958}
2959
2960/*!
2961 \since 4.4
2962
2963 Updates the cell specified by the given \a date unless updates
2964 are disabled or the cell is hidden.
2965
2966 \sa updateCells(), yearShown(), monthShown()
2967*/
2968void QCalendarWidget::updateCell(QDate date)
2969{
2970 if (Q_UNLIKELY(!date.isValid())) {
2971 qWarning(msg: "QCalendarWidget::updateCell: Invalid date");
2972 return;
2973 }
2974
2975 if (!isVisible())
2976 return;
2977
2978 Q_D(QCalendarWidget);
2979 int row, column;
2980 d->m_model->cellForDate(date, row: &row, column: &column);
2981 if (row == -1 || column == -1)
2982 return;
2983
2984 QModelIndex modelIndex = d->m_model->index(row, column);
2985 if (!modelIndex.isValid())
2986 return;
2987
2988 d->m_view->viewport()->update(d->m_view->visualRect(index: modelIndex));
2989}
2990
2991/*!
2992 \since 4.4
2993
2994 Updates all visible cells unless updates are disabled.
2995
2996 \sa updateCell()
2997*/
2998void QCalendarWidget::updateCells()
2999{
3000 Q_D(QCalendarWidget);
3001 if (isVisible())
3002 d->m_view->viewport()->update();
3003}
3004
3005/*!
3006 \fn void QCalendarWidget::selectionChanged()
3007
3008 This signal is emitted when the currently selected date is
3009 changed.
3010
3011 The currently selected date can be changed by the user using the
3012 mouse or keyboard, or by the programmer using setSelectedDate().
3013
3014 \sa selectedDate()
3015*/
3016
3017/*!
3018 \fn void QCalendarWidget::currentPageChanged(int year, int month)
3019
3020 This signal is emitted when the currently shown month is changed.
3021 The new \a year and \a month are passed as parameters.
3022
3023 \sa setCurrentPage()
3024*/
3025
3026/*!
3027 \fn void QCalendarWidget::activated(QDate date)
3028
3029 This signal is emitted whenever the user presses the Return or
3030 Enter key or double-clicks a \a date in the calendar
3031 widget.
3032*/
3033
3034/*!
3035 \fn void QCalendarWidget::clicked(QDate date)
3036
3037 This signal is emitted when a mouse button is clicked. The date
3038 the mouse was clicked on is specified by \a date. The signal is
3039 only emitted when clicked on a valid date, e.g., dates are not
3040 outside the minimumDate() and maximumDate(). If the selection mode
3041 is NoSelection, this signal will not be emitted.
3042
3043*/
3044
3045/*!
3046 \property QCalendarWidget::navigationBarVisible
3047 \brief whether the navigation bar is shown or not
3048
3049 \since 4.3
3050
3051 When this property is \c true (the default), the next month,
3052 previous month, month selection, year selection controls are
3053 shown on top.
3054
3055 When the property is set to false, these controls are hidden.
3056*/
3057
3058bool QCalendarWidget::isNavigationBarVisible() const
3059{
3060 Q_D(const QCalendarWidget);
3061 return d->navBarVisible;
3062}
3063
3064
3065void QCalendarWidget::setNavigationBarVisible(bool visible)
3066{
3067 Q_D(QCalendarWidget);
3068 d->navBarVisible = visible;
3069 d->cachedSizeHint = QSize();
3070 d->navBarBackground->setVisible(visible);
3071 updateGeometry();
3072}
3073
3074/*!
3075 \reimp
3076*/
3077bool QCalendarWidget::event(QEvent *event)
3078{
3079 Q_D(QCalendarWidget);
3080 switch (event->type()) {
3081 case QEvent::LayoutDirectionChange:
3082 d->updateButtonIcons();
3083 break;
3084 case QEvent::LocaleChange:
3085 d->m_model->setFirstColumnDay(locale().firstDayOfWeek());
3086 d->cachedSizeHint = QSize();
3087 d->updateMonthMenuNames();
3088 d->updateNavigationBar();
3089 d->m_view->updateGeometry();
3090 // TODO: fix this known bug of calendaring API:
3091 // Changing locale before calendar works, but reverse order causes
3092 // invalid month names (in C Locale apparently).
3093 break;
3094 case QEvent::FontChange:
3095 case QEvent::ApplicationFontChange:
3096 d->cachedSizeHint = QSize();
3097 d->m_view->updateGeometry();
3098 break;
3099 case QEvent::StyleChange:
3100 d->cachedSizeHint = QSize();
3101 d->m_view->updateGeometry();
3102 default:
3103 break;
3104 }
3105 return QWidget::event(event);
3106}
3107
3108/*!
3109 \reimp
3110*/
3111bool QCalendarWidget::eventFilter(QObject *watched, QEvent *event)
3112{
3113 Q_D(QCalendarWidget);
3114 if (event->type() == QEvent::MouseButtonPress && d->yearEdit->hasFocus()) {
3115 // We can get filtered press events that were intended for Qt Virtual Keyboard's
3116 // input panel (QQuickView), so we have to make sure that the window is indeed a QWidget - no static_cast.
3117 // In addition, as we have a event filter on the whole application we first make sure that the top level widget
3118 // of both this and the watched widget are the same to decide if we should finish the year edition.
3119 QWidget *tlw = window();
3120 QWidget *widget = qobject_cast<QWidget *>(o: watched);
3121 if (!widget || widget->window() != tlw)
3122 return QWidget::eventFilter(watched, event);
3123
3124 QPoint mousePos = widget->mapTo(tlw, static_cast<QMouseEvent *>(event)->position().toPoint());
3125 QRect geom = QRect(d->yearEdit->mapTo(tlw, QPoint(0, 0)), d->yearEdit->size());
3126 if (!geom.contains(p: mousePos)) {
3127 event->accept();
3128 d->_q_yearEditingFinished();
3129 setFocus();
3130 return true;
3131 }
3132 }
3133 return QWidget::eventFilter(watched, event);
3134}
3135
3136/*!
3137 \reimp
3138*/
3139void QCalendarWidget::mousePressEvent(QMouseEvent *event)
3140{
3141 setAttribute(Qt::WA_NoMouseReplay);
3142 QWidget::mousePressEvent(event);
3143 setFocus();
3144}
3145
3146/*!
3147 \reimp
3148*/
3149void QCalendarWidget::resizeEvent(QResizeEvent * event)
3150{
3151 Q_D(QCalendarWidget);
3152
3153 // XXX Should really use a QWidgetStack for yearEdit and yearButton,
3154 // XXX here we hide the year edit when the layout is likely to break
3155 // XXX the manual positioning of the yearEdit over the yearButton.
3156 if (d->yearEdit->isVisible() && event->size().width() != event->oldSize().width())
3157 d->_q_yearEditingFinished();
3158
3159 QWidget::resizeEvent(event);
3160}
3161
3162/*!
3163 \reimp
3164*/
3165void QCalendarWidget::keyPressEvent(QKeyEvent * event)
3166{
3167#if QT_CONFIG(shortcut)
3168 Q_D(QCalendarWidget);
3169 if (d->yearEdit->isVisible()&& event->matches(key: QKeySequence::Cancel)) {
3170 d->yearEdit->setValue(yearShown());
3171 d->_q_yearEditingFinished();
3172 return;
3173 }
3174#endif
3175 QWidget::keyPressEvent(event);
3176}
3177
3178QT_END_NAMESPACE
3179
3180#include "qcalendarwidget.moc"
3181#include "moc_qcalendarwidget.cpp"
3182

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