diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/gui/widgets/qcalendarwidget.cpp | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'src/gui/widgets/qcalendarwidget.cpp')
-rw-r--r-- | src/gui/widgets/qcalendarwidget.cpp | 3091 |
1 files changed, 3091 insertions, 0 deletions
diff --git a/src/gui/widgets/qcalendarwidget.cpp b/src/gui/widgets/qcalendarwidget.cpp new file mode 100644 index 0000000..92c12a5 --- /dev/null +++ b/src/gui/widgets/qcalendarwidget.cpp @@ -0,0 +1,3091 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcalendarwidget.h" + +#ifndef QT_NO_CALENDARWIDGET + +#include <qabstractitemmodel.h> +#include <qitemdelegate.h> +#include <qdatetime.h> +#include <qtableview.h> +#include <qlayout.h> +#include <qevent.h> +#include <qtextformat.h> +#include <qheaderview.h> +#include <private/qwidget_p.h> +#include <qpushbutton.h> +#include <qtoolbutton.h> +#include <qlabel.h> +#include <qspinbox.h> +#include <qmenu.h> +#include <qapplication.h> +#include <qbasictimer.h> +#include <qstylepainter.h> +#include <private/qcalendartextnavigator_p.h> + +QT_BEGIN_NAMESPACE + +enum { + RowCount = 6, + ColumnCount = 7, + HeaderColumn = 0, + HeaderRow = 0, + MinimumDayOffset = 1 +}; + +class QCalendarDateSectionValidator +{ +public: + + enum Section { + NextSection, + ThisSection, + PrevSection + }; + + QCalendarDateSectionValidator() {} + virtual ~QCalendarDateSectionValidator() {} + virtual Section handleKey(int key) = 0; + virtual QDate applyToDate(const QDate &date) const = 0; + virtual void setDate(const QDate &date) = 0; + virtual QString text() const = 0; + virtual QString text(const QDate &date, int repeat) const = 0; + + QLocale m_locale; + +protected: + QString highlightString(const QString &str, int pos) const; +private: +}; + +QString QCalendarDateSectionValidator::highlightString(const QString &str, int pos) const +{ + if (pos == 0) + return QLatin1String("<b>") + str + QLatin1String("</b>"); + int startPos = str.length() - pos; + return str.mid(0, startPos) + QLatin1String("<b>") + str.mid(startPos, pos) + QLatin1String("</b>"); + +} + +class QCalendarDayValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarDayValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int m_pos; + int m_day; + int m_oldDay; +}; + +QCalendarDayValidator::QCalendarDayValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_day(1), m_oldDay(1) +{ +} + +QCalendarDateSectionValidator::Section QCalendarDayValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_day; + if (m_day > 31) + m_day = 1; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_day; + if (m_day < 1) + m_day = 31; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 1; + + if (m_pos == 0) + m_day = m_oldDay; + else + m_day = m_day / 10; + //m_day = m_oldDay / 10 * 10 + m_day / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + if (m_pos == 0) + m_day = pressedKey; + else + m_day = m_day % 10 * 10 + pressedKey; + if (m_day > 31) + m_day = 31; + ++m_pos; + if (m_pos > 1) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarDayValidator::applyToDate(const QDate &date) const +{ + int day = m_day; + if (day < 1) + day = 1; + else if (day > 31) + day = 31; + if (day > date.daysInMonth()) + day = date.daysInMonth(); + return QDate(date.year(), date.month(), day); +} + +void QCalendarDayValidator::setDate(const QDate &date) +{ + m_day = m_oldDay = date.day(); + m_pos = 0; +} + +QString QCalendarDayValidator::text() const +{ + QString str; + if (m_day / 10 == 0) + str += QLatin1String("0"); + str += QString::number(m_day); + return highlightString(str, m_pos); +} + +QString QCalendarDayValidator::text(const QDate &date, int repeat) const +{ + if (repeat <= 1) { + return QString::number(date.day()); + } else if (repeat == 2) { + QString str; + if (date.day() / 10 == 0) + str += QLatin1String("0"); + return str + QString::number(date.day()); + } else if (repeat == 3) { + return m_locale.dayName(date.dayOfWeek(), QLocale::ShortFormat); + } else if (repeat >= 4) { + return m_locale.dayName(date.dayOfWeek(), QLocale::LongFormat); + } + return QString(); +} + +////////////////////////////////// + +class QCalendarMonthValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarMonthValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int m_pos; + int m_month; + int m_oldMonth; +}; + +QCalendarMonthValidator::QCalendarMonthValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_month(1), m_oldMonth(1) +{ +} + +QCalendarDateSectionValidator::Section QCalendarMonthValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_month; + if (m_month > 12) + m_month = 1; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_month; + if (m_month < 1) + m_month = 12; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 1; + + if (m_pos == 0) + m_month = m_oldMonth; + else + m_month = m_month / 10; + //m_month = m_oldMonth / 10 * 10 + m_month / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + if (m_pos == 0) + m_month = pressedKey; + else + m_month = m_month % 10 * 10 + pressedKey; + if (m_month > 12) + m_month = 12; + ++m_pos; + if (m_pos > 1) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarMonthValidator::applyToDate(const QDate &date) const +{ + int month = m_month; + if (month < 1) + month = 1; + else if (month > 12) + month = 12; + QDate newDate(date.year(), m_month, 1); + int day = date.day(); + if (day > newDate.daysInMonth()) + day = newDate.daysInMonth(); + return QDate(date.year(), month, day); +} + +void QCalendarMonthValidator::setDate(const QDate &date) +{ + m_month = m_oldMonth = date.month(); + m_pos = 0; +} + +QString QCalendarMonthValidator::text() const +{ + QString str; + if (m_month / 10 == 0) + str += QLatin1String("0"); + str += QString::number(m_month); + return highlightString(str, m_pos); +} + +QString QCalendarMonthValidator::text(const QDate &date, int repeat) const +{ + if (repeat <= 1) { + return QString::number(date.month()); + } else if (repeat == 2) { + QString str; + if (date.month() / 10 == 0) + str += QLatin1String("0"); + return str + QString::number(date.month()); + } else if (repeat == 3) { + return m_locale.standaloneMonthName(date.month(), QLocale::ShortFormat); + } else if (repeat >= 4) { + return m_locale.standaloneMonthName(date.month(), QLocale::LongFormat); + } + return QString(); +} + +////////////////////////////////// + +class QCalendarYearValidator : public QCalendarDateSectionValidator +{ + +public: + QCalendarYearValidator(); + virtual Section handleKey(int key); + virtual QDate applyToDate(const QDate &date) const; + virtual void setDate(const QDate &date); + virtual QString text() const; + virtual QString text(const QDate &date, int repeat) const; +private: + int pow10(int n); + int m_pos; + int m_year; + int m_oldYear; +}; + +QCalendarYearValidator::QCalendarYearValidator() + : QCalendarDateSectionValidator(), m_pos(0), m_year(2000), m_oldYear(2000) +{ +} + +int QCalendarYearValidator::pow10(int n) +{ + int power = 1; + for (int i = 0; i < n; i++) + power *= 10; + return power; +} + +QCalendarDateSectionValidator::Section QCalendarYearValidator::handleKey(int key) +{ + if (key == Qt::Key_Right || key == Qt::Key_Left) { + m_pos = 0; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Up) { + m_pos = 0; + ++m_year; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Down) { + m_pos = 0; + --m_year; + return QCalendarDateSectionValidator::ThisSection; + } else if (key == Qt::Key_Back || key == Qt::Key_Backspace) { + --m_pos; + if (m_pos < 0) + m_pos = 3; + + int pow = pow10(m_pos); + m_year = m_oldYear / pow * pow + m_year % (pow * 10) / 10; + + if (m_pos == 0) + return QCalendarDateSectionValidator::PrevSection; + return QCalendarDateSectionValidator::ThisSection; + } + if (key < Qt::Key_0 || key > Qt::Key_9) + return QCalendarDateSectionValidator::ThisSection; + int pressedKey = key - Qt::Key_0; + int pow = pow10(m_pos); + m_year = m_year / (pow * 10) * (pow * 10) + m_year % pow * 10 + pressedKey; + ++m_pos; + if (m_pos > 3) { + m_pos = 0; + return QCalendarDateSectionValidator::NextSection; + } + return QCalendarDateSectionValidator::ThisSection; +} + +QDate QCalendarYearValidator::applyToDate(const QDate &date) const +{ + int year = m_year; + if (year < 1) + year = 1; + QDate newDate(year, date.month(), 1); + int day = date.day(); + if (day > newDate.daysInMonth()) + day = newDate.daysInMonth(); + return QDate(year, date.month(), day); +} + +void QCalendarYearValidator::setDate(const QDate &date) +{ + m_year = m_oldYear = date.year(); + m_pos = 0; +} + +QString QCalendarYearValidator::text() const +{ + QString str; + int pow = 10; + for (int i = 0; i < 3; i++) { + if (m_year / pow == 0) + str += QLatin1String("0"); + pow *= 10; + } + str += QString::number(m_year); + return highlightString(str, m_pos); +} + +QString QCalendarYearValidator::text(const QDate &date, int repeat) const +{ + if (repeat < 4) { + QString str; + int year = date.year() % 100; + if (year / 10 == 0) + str = QLatin1String("0"); + return str + QString::number(year); + } + return QString::number(date.year()); +} + +/////////////////////////////////// + +class QCalendarDateValidator +{ +public: + QCalendarDateValidator(); + ~QCalendarDateValidator(); + + void handleKeyEvent(QKeyEvent *keyEvent); + QString currentText() const; + QDate currentDate() const { return m_currentDate; } + void setFormat(const QString &format); + void setInitialDate(const QDate &date); + + void setLocale(const QLocale &locale); + +private: + + struct SectionToken { + SectionToken(QCalendarDateSectionValidator *val, int rep) : validator(val), repeat(rep) {} + QCalendarDateSectionValidator *validator; + int repeat; + }; + + void toNextToken(); + void toPreviousToken(); + void applyToDate(); + + int countRepeat(const QString &str, int index) const; + void clear(); + + QStringList m_separators; + QList<SectionToken *> m_tokens; + QCalendarDateSectionValidator *m_yearValidator; + QCalendarDateSectionValidator *m_monthValidator; + QCalendarDateSectionValidator *m_dayValidator; + + SectionToken *m_currentToken; + + QDate m_initialDate; + QDate m_currentDate; + + QCalendarDateSectionValidator::Section m_lastSectionMove; +}; + +QCalendarDateValidator::QCalendarDateValidator() + : m_currentToken(0), m_lastSectionMove(QCalendarDateSectionValidator::ThisSection) +{ + m_initialDate = m_currentDate = QDate::currentDate(); + m_yearValidator = new QCalendarYearValidator(); + m_monthValidator = new QCalendarMonthValidator(); + m_dayValidator = new QCalendarDayValidator(); +} + +void QCalendarDateValidator::setLocale(const QLocale &locale) +{ + m_yearValidator->m_locale = locale; + m_monthValidator->m_locale = locale; + m_dayValidator->m_locale = locale; +} + +QCalendarDateValidator::~QCalendarDateValidator() +{ + delete m_yearValidator; + delete m_monthValidator; + delete m_dayValidator; + clear(); +} + +// from qdatetime.cpp +int QCalendarDateValidator::countRepeat(const QString &str, int index) const +{ + Q_ASSERT(index >= 0 && index < str.size()); + int count = 1; + const QChar ch = str.at(index); + while (index + count < str.size() && str.at(index + count) == ch) + ++count; + return count; +} + +void QCalendarDateValidator::setInitialDate(const QDate &date) +{ + m_yearValidator->setDate(date); + m_monthValidator->setDate(date); + m_dayValidator->setDate(date); + m_initialDate = date; + m_currentDate = date; + m_lastSectionMove = QCalendarDateSectionValidator::ThisSection; +} + +QString QCalendarDateValidator::currentText() const +{ + QString str; + QStringListIterator itSep(m_separators); + QListIterator<SectionToken *> itTok(m_tokens); + while (itSep.hasNext()) { + str += itSep.next(); + if (itTok.hasNext()) { + SectionToken *token = itTok.next(); + QCalendarDateSectionValidator *validator = token->validator; + if (m_currentToken == token) + str += validator->text(); + else + str += validator->text(m_currentDate, token->repeat); + } + } + return str; +} + +void QCalendarDateValidator::clear() +{ + QListIterator<SectionToken *> it(m_tokens); + while (it.hasNext()) + delete it.next(); + + m_tokens.clear(); + m_separators.clear(); + + m_currentToken = 0; +} + +void QCalendarDateValidator::setFormat(const QString &format) +{ + clear(); + + int pos = 0; + const QLatin1String quote("'"); + bool quoting = false; + QString separator; + while (pos < format.size()) { + QString mid = format.mid(pos); + int offset = 1; + + if (mid.startsWith(quote)) { + quoting = !quoting; + } else { + const QChar nextChar = format.at(pos); + if (quoting) { + separator += nextChar; + } else { + SectionToken *token = 0; + if (nextChar == QLatin1Char('d')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_dayValidator, offset); + } else if (nextChar == QLatin1Char('M')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_monthValidator, offset); + } else if (nextChar == QLatin1Char('y')) { + offset = qMin(4, countRepeat(format, pos)); + token = new SectionToken(m_yearValidator, offset); + } else { + separator += nextChar; + } + if (token) { + m_tokens.append(token); + m_separators.append(separator); + separator = QString(); + if (!m_currentToken) + m_currentToken = token; + + } + } + } + pos += offset; + } + m_separators += separator; +} + +void QCalendarDateValidator::applyToDate() +{ + m_currentDate = m_yearValidator->applyToDate(m_currentDate); + m_currentDate = m_monthValidator->applyToDate(m_currentDate); + m_currentDate = m_dayValidator->applyToDate(m_currentDate); +} + +void QCalendarDateValidator::toNextToken() +{ + const int idx = m_tokens.indexOf(m_currentToken); + if (idx == -1) + return; + if (idx + 1 >= m_tokens.count()) + m_currentToken = m_tokens.first(); + else + m_currentToken = m_tokens.at(idx + 1); +} + +void QCalendarDateValidator::toPreviousToken() +{ + const int idx = m_tokens.indexOf(m_currentToken); + if (idx == -1) + return; + if (idx - 1 < 0) + m_currentToken = m_tokens.last(); + else + m_currentToken = m_tokens.at(idx - 1); +} + +void QCalendarDateValidator::handleKeyEvent(QKeyEvent *keyEvent) +{ + if (!m_currentToken) + return; + + int key = keyEvent->key(); + if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) { + if (key == Qt::Key_Back || key == Qt::Key_Backspace) + toPreviousToken(); + } + if (key == Qt::Key_Right) + toNextToken(); + else if (key == Qt::Key_Left) + toPreviousToken(); + + m_lastSectionMove = m_currentToken->validator->handleKey(key); + + applyToDate(); + if (m_lastSectionMove == QCalendarDateSectionValidator::NextSection) + toNextToken(); + else if (m_lastSectionMove == QCalendarDateSectionValidator::PrevSection) + toPreviousToken(); +} + +QWidget *QCalendarTextNavigator::widget() const +{ + return m_widget; +} + +void QCalendarTextNavigator::setWidget(QWidget *widget) +{ + m_widget = widget; +} + +QDate QCalendarTextNavigator::date() const +{ + return m_date; +} + +void QCalendarTextNavigator::setDate(const QDate &date) +{ + m_date = date; +} + +void QCalendarTextNavigator::updateDateLabel() +{ + if (!m_widget) + return; + + m_acceptTimer.start(m_editDelay, this); + + m_dateText->setText(m_dateValidator->currentText()); + + QSize s = m_dateFrame->sizeHint(); + QRect r = m_widget->geometry(); // later, just the table section + QRect newRect((r.width() - s.width()) / 2, (r.height() - s.height()) / 2, s.width(), s.height()); + m_dateFrame->setGeometry(newRect); + // need to set palette after geometry update as phonestyle sets transparency + // effect in move event. + QPalette p = m_dateFrame->palette(); + p.setBrush(QPalette::Window, m_dateFrame->window()->palette().brush(QPalette::Window)); + m_dateFrame->setPalette(p); + + m_dateFrame->raise(); + m_dateFrame->show(); +} + +void QCalendarTextNavigator::applyDate() +{ + QDate date = m_dateValidator->currentDate(); + if (m_date == date) + return; + + m_date = date; + emit dateChanged(date); +} + +void QCalendarTextNavigator::createDateLabel() +{ + if (m_dateFrame) + return; + m_dateFrame = new QFrame(m_widget); + QVBoxLayout *vl = new QVBoxLayout; + m_dateText = new QLabel; + vl->addWidget(m_dateText); + m_dateFrame->setLayout(vl); + m_dateFrame->setFrameShadow(QFrame::Plain); + m_dateFrame->setFrameShape(QFrame::Box); + m_dateValidator = new QCalendarDateValidator(); + m_dateValidator->setLocale(m_widget->locale()); + m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat)); + m_dateValidator->setInitialDate(m_date); + + m_dateFrame->setAutoFillBackground(true); + m_dateFrame->setBackgroundRole(QPalette::Window); +} + +void QCalendarTextNavigator::removeDateLabel() +{ + if (!m_dateFrame) + return; + m_acceptTimer.stop(); + m_dateFrame->hide(); + m_dateFrame->deleteLater(); + delete m_dateValidator; + m_dateFrame = 0; + m_dateText = 0; + m_dateValidator = 0; +} + +bool QCalendarTextNavigator::eventFilter(QObject *o, QEvent *e) +{ + if (m_widget) { + if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { + QKeyEvent* ke = (QKeyEvent*)e; + if ((ke->text().length() > 0 && ke->text()[0].isPrint()) || m_dateFrame) { + if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Select) { + applyDate(); + emit editingFinished(); + removeDateLabel(); + } else if (ke->key() == Qt::Key_Escape) { + removeDateLabel(); + } else if (e->type() == QEvent::KeyPress) { + createDateLabel(); + m_dateValidator->handleKeyEvent(ke); + updateDateLabel(); + } + ke->accept(); + return true; + } + // If we are navigating let the user finish his date in old locate. + // If we change our mind and want it to update immediately simply uncomment below + /* + } else if (e->type() == QEvent::LocaleChange) { + if (m_dateValidator) { + m_dateValidator->setLocale(m_widget->locale()); + m_dateValidator->setFormat(m_widget->locale().dateFormat(QLocale::ShortFormat)); + updateDateLabel(); + } + */ + } + } + return QObject::eventFilter(o,e); +} + +void QCalendarTextNavigator::timerEvent(QTimerEvent *e) +{ + if (e->timerId() == m_acceptTimer.timerId()) { + applyDate(); + removeDateLabel(); + } +} + +int QCalendarTextNavigator::dateEditAcceptDelay() const +{ + return m_editDelay; +} + +void QCalendarTextNavigator::setDateEditAcceptDelay(int delay) +{ + m_editDelay = delay; +} + +class QCalendarView; + +class QCalendarModel : public QAbstractTableModel +{ + Q_OBJECT +public: + QCalendarModel(QObject *parent = 0); + + int rowCount(const QModelIndex &) const + { return RowCount + m_firstRow; } + int columnCount(const QModelIndex &) const + { return ColumnCount + m_firstColumn; } + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + beginInsertRows(parent, row, row + count - 1); + endInsertRows(); + return true; + } + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) + { + beginInsertColumns(parent, column, column + count - 1); + endInsertColumns(); + return true; + } + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) + { + beginRemoveRows(parent, row, row + count - 1); + endRemoveRows(); + return true; + } + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) + { + beginRemoveColumns(parent, column, column + count - 1); + endRemoveColumns(); + return true; + } + + void showMonth(int year, int month); + void setDate(const QDate &d); + + void setMinimumDate(const QDate &date); + void setMaximumDate(const QDate &date); + + void setRange(const QDate &min, const QDate &max); + + void setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format); + + void setFirstColumnDay(Qt::DayOfWeek dayOfWeek); + Qt::DayOfWeek firstColumnDay() const; + + bool weekNumbersShown() const; + void setWeekNumbersShown(bool show); + + QTextCharFormat formatForCell(int row, int col) const; + Qt::DayOfWeek dayOfWeekForColumn(int section) const; + int columnForDayOfWeek(Qt::DayOfWeek day) const; + QDate dateForCell(int row, int column) const; + void cellForDate(const QDate &date, int *row, int *column) const; + QString dayName(Qt::DayOfWeek day) const; + + void setView(QCalendarView *view) + { m_view = view; } + + void internalUpdate(); + QDate referenceDate() const; + int columnForFirstOfMonth(const QDate &date) const; + + int m_firstColumn; + int m_firstRow; + QDate m_date; + QDate m_minimumDate; + QDate m_maximumDate; + int m_shownYear; + int m_shownMonth; + Qt::DayOfWeek m_firstDay; + QCalendarWidget::HorizontalHeaderFormat m_horizontalHeaderFormat; + bool m_weekNumbersShown; + QMap<Qt::DayOfWeek, QTextCharFormat> m_dayFormats; + QMap<QDate, QTextCharFormat> m_dateFormats; + QTextCharFormat m_headerFormat; + QCalendarView *m_view; +}; + +class QCalendarView : public QTableView +{ + Q_OBJECT +public: + QCalendarView(QWidget *parent = 0); + + void internalUpdate() { updateGeometries(); } + void setReadOnly(bool enable); + virtual void keyboardSearch(const QString & search) { Q_UNUSED(search) } + +signals: + void showDate(const QDate &date); + void changeDate(const QDate &date, bool changeMonth); + void clicked(const QDate &date); + void editingFinished(); +protected: + QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers); + void mouseDoubleClickEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); +#ifndef QT_NO_WHEELEVENT + void wheelEvent(QWheelEvent *event); +#endif + void keyPressEvent(QKeyEvent *event); + bool event(QEvent *event); + + QDate handleMouseEvent(QMouseEvent *event); +public: + bool readOnly; +private: + bool validDateClicked; +#ifdef QT_KEYPAD_NAVIGATION + QDate origDate; +#endif +}; + +QCalendarModel::QCalendarModel(QObject *parent) + : QAbstractTableModel(parent) +{ + m_date = QDate::currentDate(); + m_minimumDate = QDate::fromJulianDay(1); + m_maximumDate = QDate(7999, 12, 31); + m_shownYear = m_date.year(); + m_shownMonth = m_date.month(); + m_firstDay = Qt::Sunday; + m_horizontalHeaderFormat = QCalendarWidget::ShortDayNames; + m_weekNumbersShown = true; + m_firstColumn = 1; + m_firstRow = 1; + m_view = 0; +} + +Qt::DayOfWeek QCalendarModel::dayOfWeekForColumn(int column) const +{ + int col = column - m_firstColumn; + if (col < 0 || col > 6) + return Qt::Sunday; + int day = m_firstDay + col; + if (day > 7) + day -= 7; + return Qt::DayOfWeek(day); +} + +int QCalendarModel::columnForDayOfWeek(Qt::DayOfWeek day) const +{ + if (day < 1 || day > 7) + return -1; + int column = (int)day - (int)m_firstDay; + if (column < 0) + column += 7; + return column + m_firstColumn; +} + +/* +This simple algorithm tries to generate a valid date from the month shown. +Some months don't contain a first day (e.g. Jan of -4713 year, +so QDate (-4713, 1, 1) would be invalid). In that case we try to generate +another valid date for that month. Later, returned date's day is the number of cells +calendar widget will reserve for days before referenceDate. (E.g. if returned date's +day is 16, that day will be placed in 3rd or 4th row, not in the 1st or 2nd row). +Depending on referenceData we can change behaviour of Oct 1582. If referenceDate is 1st +of Oct we render 1 Oct in 1st or 2nd row. If referenceDate is 17 of Oct we show always 16 +dates before 17 of Oct, and since this month contains the hole 5-14 Oct, the first of Oct +will be rendered in 2nd or 3rd row, showing more dates from previous month. +*/ +QDate QCalendarModel::referenceDate() const +{ + int refDay = 1; + while (refDay <= 31) { + QDate refDate(m_shownYear, m_shownMonth, refDay); + if (refDate.isValid()) + return refDate; + refDay += 1; + } + return QDate(); +} + +int QCalendarModel::columnForFirstOfMonth(const QDate &date) const +{ + return (columnForDayOfWeek(static_cast<Qt::DayOfWeek>(date.dayOfWeek())) - (date.day() % 7) + 8) % 7; +} + +QDate QCalendarModel::dateForCell(int row, int column) const +{ + if (row < m_firstRow || row > m_firstRow + RowCount - 1 || + column < m_firstColumn || column > m_firstColumn + ColumnCount - 1) + return QDate(); + const QDate refDate = referenceDate(); + if (!refDate.isValid()) + return QDate(); + + const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate); + if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset) + row -= 1; + + const int requestedDay = 7 * (row - m_firstRow) + column - columnForFirstOfShownMonth - refDate.day() + 1; + return refDate.addDays(requestedDay); +} + +void QCalendarModel::cellForDate(const QDate &date, int *row, int *column) const +{ + if (!row && !column) + return; + + if (row) + *row = -1; + if (column) + *column = -1; + + const QDate refDate = referenceDate(); + if (!refDate.isValid()) + return; + + const int columnForFirstOfShownMonth = columnForFirstOfMonth(refDate); + const int requestedPosition = refDate.daysTo(date) - m_firstColumn + columnForFirstOfShownMonth + refDate.day() - 1; + + int c = requestedPosition % 7; + int r = requestedPosition / 7; + if (c < 0) { + c += 7; + r -= 1; + } + + if (columnForFirstOfShownMonth - m_firstColumn < MinimumDayOffset) + r += 1; + + if (r < 0 || r > RowCount - 1 || c < 0 || c > ColumnCount - 1) + return; + + if (row) + *row = r + m_firstRow; + if (column) + *column = c + m_firstColumn; +} + +QString QCalendarModel::dayName(Qt::DayOfWeek day) const +{ + switch (m_horizontalHeaderFormat) { + case QCalendarWidget::SingleLetterDayNames: { + QString standaloneDayName = m_view->locale().standaloneDayName(day, QLocale::NarrowFormat); + if (standaloneDayName == m_view->locale().dayName(day, QLocale::NarrowFormat)) + return standaloneDayName.left(1); + return standaloneDayName; + } + case QCalendarWidget::ShortDayNames: + return m_view->locale().dayName(day, QLocale::ShortFormat); + case QCalendarWidget::LongDayNames: + return m_view->locale().dayName(day, QLocale::LongFormat); + default: + break; + } + return QString(); +} + +QTextCharFormat QCalendarModel::formatForCell(int row, int col) const +{ + QPalette pal; + QPalette::ColorGroup cg = QPalette::Active; + if (m_view) { + pal = m_view->palette(); + if (!m_view->isEnabled()) + cg = QPalette::Disabled; + else if (!m_view->isActiveWindow()) + cg = QPalette::Inactive; + } + + QTextCharFormat format; + format.setFont(m_view->font()); + bool header = (m_weekNumbersShown && col == HeaderColumn) + || (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow); + format.setBackground(pal.brush(cg, header ? QPalette::AlternateBase : QPalette::Base)); + format.setForeground(pal.brush(cg, QPalette::Text)); + if (header) { + format.merge(m_headerFormat); + } + + if (col >= m_firstColumn && col < m_firstColumn + ColumnCount) { + Qt::DayOfWeek dayOfWeek = dayOfWeekForColumn(col); + if (m_dayFormats.contains(dayOfWeek)) + format.merge(m_dayFormats.value(dayOfWeek)); + } + + if (!header) { + QDate date = dateForCell(row, col); + format.merge(m_dateFormats.value(date)); + if(date < m_minimumDate || date > m_maximumDate) + format.setBackground(pal.brush(cg, QPalette::Window)); + if (m_shownMonth != date.month()) + format.setForeground(pal.brush(QPalette::Disabled, QPalette::Text)); + } + return format; +} + +QVariant QCalendarModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::TextAlignmentRole) + return (int) Qt::AlignCenter; + + int row = index.row(); + int column = index.column(); + + if(role == Qt::DisplayRole) { + if (m_weekNumbersShown && column == HeaderColumn + && row >= m_firstRow && row < m_firstRow + RowCount) { + QDate date = dateForCell(row, columnForDayOfWeek(Qt::Monday)); + if (date.isValid()) + return date.weekNumber(); + } + if (m_horizontalHeaderFormat != QCalendarWidget::NoHorizontalHeader && row == HeaderRow + && column >= m_firstColumn && column < m_firstColumn + ColumnCount) + return dayName(dayOfWeekForColumn(column)); + QDate date = dateForCell(row, column); + if (date.isValid()) + return date.day(); + return QString(); + } + + QTextCharFormat fmt = formatForCell(row, column); + if (role == Qt::BackgroundColorRole) + return fmt.background().color(); + if (role == Qt::TextColorRole) + return fmt.foreground().color(); + if (role == Qt::FontRole) + return fmt.font(); + if (role == Qt::ToolTipRole) + return fmt.toolTip(); + return QVariant(); +} + +Qt::ItemFlags QCalendarModel::flags(const QModelIndex &index) const +{ + QDate date = dateForCell(index.row(), index.column()); + if (!date.isValid()) + return QAbstractTableModel::flags(index); + if (date < m_minimumDate) + return 0; + if (date > m_maximumDate) + return 0; + return QAbstractTableModel::flags(index); +} + +void QCalendarModel::setDate(const QDate &d) +{ + m_date = d; + if (m_date < m_minimumDate) + m_date = m_minimumDate; + else if (m_date > m_maximumDate) + m_date = m_maximumDate; +} + +void QCalendarModel::showMonth(int year, int month) +{ + if (m_shownYear == year && m_shownMonth == month) + return; + + m_shownYear = year; + m_shownMonth = month; + + internalUpdate(); +} + +void QCalendarModel::setMinimumDate(const QDate &d) +{ + if (!d.isValid() || d == m_minimumDate) + return; + + m_minimumDate = d; + if (m_maximumDate < m_minimumDate) + m_maximumDate = m_minimumDate; + if (m_date < m_minimumDate) + m_date = m_minimumDate; + internalUpdate(); +} + +void QCalendarModel::setMaximumDate(const QDate &d) +{ + if (!d.isValid() || d == m_maximumDate) + return; + + m_maximumDate = d; + if (m_minimumDate > m_maximumDate) + m_minimumDate = m_maximumDate; + if (m_date > m_maximumDate) + m_date = m_maximumDate; + internalUpdate(); +} + +void QCalendarModel::setRange(const QDate &min, const QDate &max) +{ + m_minimumDate = min; + m_maximumDate = max; + if (m_minimumDate > m_maximumDate) + qSwap(m_minimumDate, m_maximumDate); + if (m_date < m_minimumDate) + m_date = m_minimumDate; + if (m_date > m_maximumDate) + m_date = m_maximumDate; + internalUpdate(); +} + +void QCalendarModel::internalUpdate() +{ + QModelIndex begin = index(0, 0); + QModelIndex end = index(m_firstRow + RowCount - 1, m_firstColumn + ColumnCount - 1); + emit dataChanged(begin, end); + emit headerDataChanged(Qt::Vertical, 0, m_firstRow + RowCount - 1); + emit headerDataChanged(Qt::Horizontal, 0, m_firstColumn + ColumnCount - 1); +} + +void QCalendarModel::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format) +{ + if (m_horizontalHeaderFormat == format) + return; + + int oldFormat = m_horizontalHeaderFormat; + m_horizontalHeaderFormat = format; + if (oldFormat == QCalendarWidget::NoHorizontalHeader) { + m_firstRow = 1; + insertRow(0); + } else if (m_horizontalHeaderFormat == QCalendarWidget::NoHorizontalHeader) { + m_firstRow = 0; + removeRow(0); + } + internalUpdate(); +} + +void QCalendarModel::setFirstColumnDay(Qt::DayOfWeek dayOfWeek) +{ + if (m_firstDay == dayOfWeek) + return; + + m_firstDay = dayOfWeek; + internalUpdate(); +} + +Qt::DayOfWeek QCalendarModel::firstColumnDay() const +{ + return m_firstDay; +} + +bool QCalendarModel::weekNumbersShown() const +{ + return m_weekNumbersShown; +} + +void QCalendarModel::setWeekNumbersShown(bool show) +{ + if (m_weekNumbersShown == show) + return; + + m_weekNumbersShown = show; + if (show) { + m_firstColumn = 1; + insertColumn(0); + } else { + m_firstColumn = 0; + removeColumn(0); + } + internalUpdate(); +} + +QCalendarView::QCalendarView(QWidget *parent) + : QTableView(parent), + readOnly(false), + validDateClicked(false) +{ + setTabKeyNavigation(false); + setShowGrid(false); + verticalHeader()->setVisible(false); + horizontalHeader()->setVisible(false); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +QModelIndex QCalendarView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) + return QTableView::moveCursor(cursorAction, modifiers); + + if (readOnly) + return currentIndex(); + + QModelIndex index = currentIndex(); + QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(index.row(), index.column()); + switch (cursorAction) { + case QAbstractItemView::MoveUp: + currentDate = currentDate.addDays(-7); + break; + case QAbstractItemView::MoveDown: + currentDate = currentDate.addDays(7); + break; + case QAbstractItemView::MoveLeft: + currentDate = currentDate.addDays(isRightToLeft() ? 1 : -1); + break; + case QAbstractItemView::MoveRight: + currentDate = currentDate.addDays(isRightToLeft() ? -1 : 1); + break; + case QAbstractItemView::MoveHome: + currentDate = QDate(currentDate.year(), currentDate.month(), 1); + break; + case QAbstractItemView::MoveEnd: + currentDate = QDate(currentDate.year(), currentDate.month(), currentDate.daysInMonth()); + break; + case QAbstractItemView::MovePageUp: + currentDate = currentDate.addMonths(-1); + break; + case QAbstractItemView::MovePageDown: + currentDate = currentDate.addMonths(1); + break; + case QAbstractItemView::MoveNext: + case QAbstractItemView::MovePrevious: + return currentIndex(); + default: + break; + } + emit changeDate(currentDate, true); + return currentIndex(); +} + +void QCalendarView::keyPressEvent(QKeyEvent *event) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (event->key() == Qt::Key_Select) { + if (QApplication::keypadNavigationEnabled()) { + if (!hasEditFocus()) { + setEditFocus(true); + return; + } + } + } else if (event->key() == Qt::Key_Back) { + if (QApplication::keypadNavigationEnabled() && hasEditFocus()) { + if (qobject_cast<QCalendarModel *>(model())) { + emit changeDate(origDate, true); //changes selection back to origDate, but doesn't activate + setEditFocus(false); + return; + } + } + } +#endif + + if (!readOnly) { + switch (event->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Select: + emit editingFinished(); + return; + default: + break; + } + } + QTableView::keyPressEvent(event); +} + +#ifndef QT_NO_WHEELEVENT +void QCalendarView::wheelEvent(QWheelEvent *event) +{ + const int numDegrees = event->delta() / 8; + const int numSteps = numDegrees / 15; + const QModelIndex index = currentIndex(); + QDate currentDate = static_cast<QCalendarModel*>(model())->dateForCell(index.row(), index.column()); + currentDate = currentDate.addMonths(-numSteps); + emit showDate(currentDate); +} +#endif + +bool QCalendarView::event(QEvent *event) +{ +#ifdef QT_KEYPAD_NAVIGATION + if (event->type() == QEvent::FocusIn) { + if (QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model())) { + origDate = calendarModel->m_date; + } + } +#endif + + return QTableView::event(event); +} + +QDate QCalendarView::handleMouseEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) + return QDate(); + + QPoint pos = event->pos(); + QModelIndex index = indexAt(pos); + QDate date = calendarModel->dateForCell(index.row(), index.column()); + if (date.isValid() && date >= calendarModel->m_minimumDate + && date <= calendarModel->m_maximumDate) { + return date; + } + return QDate(); +} + +void QCalendarView::mouseDoubleClickEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseDoubleClickEvent(event); + return; + } + + if (readOnly) + return; + + QDate date = handleMouseEvent(event); + validDateClicked = false; + if (date == calendarModel->m_date && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { + emit editingFinished(); + } +} + +void QCalendarView::mousePressEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mousePressEvent(event); + return; + } + + if (readOnly) + return; + + if (event->button() != Qt::LeftButton) + return; + + QDate date = handleMouseEvent(event); + if (date.isValid()) { + validDateClicked = true; + int row = -1, col = -1; + static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col); + if (row != -1 && col != -1) { + selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate); + } + } else { + validDateClicked = false; + event->ignore(); + } +} + +void QCalendarView::mouseMoveEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseMoveEvent(event); + return; + } + + if (readOnly) + return; + + if (validDateClicked) { + QDate date = handleMouseEvent(event); + if (date.isValid()) { + int row = -1, col = -1; + static_cast<QCalendarModel *>(model())->cellForDate(date, &row, &col); + if (row != -1 && col != -1) { + selectionModel()->setCurrentIndex(model()->index(row, col), QItemSelectionModel::NoUpdate); + } + } + } else { + event->ignore(); + } +} + +void QCalendarView::mouseReleaseEvent(QMouseEvent *event) +{ + QCalendarModel *calendarModel = qobject_cast<QCalendarModel *>(model()); + if (!calendarModel) { + QTableView::mouseReleaseEvent(event); + return; + } + + if (event->button() != Qt::LeftButton) + return; + + if (readOnly) + return; + + if (validDateClicked) { + QDate date = handleMouseEvent(event); + if (date.isValid()) { + emit changeDate(date, true); + emit clicked(date); + if (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) + emit editingFinished(); + } + validDateClicked = false; + } else { + event->ignore(); + } +} + +class QCalendarDelegate : public QItemDelegate +{ + Q_OBJECT +public: + QCalendarDelegate(QCalendarWidgetPrivate *w, QObject *parent = 0) + : QItemDelegate(parent), calendarWidgetPrivate(w) + { } + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const; + +private: + QCalendarWidgetPrivate *calendarWidgetPrivate; + mutable QStyleOptionViewItemV4 storedOption; +}; + +//Private tool button class +class QCalToolButton: public QToolButton +{ +public: + QCalToolButton(QWidget * parent) + : QToolButton(parent) + { } +protected: + void paintEvent(QPaintEvent *e) + { + Q_UNUSED(e) + +#ifndef Q_WS_MAC + QStyleOptionToolButton opt; + initStyleOption(&opt); + + if (opt.state & QStyle::State_MouseOver || isDown()) { + //act as normal button + setPalette(QPalette()); + } else { + //set the highlight color for button text + QPalette toolPalette = palette(); + toolPalette.setColor(QPalette::ButtonText, toolPalette.color(QPalette::HighlightedText)); + setPalette(toolPalette); + } +#endif + QToolButton::paintEvent(e); + } +}; + +class QPrevNextCalButton : public QToolButton +{ + Q_OBJECT +public: + QPrevNextCalButton(QWidget *parent) : QToolButton(parent) {} +protected: + void paintEvent(QPaintEvent *) { + QStylePainter painter(this); + QStyleOptionToolButton opt; + initStyleOption(&opt); + opt.state &= ~QStyle::State_HasFocus; + painter.drawComplexControl(QStyle::CC_ToolButton, opt); + } +}; + +class QCalendarWidgetPrivate : public QWidgetPrivate +{ + Q_DECLARE_PUBLIC(QCalendarWidget) +public: + QCalendarWidgetPrivate(); + + void showMonth(int year, int month); + void update(); + void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const; + + void _q_slotShowDate(const QDate &date); + void _q_slotChangeDate(const QDate &date); + void _q_slotChangeDate(const QDate &date, bool changeMonth); + void _q_editingFinished(); + void _q_monthChanged(QAction*); + void _q_prevMonthClicked(); + void _q_nextMonthClicked(); + void _q_yearEditingFinished(); + void _q_yearClicked(); + + void createNavigationBar(QWidget *widget); + void updateButtonIcons(); + void updateMonthMenu(); + void updateMonthMenuNames(); + void updateNavigationBar(); + void updateCurrentPage(const QDate &newDate); + inline QDate getCurrentDate(); + void setNavigatorEnabled(bool enable); + + QCalendarModel *m_model; + QCalendarView *m_view; + QCalendarDelegate *m_delegate; + QItemSelectionModel *m_selection; + QCalendarTextNavigator *m_navigator; + bool m_dateEditEnabled; + + QToolButton *nextMonth; + QToolButton *prevMonth; + QCalToolButton *monthButton; + QMenu *monthMenu; + QMap<int, QAction *> monthToAction; + QCalToolButton *yearButton; + QSpinBox *yearEdit; + QWidget *navBarBackground; + QSpacerItem *spaceHolder; + + bool navBarVisible; + mutable QSize cachedSizeHint; + Qt::FocusPolicy oldFocusPolicy; +}; + +void QCalendarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QDate date = calendarWidgetPrivate->m_model->dateForCell(index.row(), index.column()); + if (date.isValid()) { + storedOption = option; + QRect rect = option.rect; + calendarWidgetPrivate->paintCell(painter, rect, date); + } else { + QItemDelegate::paint(painter, option, index); + } +} + +void QCalendarDelegate::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + storedOption.rect = rect; + int row = -1; + int col = -1; + calendarWidgetPrivate->m_model->cellForDate(date, &row, &col); + QModelIndex idx = calendarWidgetPrivate->m_model->index(row, col); + QItemDelegate::paint(painter, storedOption, idx); +} + +QCalendarWidgetPrivate::QCalendarWidgetPrivate() + : QWidgetPrivate() +{ + m_model = 0; + m_view = 0; + m_delegate = 0; + m_selection = 0; + m_navigator = 0; + m_dateEditEnabled = false; + navBarVisible = true; + oldFocusPolicy = Qt::StrongFocus; +} + +void QCalendarWidgetPrivate::setNavigatorEnabled(bool enable) +{ + Q_Q(QCalendarWidget); + + bool navigatorEnabled = (m_navigator->widget() != 0); + if (enable == navigatorEnabled) + return; + + if (enable) { + m_navigator->setWidget(q); + q->connect(m_navigator, SIGNAL(dateChanged(QDate)), + q, SLOT(_q_slotChangeDate(QDate))); + q->connect(m_navigator, SIGNAL(editingFinished()), + q, SLOT(_q_editingFinished())); + m_view->installEventFilter(m_navigator); + } else { + m_navigator->setWidget(0); + q->disconnect(m_navigator, SIGNAL(dateChanged(QDate)), + q, SLOT(_q_slotChangeDate(QDate))); + q->disconnect(m_navigator, SIGNAL(editingFinished()), + q, SLOT(_q_editingFinished())); + m_view->removeEventFilter(m_navigator); + } +} + +void QCalendarWidgetPrivate::createNavigationBar(QWidget *widget) +{ + Q_Q(QCalendarWidget); + navBarBackground = new QWidget(widget); + navBarBackground->setObjectName(QLatin1String("qt_calendar_navigationbar")); + navBarBackground->setAutoFillBackground(true); + navBarBackground->setBackgroundRole(QPalette::Highlight); + + prevMonth = new QPrevNextCalButton(navBarBackground); + nextMonth = new QPrevNextCalButton(navBarBackground); + prevMonth->setAutoRaise(true); + nextMonth->setAutoRaise(true); + prevMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + nextMonth->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + nextMonth->setAutoRaise(true); + updateButtonIcons(); + prevMonth->setAutoRepeat(true); + nextMonth->setAutoRepeat(true); + + monthButton = new QCalToolButton(navBarBackground); + monthButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + monthButton->setAutoRaise(true); + monthButton->setPopupMode(QToolButton::InstantPopup); + monthMenu = new QMenu(monthButton); + for (int i = 1; i <= 12; i++) { + QString monthName(q->locale().standaloneMonthName(i, QLocale::LongFormat)); + QAction *act = monthMenu->addAction(monthName); + act->setData(i); + monthToAction[i] = act; + } + monthButton->setMenu(monthMenu); + yearButton = new QCalToolButton(navBarBackground); + yearButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + yearButton->setAutoRaise(true); + yearEdit = new QSpinBox(navBarBackground); + + QFont font = q->font(); + font.setBold(true); + monthButton->setFont(font); + yearButton->setFont(font); + yearEdit->setFrame(false); + yearEdit->setMinimum(m_model->m_minimumDate.year()); + yearEdit->setMaximum(m_model->m_maximumDate.year()); + yearEdit->hide(); + spaceHolder = new QSpacerItem(0,0); + + QHBoxLayout *headerLayout = new QHBoxLayout; + headerLayout->setMargin(0); + headerLayout->setSpacing(0); + headerLayout->addWidget(prevMonth); + headerLayout->insertStretch(headerLayout->count()); + headerLayout->addWidget(monthButton); + headerLayout->addItem(spaceHolder); + headerLayout->addWidget(yearButton); + headerLayout->insertStretch(headerLayout->count()); + headerLayout->addWidget(nextMonth); + navBarBackground->setLayout(headerLayout); + + yearEdit->setFocusPolicy(Qt::StrongFocus); + prevMonth->setFocusPolicy(Qt::NoFocus); + nextMonth->setFocusPolicy(Qt::NoFocus); + yearButton->setFocusPolicy(Qt::NoFocus); + monthButton->setFocusPolicy(Qt::NoFocus); + + //set names for the header controls. + prevMonth->setObjectName(QLatin1String("qt_calendar_prevmonth")); + nextMonth->setObjectName(QLatin1String("qt_calendar_nextmonth")); + monthButton->setObjectName(QLatin1String("qt_calendar_monthbutton")); + yearButton->setObjectName(QLatin1String("qt_calendar_yearbutton")); + yearEdit->setObjectName(QLatin1String("qt_calendar_yearedit")); + + updateMonthMenu(); + showMonth(m_model->m_date.year(), m_model->m_date.month()); +} + +void QCalendarWidgetPrivate::updateButtonIcons() +{ + Q_Q(QCalendarWidget); + prevMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowRight : QStyle::SP_ArrowLeft, 0, q)); + nextMonth->setIcon(q->style()->standardIcon(q->isRightToLeft() ? QStyle::SP_ArrowLeft : QStyle::SP_ArrowRight, 0, q)); +} + +void QCalendarWidgetPrivate::updateMonthMenu() +{ + int beg = 1, end = 12; + bool prevEnabled = true; + bool nextEnabled = true; + if (m_model->m_shownYear == m_model->m_minimumDate.year()) { + beg = m_model->m_minimumDate.month(); + if (m_model->m_shownMonth == m_model->m_minimumDate.month()) + prevEnabled = false; + } + if (m_model->m_shownYear == m_model->m_maximumDate.year()) { + end = m_model->m_maximumDate.month(); + if (m_model->m_shownMonth == m_model->m_maximumDate.month()) + nextEnabled = false; + } + prevMonth->setEnabled(prevEnabled); + nextMonth->setEnabled(nextEnabled); + for (int i = 1; i <= 12; i++) { + bool monthEnabled = true; + if (i < beg || i > end) + monthEnabled = false; + monthToAction[i]->setEnabled(monthEnabled); + } +} + +void QCalendarWidgetPrivate::updateMonthMenuNames() +{ + Q_Q(QCalendarWidget); + + for (int i = 1; i <= 12; i++) { + QString monthName(q->locale().standaloneMonthName(i, QLocale::LongFormat)); + monthToAction[i]->setText(monthName); + } +} + +void QCalendarWidgetPrivate::updateCurrentPage(const QDate &date) +{ + Q_Q(QCalendarWidget); + + QDate newDate = date; + QDate minDate = q->minimumDate(); + QDate maxDate = q->maximumDate(); + if (minDate.isValid()&& minDate.daysTo(newDate) < 0) + newDate = minDate; + if (maxDate.isValid()&& maxDate.daysTo(newDate) > 0) + newDate = maxDate; + showMonth(newDate.year(), newDate.month()); + int row = -1, col = -1; + m_model->cellForDate(newDate, &row, &col); + if (row != -1 && col != -1) + { + m_view->selectionModel()->setCurrentIndex(m_model->index(row, col), + QItemSelectionModel::NoUpdate); + } +} + +void QCalendarWidgetPrivate::_q_monthChanged(QAction *act) +{ + monthButton->setText(act->text()); + QDate currentDate = getCurrentDate(); + QDate newDate = currentDate.addMonths(act->data().toInt()-currentDate.month()); + updateCurrentPage(newDate); +} + +QDate QCalendarWidgetPrivate::getCurrentDate() +{ + QModelIndex index = m_view->currentIndex(); + return m_model->dateForCell(index.row(), index.column()); +} + +void QCalendarWidgetPrivate::_q_prevMonthClicked() +{ + QDate currentDate = getCurrentDate().addMonths(-1); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_nextMonthClicked() +{ + QDate currentDate = getCurrentDate().addMonths(1); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_yearEditingFinished() +{ + Q_Q(QCalendarWidget); + yearButton->setText(yearEdit->text()); + yearEdit->hide(); + q->setFocusPolicy(oldFocusPolicy); + qApp->removeEventFilter(q); + spaceHolder->changeSize(0, 0); + yearButton->show(); + QDate currentDate = getCurrentDate(); + currentDate = currentDate.addYears(yearEdit->text().toInt() - currentDate.year()); + updateCurrentPage(currentDate); +} + +void QCalendarWidgetPrivate::_q_yearClicked() +{ + Q_Q(QCalendarWidget); + //show the spinbox on top of the button + yearEdit->setGeometry(yearButton->x(), yearButton->y(), + yearEdit->sizeHint().width(), yearButton->height()); + spaceHolder->changeSize(yearButton->width(), 0); + yearButton->hide(); + oldFocusPolicy = q->focusPolicy(); + q->setFocusPolicy(Qt::NoFocus); + yearEdit->show(); + qApp->installEventFilter(q); + yearEdit->raise(); + yearEdit->selectAll(); + yearEdit->setFocus(Qt::MouseFocusReason); +} + +void QCalendarWidgetPrivate::showMonth(int year, int month) +{ + if (m_model->m_shownYear == year && m_model->m_shownMonth == month) + return; + Q_Q(QCalendarWidget); + m_model->showMonth(year, month); + updateNavigationBar(); + emit q->currentPageChanged(year, month); + m_view->internalUpdate(); + cachedSizeHint = QSize(); + update(); + updateMonthMenu(); +} + +void QCalendarWidgetPrivate::updateNavigationBar() +{ + Q_Q(QCalendarWidget); + + QString monthName = q->locale().standaloneMonthName(m_model->m_shownMonth, QLocale::LongFormat); + + monthButton->setText(monthName); + yearButton->setText(QString::number(m_model->m_shownYear)); + yearEdit->setValue(m_model->m_shownYear); +} + +void QCalendarWidgetPrivate::update() +{ + QDate currentDate = m_model->m_date; + int row, column; + m_model->cellForDate(currentDate, &row, &column); + QModelIndex idx; + m_selection->clear(); + if (row != -1 && column != -1) { + idx = m_model->index(row, column); + m_selection->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent); + } +} + +void QCalendarWidgetPrivate::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + Q_Q(const QCalendarWidget); + q->paintCell(painter, rect, date); +} + +void QCalendarWidgetPrivate::_q_slotShowDate(const QDate &date) +{ + updateCurrentPage(date); +} + +void QCalendarWidgetPrivate::_q_slotChangeDate(const QDate &date) +{ + _q_slotChangeDate(date, true); +} + +void QCalendarWidgetPrivate::_q_slotChangeDate(const QDate &date, bool changeMonth) +{ + QDate oldDate = m_model->m_date; + m_model->setDate(date); + QDate newDate = m_model->m_date; + if (changeMonth) + showMonth(newDate.year(), newDate.month()); + if (oldDate != newDate) { + update(); + Q_Q(QCalendarWidget); + m_navigator->setDate(newDate); + emit q->selectionChanged(); + } +} + +void QCalendarWidgetPrivate::_q_editingFinished() +{ + Q_Q(QCalendarWidget); + emit q->activated(m_model->m_date); +} + +/*! + \class QCalendarWidget + \brief The QCalendarWidget class provides a monthly based + calendar widget allowing the user to select a date. + \since 4.2 + \mainclass + \ingroup advanced + + \image cleanlooks-calendarwidget.png + + The widget is initialized with the current month and year, but + QCalendarWidget provides several public slots to change the year + and month that is shown. The currently displayed month and year + can be retrieved using the currentPageMonth() and currentPageYear() + functions, respectively. + + By default, today's date is selected, and the user can select a + date using both mouse and keyboard. The currently selected date + can be retrieved using the selectedDate() function. It is + possible to constrain the user selection to a given date range by + setting the minimumDate and maximumDate properties. + Alternatively, both properties can be set in one go using the + setDateRange() convenience slot. Set the \l selectionMode + property to NoSelection to prohibit the user from selecting at + all. Note that a date also can be selected programmatically using + the setSelectedDate() slot. + + A newly created calendar widget uses abbreviated day names, and + both Saturdays and Sundays are marked in red. The calendar grid is + not visible. The week numbers are displayed, and the first column + day is Sunday. + + The notation of the days can be altered to a single letter + abbreviations ("M" for "Monday") by setting the + horizontalHeaderFormat property to + QCalendarWidget::SingleLetterDayNames. Setting the same property + to QCalendarWidget::LongDayNames makes the header display the + complete day names. The week numbers can be removed by setting + the verticalHeaderFormat property to + QCalendarWidget::NoVerticalHeader. The calendar grid can be + turned on by setting the gridVisible property to true using the + setGridVisible() function: + + \table + \row \o + \image qcalendarwidget-grid.png + \row \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 0 + \endtable + + Finally, the day in the first column can be altered using the + setFirstDayOfWeek() function. + + The QCalendarWidget class also provides three signals, + selectionChanged(), activated() and currentPageChanged() making it + possible to respond to user interaction. + + The rendering of the headers, weekdays or single days can be + largely customized by setting QTextCharFormat's for some special + weekday, a special date or for the rendering of the headers. + + Only a subset of the properties in QTextCharFormat are used by the + calendar widget. Currently, the foreground, background and font + properties are used to determine the rendering of individual cells + in the widget. + + \sa QDate, QDateEdit, QTextCharFormat +*/ + +/*! + \enum QCalendarWidget::SelectionMode + + This enum describes the types of selection offered to the user for + selecting dates in the calendar. + + \value NoSelection Dates cannot be selected. + \value SingleSelection Single dates can be selected. + + \sa selectionMode +*/ + +/*! + Constructs a calendar widget with the given \a parent. + + The widget is initialized with the current month and year, and the + currently selected date is today. + + \sa setCurrentPage() +*/ +QCalendarWidget::QCalendarWidget(QWidget *parent) + : QWidget(*new QCalendarWidgetPrivate, parent, 0) +{ + Q_D(QCalendarWidget); + + setAutoFillBackground(true); + setBackgroundRole(QPalette::Window); + + QVBoxLayout *layoutV = new QVBoxLayout(this); + layoutV->setMargin(0); + d->m_model = new QCalendarModel(this); + QTextCharFormat fmt; + fmt.setForeground(QBrush(Qt::red)); + d->m_model->m_dayFormats.insert(Qt::Saturday, fmt); + d->m_model->m_dayFormats.insert(Qt::Sunday, fmt); + d->m_view = new QCalendarView(this); + d->m_view->setObjectName(QLatin1String("qt_calendar_calendarview")); + d->m_view->setModel(d->m_model); + d->m_model->setView(d->m_view); + d->m_view->setSelectionBehavior(QAbstractItemView::SelectItems); + d->m_view->setSelectionMode(QAbstractItemView::SingleSelection); + d->m_view->horizontalHeader()->setResizeMode(QHeaderView::Stretch); + d->m_view->horizontalHeader()->setClickable(false); + d->m_view->verticalHeader()->setResizeMode(QHeaderView::Stretch); + d->m_view->verticalHeader()->setClickable(false); + d->m_selection = d->m_view->selectionModel(); + d->createNavigationBar(this); + d->m_view->setFrameStyle(QFrame::NoFrame); + d->m_delegate = new QCalendarDelegate(d, this); + d->m_view->setItemDelegate(d->m_delegate); + d->update(); + d->updateNavigationBar(); + setFocusPolicy(Qt::StrongFocus); + setFocusProxy(d->m_view); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + connect(d->m_view, SIGNAL(showDate(QDate)), + this, SLOT(_q_slotShowDate(QDate))); + connect(d->m_view, SIGNAL(changeDate(QDate,bool)), + this, SLOT(_q_slotChangeDate(QDate,bool))); + connect(d->m_view, SIGNAL(clicked(QDate)), + this, SIGNAL(clicked(QDate))); + connect(d->m_view, SIGNAL(editingFinished()), + this, SLOT(_q_editingFinished())); + + connect(d->prevMonth, SIGNAL(clicked(bool)), + this, SLOT(_q_prevMonthClicked())); + connect(d->nextMonth, SIGNAL(clicked(bool)), + this, SLOT(_q_nextMonthClicked())); + connect(d->yearButton, SIGNAL(clicked(bool)), + this, SLOT(_q_yearClicked())); + connect(d->monthMenu, SIGNAL(triggered(QAction*)), + this, SLOT(_q_monthChanged(QAction*))); + connect(d->yearEdit, SIGNAL(editingFinished()), + this, SLOT(_q_yearEditingFinished())); + + layoutV->setMargin(0); + layoutV->setSpacing(0); + layoutV->addWidget(d->navBarBackground); + layoutV->addWidget(d->m_view); + + d->m_navigator = new QCalendarTextNavigator(this); + setDateEditEnabled(true); +} + +/*! + Destroys the calendar widget. +*/ +QCalendarWidget::~QCalendarWidget() +{ +} + +/*! + \reimp +*/ +QSize QCalendarWidget::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \reimp +*/ +QSize QCalendarWidget::minimumSizeHint() const +{ + Q_D(const QCalendarWidget); + if (d->cachedSizeHint.isValid()) + return d->cachedSizeHint; + + ensurePolished(); + + int w = 0; + int h = 0; + + int end = 53; + int rows = 7; + int cols = 8; + int startRow = 0; + int startCol = 0; + + const int marginH = (style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1) * 2; + + if (horizontalHeaderFormat() == QCalendarWidget::NoHorizontalHeader) { + rows = 6; + startRow = 1; + } else { + for (int i = 1; i <= 7; i++) { + QFontMetrics fm(d->m_model->formatForCell(0, i).font()); + w = qMax(w, fm.width(d->m_model->dayName(d->m_model->dayOfWeekForColumn(i))) + marginH); + h = qMax(h, fm.height()); + } + } + + if (verticalHeaderFormat() == QCalendarWidget::NoVerticalHeader) { + cols = 7; + startCol = 1; + } else { + for (int i = 1; i <= 6; i++) { + QFontMetrics fm(d->m_model->formatForCell(i, 0).font()); + for (int j = 1; j < end; j++) + w = qMax(w, fm.width(QString::number(j)) + marginH); + h = qMax(h, fm.height()); + } + } + + QFontMetrics fm(d->m_model->formatForCell(1, 1).font()); + for (int i = 1; i <= end; i++) { + w = qMax(w, fm.width(QString::number(i)) + marginH); + h = qMax(h, fm.height()); + } + + if (d->m_view->showGrid()) { + // hardcoded in tableview + w += 1; + h += 1; + } + + w += 1; // default column span + + h = qMax(h, d->m_view->verticalHeader()->minimumSectionSize()); + w = qMax(w, d->m_view->horizontalHeader()->minimumSectionSize()); + + //add the size of the header. + QSize headerSize(0, 0); + if (d->navBarVisible) { + int headerH = d->navBarBackground->sizeHint().height(); + int headerW = 0; + + headerW += d->prevMonth->sizeHint().width(); + headerW += d->nextMonth->sizeHint().width(); + + QFontMetrics fm = d->monthButton->fontMetrics(); + int monthW = 0; + for (int i = 1; i < 12; i++) { + QString monthName = locale().standaloneMonthName(i, QLocale::LongFormat); + monthW = qMax(monthW, fm.boundingRect(monthName).width()); + } + const int buttonDecoMargin = d->monthButton->sizeHint().width() - fm.boundingRect(d->monthButton->text()).width(); + headerW += monthW + buttonDecoMargin; + + fm = d->yearButton->fontMetrics(); + headerW += fm.boundingRect(QLatin1String("5555")).width() + buttonDecoMargin; + + headerSize = QSize(headerW, headerH); + } + w *= cols; + w = qMax(headerSize.width(), w); + h = (h * rows) + headerSize.height(); + d->cachedSizeHint = QSize(w, h); + return d->cachedSizeHint; +} + +/*! + Paints the cell specified by the given \a date, using the given \a painter and \a rect. +*/ + +void QCalendarWidget::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const +{ + Q_D(const QCalendarWidget); + d->m_delegate->paintCell(painter, rect, date); +} + +/*! + \property QCalendarWidget::selectedDate + \brief the currently selected date. + + The selected date must be within the date range specified by the + minimumDate and maximumDate properties. By default, the selected + date is the current date. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::selectedDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_date; +} + +void QCalendarWidget::setSelectedDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_date == date && date == d->getCurrentDate()) + return; + + if (!date.isValid()) + return; + + d->m_model->setDate(date); + d->update(); + QDate newDate = d->m_model->m_date; + d->showMonth(newDate.year(), newDate.month()); + emit selectionChanged(); +} + +/*! + Returns the year of the currently displayed month. Months are + numbered from 1 to 12. + + \sa monthShown(), setCurrentPage() +*/ + +int QCalendarWidget::yearShown() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_shownYear; +} + +/*! + Returns the currently displayed month. Months are numbered from 1 to + 12. + + \sa yearShown(), setCurrentPage() +*/ + +int QCalendarWidget::monthShown() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_shownMonth; +} + +/*! + Displays the given \a month of the given \a year without changing + the selected date. Use the setSelectedDate() function to alter the + selected date. + + The currently displayed month and year can be retrieved using the + currentPageMonth() and currentPageYear() functions respectively. + + \sa yearShown(), monthShown(), showPreviousMonth(), showNextMonth(), + showPreviousYear(), showNextYear() +*/ + +void QCalendarWidget::setCurrentPage(int year, int month) +{ + Q_D(QCalendarWidget); + d->showMonth(year, month); +} + +/*! + Shows the next month relative to the currently displayed + month. Note that the selected date is not changed. + + \sa showPreviousMonth(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showNextMonth() +{ + int year = yearShown(); + int month = monthShown(); + if (month == 12) { + ++year; + month = 1; + } else { + ++month; + } + setCurrentPage(year, month); +} + +/*! + Shows the previous month relative to the currently displayed + month. Note that the selected date is not changed. + + \sa showNextMonth(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showPreviousMonth() +{ + int year = yearShown(); + int month = monthShown(); + if (month == 1) { + --year; + month = 12; + } else { + --month; + } + setCurrentPage(year, month); +} + +/*! + Shows the currently displayed month in the \e next year relative + to the currently displayed year. Note that the selected date is + not changed. + + \sa showPreviousYear(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showNextYear() +{ + int year = yearShown(); + int month = monthShown(); + ++year; + setCurrentPage(year, month); +} + +/*! + Shows the currently displayed month in the \e previous year + relative to the currently displayed year. Note that the selected + date is not changed. + + \sa showNextYear(), setCurrentPage(), setSelectedDate() +*/ + +void QCalendarWidget::showPreviousYear() +{ + int year = yearShown(); + int month = monthShown(); + --year; + setCurrentPage(year, month); +} + +/*! + Shows the month of the selected date. + + \sa selectedDate(), setCurrentPage() +*/ +void QCalendarWidget::showSelectedDate() +{ + QDate currentDate = selectedDate(); + setCurrentPage(currentDate.year(), currentDate.month()); +} + +/*! + Shows the month of the today's date. + + \sa selectedDate(), setCurrentPage() +*/ +void QCalendarWidget::showToday() +{ + QDate currentDate = QDate::currentDate(); + setCurrentPage(currentDate.year(), currentDate.month()); +} + +/*! + \property QCalendarWidget::minimumDate + \brief the minimum date of the currently specified date range. + + The user will not be able to select a date that is before the + currently set minimum date. + + \table + \row + \o \image qcalendarwidget-minimum.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 1 + \endtable + + By default, the minimum date is the earliest date that the QDate + class can handle. + + When setting a minimum date, the maximumDate and selectedDate + properties are adjusted if the selection range becomes invalid. If + the provided date is not a valid QDate object, the + setMinimumDate() function does nothing. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::minimumDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_minimumDate; +} + +void QCalendarWidget::setMinimumDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (!date.isValid() || d->m_model->m_minimumDate == date) + return; + + QDate oldDate = d->m_model->m_date; + d->m_model->setMinimumDate(date); + d->yearEdit->setMinimum(d->m_model->m_minimumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + +/*! + \property QCalendarWidget::maximumDate + \brief the maximum date of the currently specified date range. + + The user will not be able to select a date which is after the + currently set maximum date. + + \table + \row + \o \image qcalendarwidget-maximum.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 2 + \endtable + + By default, the maximum date is the last day the QDate class can + handle. + + When setting a maximum date, the minimumDate and selectedDate + properties are adjusted if the selection range becomes invalid. If + the provided date is not a valid QDate object, the + setMaximumDate() function does nothing. + + \sa setDateRange() +*/ + +QDate QCalendarWidget::maximumDate() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_maximumDate; +} + +void QCalendarWidget::setMaximumDate(const QDate &date) +{ + Q_D(QCalendarWidget); + if (!date.isValid() || d->m_model->m_maximumDate == date) + return; + + QDate oldDate = d->m_model->m_date; + d->m_model->setMaximumDate(date); + d->yearEdit->setMaximum(d->m_model->m_maximumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + +/*! + Defines a date range by setting the minimumDate and maximumDate + properties. + + The date range restricts the user selection, i.e. the user can + only select dates within the specified date range. Note that + + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 3 + + is analogous to + + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 4 + + If either the \a min or \a max parameters are not valid QDate + objects, this function does nothing. + + \sa setMinimumDate(), setMaximumDate() +*/ + +void QCalendarWidget::setDateRange(const QDate &min, const QDate &max) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_minimumDate == min && d->m_model->m_maximumDate == max) + return; + if (!min.isValid() || !max.isValid()) + return; + + QDate minimum = min; + QDate maximum = max; + if (min > max) { + minimum = max; + maximum = min; + } + + QDate oldDate = d->m_model->m_date; + d->m_model->setRange(min, max); + d->yearEdit->setMinimum(d->m_model->m_minimumDate.year()); + d->yearEdit->setMaximum(d->m_model->m_maximumDate.year()); + d->updateMonthMenu(); + QDate newDate = d->m_model->m_date; + if (oldDate != newDate) { + d->update(); + d->showMonth(newDate.year(), newDate.month()); + d->m_navigator->setDate(newDate); + emit selectionChanged(); + } +} + + +/*! \enum QCalendarWidget::HorizontalHeaderFormat + + This enum type defines the various formats the horizontal header can display. + + \value SingleLetterDayNames The header displays a single letter abbreviation for day names (e.g. M for Monday). + \value ShortDayNames The header displays a short abbreviation for day names (e.g. Mon for Monday). + \value LongDayNames The header displays complete day names (e.g. Monday). + \value NoHorizontalHeader The header is hidden. + + \sa horizontalHeaderFormat(), VerticalHeaderFormat +*/ + +/*! + \property QCalendarWidget::horizontalHeaderFormat + \brief the format of the horizontal header. + + The default value is QCalendarWidget::ShortDayNames. +*/ + +void QCalendarWidget::setHorizontalHeaderFormat(QCalendarWidget::HorizontalHeaderFormat format) +{ + Q_D(QCalendarWidget); + if (d->m_model->m_horizontalHeaderFormat == format) + return; + + d->m_model->setHorizontalHeaderFormat(format); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +QCalendarWidget::HorizontalHeaderFormat QCalendarWidget::horizontalHeaderFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_horizontalHeaderFormat; +} + + +/*! + \enum QCalendarWidget::VerticalHeaderFormat + + This enum type defines the various formats the vertical header can display. + + \value ISOWeekNumbers The header displays ISO week numbers as described by \l QDate::weekNumber(). + \value NoVerticalHeader The header is hidden. + + \sa verticalHeaderFormat(), HorizontalHeaderFormat +*/ + +/*! + \property QCalendarWidget::verticalHeaderFormat + \brief the format of the vertical header. + + The default value is QCalendarWidget::ISOWeekNumber. +*/ + +QCalendarWidget::VerticalHeaderFormat QCalendarWidget::verticalHeaderFormat() const +{ + Q_D(const QCalendarWidget); + bool shown = d->m_model->weekNumbersShown(); + if (shown) + return QCalendarWidget::ISOWeekNumbers; + return QCalendarWidget::NoVerticalHeader; +} + +void QCalendarWidget::setVerticalHeaderFormat(QCalendarWidget::VerticalHeaderFormat format) +{ + Q_D(QCalendarWidget); + bool show = false; + if (format == QCalendarWidget::ISOWeekNumbers) + show = true; + if (d->m_model->weekNumbersShown() == show) + return; + d->m_model->setWeekNumbersShown(show); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::gridVisible + \brief whether the table grid is displayed. + + \table + \row + \o \inlineimage qcalendarwidget-grid.png + \row + \o + \snippet doc/src/snippets/code/src_gui_widgets_qcalendarwidget.cpp 5 + \endtable + + The default value is false. +*/ + +bool QCalendarWidget::isGridVisible() const +{ + Q_D(const QCalendarWidget); + return d->m_view->showGrid(); +} + +void QCalendarWidget::setGridVisible(bool show) +{ + Q_D(QCalendarWidget); + d->m_view->setShowGrid(show); + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::selectionMode + \brief the type of selection the user can make in the calendar + + When this property is set to SingleSelection, the user can select a date + within the minimum and maximum allowed dates, using either the mouse or + the keyboard. + + When the property is set to NoSelection, the user will be unable to select + dates, but they can still be selected programmatically. Note that the date + that is selected when the property is set to NoSelection will still be + the selected date of the calendar. + + The default value is SingleSelection. +*/ + +QCalendarWidget::SelectionMode QCalendarWidget::selectionMode() const +{ + Q_D(const QCalendarWidget); + return d->m_view->readOnly ? QCalendarWidget::NoSelection : QCalendarWidget::SingleSelection; +} + +void QCalendarWidget::setSelectionMode(SelectionMode mode) +{ + Q_D(QCalendarWidget); + d->m_view->readOnly = (mode == QCalendarWidget::NoSelection); + d->setNavigatorEnabled(isDateEditEnabled() && (selectionMode() != QCalendarWidget::NoSelection)); + d->update(); +} + +/*! + \property QCalendarWidget::firstDayOfWeek + \brief a value identifying the day displayed in the first column. + + By default, the day displayed in the first column is Sunday +*/ + +void QCalendarWidget::setFirstDayOfWeek(Qt::DayOfWeek dayOfWeek) +{ + Q_D(QCalendarWidget); + if ((Qt::DayOfWeek)d->m_model->firstColumnDay() == dayOfWeek) + return; + + d->m_model->setFirstColumnDay(dayOfWeek); + d->update(); +} + +Qt::DayOfWeek QCalendarWidget::firstDayOfWeek() const +{ + Q_D(const QCalendarWidget); + return (Qt::DayOfWeek)d->m_model->firstColumnDay(); +} + +/*! + Returns the text char format for rendering the header. +*/ +QTextCharFormat QCalendarWidget::headerTextFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_headerFormat; +} + +/*! + Sets the text char format for rendering the header to \a format. + If you also set a weekday text format, this format's foreground and + background color will take precedence over the header's format. + The other formatting information will still be decided by + the header's format. +*/ +void QCalendarWidget::setHeaderTextFormat(const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + d->m_model->m_headerFormat = format; + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + Returns the text char format for rendering of day in the week \a dayOfWeek. + + \sa headerTextFormat() +*/ +QTextCharFormat QCalendarWidget::weekdayTextFormat(Qt::DayOfWeek dayOfWeek) const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dayFormats.value(dayOfWeek); +} + +/*! + Sets the text char format for rendering of day in the week \a dayOfWeek to \a format. + The format will take precedence over the header format in case of foreground + and background color. Other text formatting information is taken from the headers format. + + \sa setHeaderTextFormat() +*/ +void QCalendarWidget::setWeekdayTextFormat(Qt::DayOfWeek dayOfWeek, const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + d->m_model->m_dayFormats[dayOfWeek] = format; + d->cachedSizeHint = QSize(); + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + Returns a QMap from QDate to QTextCharFormat showing all dates + that use a special format that alters their rendering. +*/ +QMap<QDate, QTextCharFormat> QCalendarWidget::dateTextFormat() const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dateFormats; +} + +/*! + Returns a QTextCharFormat for \a date. The char format can be be + empty if the date is not renderd specially. +*/ +QTextCharFormat QCalendarWidget::dateTextFormat(const QDate &date) const +{ + Q_D(const QCalendarWidget); + return d->m_model->m_dateFormats.value(date); +} + +/*! + Sets the format used to render the given \a date to that specified by \a format. + + If \a date is null, all date formats are cleared. +*/ +void QCalendarWidget::setDateTextFormat(const QDate &date, const QTextCharFormat &format) +{ + Q_D(QCalendarWidget); + if ( date.isNull() && !format.isValid() ) + d->m_model->m_dateFormats.clear(); + else + d->m_model->m_dateFormats[date] = format; + d->m_view->viewport()->update(); + d->m_view->updateGeometry(); +} + +/*! + \property QCalendarWidget::dateEditEnabled + \brief whether the date edit popup is enabled + \since 4.3 + + If this property is enabled, pressing a non-modifier key will cause a + date edit to popup if the calendar widget has focus, allowing the user + to specify a date in the form specified by the current locale. + + By default, this property is enabled. + + The date edit is simpler in appearance than QDateEdit, but allows the + user to navigate between fields using the left and right cursor keys, + increment and decrement individual fields using the up and down cursor + keys, and enter values directly using the number keys. + + \sa QCalendarWidget::dateEditAcceptDelay +*/ +bool QCalendarWidget::isDateEditEnabled() const +{ + Q_D(const QCalendarWidget); + return d->m_dateEditEnabled; +} + +void QCalendarWidget::setDateEditEnabled(bool enable) +{ + Q_D(QCalendarWidget); + if (isDateEditEnabled() == enable) + return; + + d->m_dateEditEnabled = enable; + + d->setNavigatorEnabled(enable && (selectionMode() != QCalendarWidget::NoSelection)); +} + +/*! + \property QCalendarWidget::dateEditAcceptDelay + \brief the time an inactive date edit is shown before its contents are accepted + \since 4.3 + + If the calendar widget's \l{dateEditEnabled}{date edit is enabled}, this + property specifies the amount of time (in millseconds) that the date edit + remains open after the most recent user input. Once this time has elapsed, + the date specified in the date edit is accepted and the popup is closed. + + By default, the delay is defined to be 1500 milliseconds (1.5 seconds). +*/ +int QCalendarWidget::dateEditAcceptDelay() const +{ + Q_D(const QCalendarWidget); + return d->m_navigator->dateEditAcceptDelay(); +} + +void QCalendarWidget::setDateEditAcceptDelay(int delay) +{ + Q_D(QCalendarWidget); + d->m_navigator->setDateEditAcceptDelay(delay); +} + +/*! + \since 4.4 + + Updates the cell specified by the given \a date unless updates + are disabled or the cell is hidden. + + \sa updateCells(), yearShown(), monthShown() +*/ +void QCalendarWidget::updateCell(const QDate &date) +{ + if (!date.isValid()) { + qWarning("QCalendarWidget::updateCell: Invalid date"); + return; + } + + if (!isVisible()) + return; + + Q_D(QCalendarWidget); + int row, column; + d->m_model->cellForDate(date, &row, &column); + if (row == -1 || column == -1) + return; + + QModelIndex modelIndex = d->m_model->index(row, column); + if (!modelIndex.isValid()) + return; + + d->m_view->viewport()->update(d->m_view->visualRect(modelIndex)); +} + +/*! + \since 4.4 + + Updates all visible cells unless updates are disabled. + + \sa updateCell() +*/ +void QCalendarWidget::updateCells() +{ + Q_D(QCalendarWidget); + if (isVisible()) + d->m_view->viewport()->update(); +} + +/*! + \fn void QCalendarWidget::selectionChanged() + + This signal is emitted when the currently selected date is + changed. + + The currently selected date can be changed by the user using the + mouse or keyboard, or by the programmer using setSelectedDate(). + + \sa selectedDate() +*/ + +/*! + \fn void QCalendarWidget::currentPageChanged(int year, int month) + + This signal is emitted when the currently shown month is changed. + The new \a year and \a month are passed as parameters. + + \sa setCurrentPage() +*/ + +/*! + \fn void QCalendarWidget::activated(const QDate &date) + + This signal is emitted whenever the user presses the Return or + Enter key or double-clicks a \a date in the calendar + widget. +*/ + +/*! + \fn void QCalendarWidget::clicked(const QDate &date) + + This signal is emitted when a mouse button is clicked. The date + the mouse was clicked on is specified by \a date. The signal is + only emitted when clicked on a valid date, e.g., dates are not + outside the minimumDate() and maximumDate(). If the selection mode + is NoSelection, this signal will not be emitted. + +*/ + +/*! + \property QCalendarWidget::headerVisible + \brief whether the navigation bar is shown or not + + \obsolete + + Use navigationBarVisible() instead. + + By default, this property is true. +*/ + +/*! + \obsolete + + Use setNavigationBarVisible() instead. +*/ +bool QCalendarWidget::isHeaderVisible() const +{ + Q_D(const QCalendarWidget); + return d->navBarVisible; +} + +/*! + \obsolete + + Use setNavigationBarVisible() instead. + +*/ +void QCalendarWidget::setHeaderVisible(bool visible) +{ + setNavigationBarVisible(visible); +} + +/*! + \property QCalendarWidget::navigationBarVisible + \brief whether the navigation bar is shown or not + + \since 4.3 + + When this property is true (the default), the next month, + previous month, month selection, year selection controls are + shown on top. + + When the property is set to false, these controls are hidden. +*/ + +void QCalendarWidget::setNavigationBarVisible(bool visible) +{ + Q_D(QCalendarWidget); + d->navBarVisible = visible; + d->cachedSizeHint = QSize(); + d->navBarBackground->setVisible(visible); + updateGeometry(); +} + +/*! + \reimp +*/ +bool QCalendarWidget::event(QEvent *event) +{ + Q_D(QCalendarWidget); + switch (event->type()) { + case QEvent::LayoutDirectionChange: + d->updateButtonIcons(); + case QEvent::LocaleChange: + d->cachedSizeHint = QSize(); + d->updateMonthMenuNames(); + d->updateNavigationBar(); + d->m_view->updateGeometry(); + break; + case QEvent::FontChange: + case QEvent::ApplicationFontChange: + d->cachedSizeHint = QSize(); + d->m_view->updateGeometry(); + break; + case QEvent::StyleChange: + d->cachedSizeHint = QSize(); + d->m_view->updateGeometry(); + default: + break; + } + return QWidget::event(event); +} + +/*! + \reimp +*/ +bool QCalendarWidget::eventFilter(QObject *watched, QEvent *event) +{ + Q_D(QCalendarWidget); + if (event->type() == QEvent::MouseButtonPress && d->yearEdit->hasFocus() && !QRect(d->yearEdit->mapToGlobal(QPoint(0, 0)), d->yearEdit->size()).contains(static_cast<QMouseEvent *>(event)->globalPos())) { + event->accept(); + d->_q_yearEditingFinished(); + setFocus(); + return true; + } + return QWidget::eventFilter(watched, event); +} + +/*! + \reimp +*/ +void QCalendarWidget::mousePressEvent(QMouseEvent *event) +{ + setAttribute(Qt::WA_NoMouseReplay); + QWidget::mousePressEvent(event); + setFocus(); +} + +/*! + \reimp +*/ +void QCalendarWidget::resizeEvent(QResizeEvent * event) +{ + Q_D(QCalendarWidget); + + // XXX Should really use a QWidgetStack for yearEdit and yearButton, + // XXX here we hide the year edit when the layout is likely to break + // XXX the manual positioning of the yearEdit over the yearButton. + if(d->yearEdit->isVisible() && event->size().width() != event->oldSize().width()) + d->_q_yearEditingFinished(); + + QWidget::resizeEvent(event); +} + +/*! + \reimp +*/ +void QCalendarWidget::keyPressEvent(QKeyEvent * event) +{ + Q_D(QCalendarWidget); + if(d->yearEdit->isVisible()&& event->key() == Qt::Key_Escape) + { + d->yearEdit->setValue(yearShown()); + d->_q_yearEditingFinished(); + return; + } + QWidget::keyPressEvent(event); +} + +QT_END_NAMESPACE + +#include "qcalendarwidget.moc" +#include "moc_qcalendarwidget.cpp" + +#endif //QT_NO_CALENDARWIDGET |