diff options
author | Mitch Curtis <mitch.curtis@digia.com> | 2013-07-15 22:57:52 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2014-02-14 13:13:53 +0100 |
commit | e023dd212c81a2ad4ba4b4db22df9cde65a502e8 (patch) | |
tree | 2c3521e8a4154d65a55fc3032254b58b181d957e | |
parent | e88bdffe644e53912dfbce95117555cb6a87bfd2 (diff) | |
download | qtquickcontrols-e023dd212c81a2ad4ba4b4db22df9cde65a502e8.tar.gz |
Add Calendar to Qt Quick Controls.
Task-number: QTBUG-29948
[ChangeLog][QtQuickControls] Calendar was added. Calendar allows
selection of dates from a grid of days, similar to
QCalendarWidget.
Change-Id: I279130e704bc0dfd8dfe114ec9b6b49e111faf96
Reviewed-by: Jens Bache-Wiig <jens.bache-wiig@digia.com>
30 files changed, 3380 insertions, 0 deletions
diff --git a/examples/quick/controls/calendar/calendar.pro b/examples/quick/controls/calendar/calendar.pro new file mode 100644 index 00000000..cfba66a6 --- /dev/null +++ b/examples/quick/controls/calendar/calendar.pro @@ -0,0 +1,9 @@ +QT += qml quick sql +TARGET = calendar + +include(src/src.pri) +include(../shared/shared.pri) + +OTHER_FILES += qml/main.qml + +RESOURCES += resources.qrc diff --git a/examples/quick/controls/calendar/qml/main.qml b/examples/quick/controls/calendar/qml/main.qml new file mode 100644 index 00000000..a42745ed --- /dev/null +++ b/examples/quick/controls/calendar/qml/main.qml @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.1 +import org.qtproject.examples.calendar 1.0 + +ApplicationWindow { + visible: true + width: 640 + height: 480 + minimumWidth: 400 + minimumHeight: 300 + + title: "Calendar Example" + + SqlEventModel { + id: eventModel + } + + Row { + id: row + anchors.fill: parent + anchors.margins: 20 + spacing: 10 + + Rectangle { + width: row.width * 0.4 - row.spacing / 2 + height: calendar.height + + Column { + id: eventsPane + anchors.fill: parent + anchors.margins: spacing / 2 + spacing: 10 + + Row { + id: eventDateRow + width: parent.width + height: eventDayLabel.height + spacing: 10 + + Label { + id: eventDayLabel + text: calendar.selectedDate.getDate() + font.pointSize: 35 + } + + Column { + height: eventDayLabel.height + + Label { + readonly property var options: { weekday: "long" } + text: Qt.locale().standaloneDayName(calendar.selectedDate.getDay(), Locale.LongFormat) + font.pointSize: 18 + } + Label { + text: Qt.locale().standaloneMonthName(calendar.selectedDate.getMonth()) + + calendar.selectedDate.toLocaleDateString(Qt.locale(), " yyyy") + font.pointSize: 12 + } + } + } + + ListView { + id: eventsListView + width: parent.width + height: parent.height - eventDateRow.height + spacing: 4 + clip: true + + model: eventModel.eventsForDate(calendar.selectedDate) + delegate: Rectangle { + width: eventsListView.width + height: eventItemColumn.height + + Column { + id: eventItemColumn + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.right: parent.right + height: timeLabel.height + nameLabel.height + + Label { + id: nameLabel + width: parent.width + wrapMode: Text.Wrap + text: modelData.name + font.pointSize: 12 + } + Label { + id: timeLabel + width: parent.width + wrapMode: Text.Wrap + text: modelData.startDate.toLocaleTimeString(calendar.locale, Locale.ShortFormat) + color: "#aaa" + } + } + } + } + } + } + + Calendar { + id: calendar + width: parent.width * 0.6 - row.spacing / 2 + height: parent.height + selectedDate: new Date(2014, 0, 1) + focus: true + + style: CalendarStyle { + dayDelegate: Rectangle { + color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "white" + readonly property color sameMonthDateTextColor: "black" + readonly property color selectedDateColor: "#aaa" + readonly property color selectedDateTextColor: "white" + readonly property color differentMonthDateTextColor: Qt.darker("darkgrey", 1.4) + readonly property color invalidDatecolor: "#dddddd" + + Label { + id: dayDelegateText + text: styleData.date.getDate() + font.pixelSize: 14 + anchors.centerIn: parent + color: { + var color = invalidDatecolor; + if (styleData.valid) { + // Date is within the valid range. + color = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor; + if (styleData.selected) { + color = selectedDateTextColor; + } + } + color; + } + } + + Rectangle { + color: styleData.selected ? "white" : "red" + width: 4 + height: width + radius: width / 2 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: dayDelegateText.bottom + anchors.topMargin: 2 + visible: eventModel.eventsForDate(styleData.date).length > 0 + } + } + } + } + } +} diff --git a/examples/quick/controls/calendar/resources.qrc b/examples/quick/controls/calendar/resources.qrc new file mode 100644 index 00000000..69145a82 --- /dev/null +++ b/examples/quick/controls/calendar/resources.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>qml/main.qml</file> + </qresource> +</RCC> diff --git a/examples/quick/controls/calendar/src/event.cpp b/examples/quick/controls/calendar/src/event.cpp new file mode 100644 index 00000000..a54efa2b --- /dev/null +++ b/examples/quick/controls/calendar/src/event.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "event.h" + +Event::Event(QObject *parent) : + QObject(parent) +{ +} + +QString Event::name() const +{ + return mName; +} + +void Event::setName(const QString &name) +{ + if (name != mName) { + mName = name; + emit nameChanged(mName); + } +} + +QDateTime Event::startDate() const +{ + return mStartDate; +} + +void Event::setStartDate(const QDateTime &startDate) +{ + if (startDate != mStartDate) { + mStartDate = startDate; + emit startDateChanged(mStartDate); + } +} + +QDateTime Event::endDate() const +{ + return mEndDate; +} + +void Event::setEndDate(const QDateTime &endDate) +{ + if (endDate != mEndDate) { + mEndDate = endDate; + emit endDateChanged(mEndDate); + } +} diff --git a/examples/quick/controls/calendar/src/event.h b/examples/quick/controls/calendar/src/event.h new file mode 100644 index 00000000..eff21036 --- /dev/null +++ b/examples/quick/controls/calendar/src/event.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef EVENT_H +#define EVENT_H + +#include <QDateTime> +#include <QObject> +#include <QString> + +class Event : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QDateTime startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) + Q_PROPERTY(QDateTime endDate READ endDate WRITE setEndDate NOTIFY endDateChanged) +public: + explicit Event(QObject *parent = 0); + + QString name() const; + void setName(const QString &name); + + QDateTime startDate() const; + void setStartDate(const QDateTime &startDate); + + QDateTime endDate() const; + void setEndDate(const QDateTime &endDate); +signals: + void nameChanged(const QString &name); + void startDateChanged(const QDateTime &startDate); + void endDateChanged(const QDateTime &endDate); +private: + QString mName; + QDateTime mStartDate; + QDateTime mEndDate; +}; + +#endif diff --git a/examples/quick/controls/calendar/src/main.cpp b/examples/quick/controls/calendar/src/main.cpp new file mode 100644 index 00000000..effbb6ad --- /dev/null +++ b/examples/quick/controls/calendar/src/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml> + +#include "qtquickcontrolsapplication.h" +#include "sqleventmodel.h" + +int main(int argc, char *argv[]) +{ + QtQuickControlsApplication app(argc, argv); + qmlRegisterType<SqlEventModel>("org.qtproject.examples.calendar", 1, 0, "SqlEventModel"); + QQmlApplicationEngine engine(QUrl("qrc:/qml/main.qml")); + return app.exec(); +} diff --git a/examples/quick/controls/calendar/src/sqleventmodel.cpp b/examples/quick/controls/calendar/src/sqleventmodel.cpp new file mode 100644 index 00000000..6277a316 --- /dev/null +++ b/examples/quick/controls/calendar/src/sqleventmodel.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "sqleventmodel.h" + +#include <QDebug> +#include <QFileInfo> +#include <QSqlError> +#include <QSqlQuery> + +SqlEventModel::SqlEventModel() : + QSqlQueryModel() +{ + createConnection(); +} + +QList<QObject*> SqlEventModel::eventsForDate(const QDate &date) +{ + const QString queryStr = QString::fromLatin1("SELECT * FROM Event WHERE '%1' >= startDate AND '%1' <= endDate").arg(date.toString("yyyy-MM-dd")); + QSqlQuery query(queryStr); + if (!query.exec()) + qFatal("Query failed"); + + QList<QObject*> events; + while (query.next()) { + Event *event = new Event(this); + event->setName(query.value("name").toString()); + + QDateTime startDate; + startDate.setDate(query.value("startDate").toDate()); + startDate.setTime(QTime(0, 0).addSecs(query.value("startTime").toInt())); + event->setStartDate(startDate); + + QDateTime endDate; + endDate.setDate(query.value("endDate").toDate()); + endDate.setTime(QTime(0, 0).addSecs(query.value("endTime").toInt())); + event->setEndDate(endDate); + + events.append(event); + } + + return events; +} + +/* + Defines a helper function to open a connection to an + in-memory SQLITE database and to create a test table. +*/ +void SqlEventModel::createConnection() +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(":memory:"); + if (!db.open()) { + qFatal("Cannot open database"); + return; + } + + QSqlQuery query; + // We store the time as seconds because it's easier to query. + query.exec("create table Event (name TEXT, startDate DATE, startTime INT, endDate DATE, endTime INT)"); + query.exec("insert into Event values('Grocery shopping', '2014-01-01', 36000, '2014-01-01', 39600)"); + query.exec("insert into Event values('Ice skating', '2014-01-01', 57600, '2014-01-01', 61200)"); + query.exec("insert into Event values('Doctor''s appointment', '2014-01-15', 57600, '2014-01-15', 63000)"); + query.exec("insert into Event values('Conference', '2014-01-24', 32400, '2014-01-28', 61200)"); + + return; +} diff --git a/examples/quick/controls/calendar/src/sqleventmodel.h b/examples/quick/controls/calendar/src/sqleventmodel.h new file mode 100644 index 00000000..a2b72c07 --- /dev/null +++ b/examples/quick/controls/calendar/src/sqleventmodel.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SQLEVENTMODEL_H +#define SQLEVENTMODEL_H + +#include <QList> +#include <QObject> +#include <QSqlTableModel> + +#include "event.h" + +class SqlEventModel : public QSqlQueryModel +{ + Q_OBJECT + +public: + SqlEventModel(); + + Q_INVOKABLE QList<QObject*> eventsForDate(const QDate &date); + +private: + static void createConnection(); +}; + +#endif diff --git a/examples/quick/controls/calendar/src/src.pri b/examples/quick/controls/calendar/src/src.pri new file mode 100644 index 00000000..50b2ea62 --- /dev/null +++ b/examples/quick/controls/calendar/src/src.pri @@ -0,0 +1,9 @@ +SOURCES += \ + $$PWD/event.cpp \ + $$PWD/main.cpp \ + $$PWD/sqleventmodel.cpp + + +HEADERS += \ + $$PWD/event.h \ + $$PWD/sqleventmodel.h diff --git a/examples/quick/controls/controls.pro b/examples/quick/controls/controls.pro index ad246f1d..84e4b6ba 100644 --- a/examples/quick/controls/controls.pro +++ b/examples/quick/controls/controls.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS += \ + calendar \ gallery \ splitview \ tableview \ diff --git a/src/controls/Calendar.qml b/src/controls/Calendar.qml new file mode 100644 index 00000000..07e9b4ad --- /dev/null +++ b/src/controls/Calendar.qml @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Controls.Private 1.0 + +/*! + \qmltype Calendar + \inqmlmodule QtQuick.Controls + \since 5.3 + \ingroup controls + \brief Provides a way to select dates from a calendar + + Calendar allows selection of dates from a grid of days, similar to + QCalendarWidget. + + The dates on the calendar can be selected with the mouse, or navigated + with the keyboard. + + The selected date can be set through \l selectedDate. + A minimum and maximum date can be set through \l minimumDate and + \l maximumDate. The earliest minimum date that can be set is 1 January, 1 + AD. The latest maximum date that can be set is 25 October, 275759 AD. + + The selected date is displayed using the format in the application's + default locale. + + Week numbers can be displayed by setting the weekNumbersVisible property to + \c true. + + You can create a custom appearance for Calendar by assigning a + \l {QtQuick.Controls.Styles::CalendarStyle}{CalendarStyle}. +*/ + +Control { + id: calendar + + implicitWidth: 250 + implicitHeight: 250 + + /*! + \qmlproperty date Calendar::selectedDate + + The date that has been selected by the user. + + This property is subject to the following validation: + + \list + \li If selectedDate is outside the range of \l minimumDate and + \l maximumDate, it will be clamped to be within that range. + + \li selectedDate will not be changed if \c undefined or some other + invalid value is assigned. + + \li If there are hours, minutes, seconds or milliseconds set, they + will be removed. + \endlist + + The default value is the current date, which is equivalent to: + + \code + new Date() + \endcode + */ + property alias selectedDate: rangedDate.date + + /*! + \qmlproperty date Calendar::minimumDate + + The earliest date that this calendar will accept. + + By default, this property is set to the earliest minimum date + (1 January, 1 AD). + */ + property alias minimumDate: rangedDate.minimumDate + + /*! + \qmlproperty date Calendar::maximumDate + + The latest date that this calendar will accept. + + By default, this property is set to the latest maximum date + (25 October, 275759 AD). + */ + property alias maximumDate: rangedDate.maximumDate + + /*! + This property determines which month in visibleYear is shown on the + calendar. + + The month is from \c 0 to \c 11 to be consistent with the JavaScript + Date object. + + \sa visibleYear + */ + property int visibleMonth: selectedDate.getMonth() + + /*! + This property determines which year is shown on the + calendar. + + \sa visibleMonth + */ + property int visibleYear: selectedDate.getFullYear() + + onSelectedDateChanged: { + // When the selected date changes, the view should move back to that date. + visibleMonth = selectedDate.getMonth(); + visibleYear = selectedDate.getFullYear(); + } + + RangedDate { + id: rangedDate + date: new Date() + minimumDate: CalendarUtils.minimumCalendarDate + maximumDate: CalendarUtils.maximumCalendarDate + } + + /*! + This property determines the visibility of the grid. + + The default value is \c true. + */ + property bool gridVisible: true + + /*! + This property determines the visibility of week numbers. + + The default value is \c false. + */ + property bool weekNumbersVisible: false + + /*! + \qmlproperty enum Calendar::dayOfWeekFormat + + The format in which the days of the week (in the header) are displayed. + + \c Locale.ShortFormat is the default and recommended format, as + \c Locale.NarrowFormat may not be fully supported by each locale (see + \l {Locale String Format Types}) and + \c Locale.LongFormat may not fit within the header cells. + */ + property int dayOfWeekFormat: Locale.ShortFormat + + /*! + The locale that this calendar should use to display itself. + + Affects how dates and day names are localized, as well as which + day is considered the first in a week. + + To set an Australian locale, for example: + + \code + locale: Qt.locale("en_AU") + \endcode + + The default locale is \c Qt.locale(). + */ + property var __locale: Qt.locale() + + /*! + \internal + + This property holds the model that will be used by the Calendar to + populate the dates available to the user. + */ + property CalendarModel __model: CalendarModel { + locale: calendar.__locale + visibleDate: new Date(visibleYear, visibleMonth, 1) + } + + style: Qt.createComponent(Settings.style + "/CalendarStyle.qml", calendar) + + /*! + \qmlsignal Calendar::hovered(date date) + + Emitted when the mouse hovers over a valid date in the calendar. + + \a date is the date that was hovered over. + */ + signal hovered(date date) + + /*! + \qmlsignal Calendar::pressed(date date) + + Emitted when the mouse is pressed on a valid date in the calendar. + + This is also emitted when dragging the mouse to another date while it is pressed. + + \a date is the date that the mouse was pressed on. + */ + signal pressed(date date) + + /*! + \qmlsignal Calendar::released(date date) + + Emitted when the mouse is released over a valid date in the calendar. + + \a date is the date that the mouse was released over. + */ + signal released(date date) + + /*! + \qmlsignal Calendar::clicked(date date) + + Emitted when the mouse is clicked on a valid date in the calendar. + + \a date is the date that the mouse was clicked on. + */ + signal clicked(date date) + + /*! + \qmlsignal Calendar::doubleClicked(date date) + + Emitted when the mouse is double-clicked on a valid date in the calendar. + + \a date is the date that the mouse was double-clicked on. + */ + signal doubleClicked(date date) + + /*! + Sets visibleMonth to the previous month. + */ + function showPreviousMonth() { + if (visibleMonth === 0) { + visibleMonth = CalendarUtils.monthsInAYear - 1; + --visibleYear; + } else { + --visibleMonth; + } + } + + /*! + Sets visibleMonth to the next month. + */ + function showNextMonth() { + if (visibleMonth === CalendarUtils.monthsInAYear - 1) { + visibleMonth = 0; + ++visibleYear; + } else { + ++visibleMonth; + } + } + + /*! + Sets visibleYear to the previous year. + */ + function showPreviousYear() { + if (visibleYear - 1 >= minimumDate.getFullYear()) { + --visibleYear; + } + } + + /*! + Sets visibleYear to the next year. + */ + function showNextYear() { + if (visibleYear + 1 <= maximumDate.getFullYear()) { + ++visibleYear; + } + } + + /*! + Selects the month before the current month in \l selectedDate. + */ + function __selectPreviousMonth() { + calendar.selectedDate = CalendarUtils.setMonth(calendar.selectedDate, calendar.selectedDate.getMonth() - 1); + } + + /*! + Selects the month after the current month in \l selectedDate. + */ + function __selectNextMonth() { + calendar.selectedDate = CalendarUtils.setMonth(calendar.selectedDate, calendar.selectedDate.getMonth() + 1); + } + + /*! + Selects the week before the current week in \l selectedDate. + */ + function __selectPreviousWeek() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(newDate.getDate() - CalendarUtils.daysInAWeek); + calendar.selectedDate = newDate; + } + + /*! + Selects the week after the current week in \l selectedDate. + */ + function __selectNextWeek() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(newDate.getDate() + CalendarUtils.daysInAWeek); + calendar.selectedDate = newDate; + } + + /*! + Selects the first day of the current month in \l selectedDate. + */ + function __selectFirstDayOfMonth() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(1); + calendar.selectedDate = newDate; + } + + /*! + Selects the last day of the current month in \l selectedDate. + */ + function __selectLastDayOfMonth() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(CalendarUtils.daysInMonth(newDate)); + calendar.selectedDate = newDate; + } + + /*! + Selects the day before the current day in \l selectedDate. + */ + function __selectPreviousDay() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(newDate.getDate() - 1); + calendar.selectedDate = newDate; + } + + /*! + Selects the day after the current day in \l selectedDate. + */ + function __selectNextDay() { + var newDate = new Date(calendar.selectedDate); + newDate.setDate(newDate.getDate() + 1); + calendar.selectedDate = newDate; + } + + Keys.onLeftPressed: { + calendar.__selectPreviousDay(); + } + + Keys.onUpPressed: { + calendar.__selectPreviousWeek(); + } + + Keys.onDownPressed: { + calendar.__selectNextWeek(); + } + + Keys.onRightPressed: { + calendar.__selectNextDay(); + } + + Keys.onPressed: { + if (event.key === Qt.Key_Home) { + calendar.__selectFirstDayOfMonth(); + event.accepted = true; + } else if (event.key === Qt.Key_End) { + calendar.__selectLastDayOfMonth(); + event.accepted = true; + } else if (event.key === Qt.Key_PageUp) { + calendar.__selectPreviousMonth(); + event.accepted = true; + } else if (event.key === Qt.Key_PageDown) { + calendar.__selectNextMonth(); + event.accepted = true; + } + } +} diff --git a/src/controls/Private/CalendarHeaderModel.qml b/src/controls/Private/CalendarHeaderModel.qml new file mode 100644 index 00000000..ef57e65b --- /dev/null +++ b/src/controls/Private/CalendarHeaderModel.qml @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 + +/* + CalendarHeaderModel contains a list of the days of a week, + according to a \l locale. The \l locale affects which day of the week + is first in the model. + + The only role provided by the model is \c dayOfWeek, which is one of the + following JavaScript values: + + \list + \li \c Locale.Sunday + \li \c Locale.Monday + \li \c Locale.Tuesday + \li \c Locale.Wednesday + \li \c Locale.Thursday + \li \c Locale.Friday + \li \c Locale.Saturday + \endlist + */ + +ListModel { + id: root + + /* + The locale that this model should be based on. + This affects which day of the week is first in the model. + */ + property var locale + + Component.onCompleted: { + var daysOfWeek = [Locale.Sunday, Locale.Monday, Locale.Tuesday, + Locale.Wednesday, Locale.Thursday, Locale.Friday, Locale.Saturday]; + var firstDayOfWeek = root.locale.firstDayOfWeek; + + var shifted = daysOfWeek.splice(firstDayOfWeek, daysOfWeek.length - firstDayOfWeek); + daysOfWeek = shifted.concat(daysOfWeek) + + for (var i = 0; i < daysOfWeek.length; ++i) { + var element = { dayOfWeek: daysOfWeek[i] } + root.append(element); + } + } +} diff --git a/src/controls/Private/CalendarUtils.js b/src/controls/Private/CalendarUtils.js new file mode 100644 index 00000000..c055118e --- /dev/null +++ b/src/controls/Private/CalendarUtils.js @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +.pragma library + +var daysInAWeek = 7; +var monthsInAYear = 12; + +// Not the number of weeks per month, but the number of weeks that are +// shown on a typical calendar. +var weeksOnACalendarMonth = 6; + +// Can't create year 1 directly... +var minimumCalendarDate = new Date(-1, 0, 1); +minimumCalendarDate.setFullYear(minimumCalendarDate.getFullYear() + 2); +var maximumCalendarDate = new Date(275759, 9, 25); + +function daysInMonth(date) { + // Passing 0 as the day will give us the previous month, which will be + // date.getMonth() since we added 1 to it. + return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); +} + +/*! + Returns a copy of \a date with its month set to \a month, keeping the same + day if possible. Does not modify \a date. +*/ +function setMonth(date, month) { + var oldDay = date.getDate(); + var newDate = new Date(date); + // Set the day first, because setting the month could cause it to skip ahead + // a month if the day is larger than the latest day in that month. + newDate.setDate(1); + newDate.setMonth(month); + // We'd like to have the previous day still selected when we change + // months, but it might not be possible, so use the smallest of the two. + newDate.setDate(Math.min(oldDay, daysInMonth(newDate))); + return newDate; +} + +function cellRectAt(index, columns, rows, availableWidth, availableHeight) { + var col = Math.floor(index % columns); + var row = Math.floor(index / columns); + + var remainingHorizontalSpace = Math.floor(availableWidth % columns); + var remainingVerticalSpace = Math.floor(availableHeight % rows); + var baseCellWidth = Math.floor(availableWidth / columns); + var baseCellHeight = Math.floor(availableHeight / rows); + + var rect = Qt.rect(0, 0, 0, 0); + + rect.x = baseCellWidth * col; + rect.width = baseCellWidth; + if (remainingHorizontalSpace > 0) { + if (col < remainingHorizontalSpace) { + ++rect.width; + } + + // This cell's x position should be increased by 1 for every column above it. + rect.x += Math.min(remainingHorizontalSpace, col); + } + + rect.y = baseCellHeight * row; + rect.height = baseCellHeight; + if (remainingVerticalSpace > 0) { + if (row < remainingVerticalSpace) { + ++rect.height; + } + + // This cell's y position should be increased by 1 for every row above it. + rect.y += Math.min(remainingVerticalSpace, row); + } + + return rect; +} + +function cellIndexAt(x, y, columns, rows, availableWidth, availableHeight) { + var remainingHorizontalSpace = Math.floor(availableWidth % columns); + var remainingVerticalSpace = Math.floor(availableHeight % rows); + var baseCellWidth = Math.floor(availableWidth / columns); + var baseCellHeight = Math.floor(availableHeight / rows); + + // TODO: improve this. + for (var row = 0; row < rows; ++row) { + for (var col = 0; col < columns; ++col) { + var index = row * columns + col; + var rect = cellRectAt(index, columns, rows, availableWidth, availableHeight); + if (x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height) { + return index; + } + } + } + + return -1; +} diff --git a/src/controls/Private/private.pri b/src/controls/Private/private.pri index b12cabd7..49227c9d 100644 --- a/src/controls/Private/private.pri +++ b/src/controls/Private/private.pri @@ -1,8 +1,10 @@ HEADERS += \ + $$PWD/qquickcalendarmodel_p.h \ $$PWD/qquicktooltip_p.h \ $$PWD/qquickspinboxvalidator_p.h \ $$PWD/qquickrangemodel_p.h \ $$PWD/qquickrangemodel_p_p.h \ + $$PWD/qquickrangeddate_p.h \ $$PWD/qquickcontrolsettings_p.h \ $$PWD/qquickwheelarea_p.h \ $$PWD/qquickabstractstyle_p.h \ @@ -10,9 +12,11 @@ HEADERS += \ $$PWD/qquickcontrolsprivate_p.h SOURCES += \ + $$PWD/qquickcalendarmodel.cpp \ $$PWD/qquicktooltip.cpp \ $$PWD/qquickspinboxvalidator.cpp \ $$PWD/qquickrangemodel.cpp \ + $$PWD/qquickrangeddate.cpp \ $$PWD/qquickcontrolsettings.cpp \ $$PWD/qquickwheelarea.cpp \ $$PWD/qquickabstractstyle.cpp @@ -30,6 +34,8 @@ PRIVATE_QML_FILES += \ $$PWD/TabBar.qml \ $$PWD/BasicButton.qml \ $$PWD/Control.qml \ + $$PWD/CalendarHeaderModel.qml \ + $$PWD/CalendarUtils.js \ $$PWD/FastGlow.qml \ $$PWD/SourceProxy.qml\ $$PWD/Style.qml \ diff --git a/src/controls/Private/qmldir b/src/controls/Private/qmldir index 7536b7e6..99740190 100644 --- a/src/controls/Private/qmldir +++ b/src/controls/Private/qmldir @@ -1,6 +1,8 @@ module QtQuick.Controls.Private AbstractCheckable 1.0 AbstractCheckable.qml +CalendarHeaderModel 1.0 CalendarHeaderModel.qml Control 1.0 Control.qml +CalendarUtils 1.0 CalendarUtils.js FocusFrame 1.0 FocusFrame.qml Margins 1.0 Margins.qml BasicButton 1.0 BasicButton.qml diff --git a/src/controls/Private/qquickcalendarmodel.cpp b/src/controls/Private/qquickcalendarmodel.cpp new file mode 100644 index 00000000..83c2885c --- /dev/null +++ b/src/controls/Private/qquickcalendarmodel.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickcalendarmodel_p.h" + +namespace { + static const int daysInAWeek = 7; + + /* + Not the number of weeks per month, but the number of weeks that are + shown on a typical calendar. + */ + static const int weeksOnACalendarMonth = 6; + + /* + The amount of days to populate the calendar with. + */ + static const int daysOnACalendarMonth = daysInAWeek * weeksOnACalendarMonth; +} + +QT_BEGIN_NAMESPACE + +/*! + QQuickCalendarModel provides a model for the Calendar control. + It is responsible for populating itself with dates based on a given month + and year. + + The model stores a list of dates whose indices map directly to the Calendar. + For example, the model would store the following dates when any day in + January 2015 is selected on the Calendar: + + [30/12/2014][31/12/2014][01/01/2015]...[31/01/2015][01/02/2015]...[09/02/2015] + + The Calendar would then display the dates in the same order within a grid: + + January 2015 + + [30][31][01][02][03][04][05] + [06][07][08][09][10][11][12] + [13][14][15][16][17][18][19] + [20][21][22][23][24][25][26] + [27][28][29][30][31][01][02] + [03][04][05][06][07][08][09] +*/ + +QQuickCalendarModel::QQuickCalendarModel(QObject *parent) : + QAbstractListModel(parent) +{ +} + +/*! + The date that determines which dates are stored. + + We store all of the days in the month of visibleDate, as well as any days + in the previous or following month if there is enough space. +*/ +QDate QQuickCalendarModel::visibleDate() const +{ + return mVisibleDate; +} + +/*! + Sets the visible date to \a visibleDate. + + If \a visibleDate is a valid date and is different than the previously + visible date, the visible date is changed and + populateFromVisibleDate() called. +*/ +void QQuickCalendarModel::setVisibleDate(const QDate &date) +{ + if (date != mVisibleDate && date.isValid()) { + const QDate previousDate = mVisibleDate; + mVisibleDate = date; + populateFromVisibleDate(previousDate); + emit visibleDateChanged(date); + } +} + +/*! + The locale set on the Calendar. + + This affects which dates are stored for visibleDate(). For example, if + the locale is en_US, the first day of the week is Sunday. Therefore, if + visibleDate() is some day in January 2014, there will be three days + displayed before the 1st of January: + + + January 2014 + + [SO][MO][TU][WE][TH][FR][SA] + [29][30][31][01][02][03][04] + ... + + If the locale is then changed to en_GB (with the same visibleDate()), + there will be 2 days before the 1st of January, because Monday is the + first day of the week for that locale: + + January 2014 + + [MO][TU][WE][TH][FR][SA][SO] + [30][31][01][02][03][04][05] + ... +*/ +QLocale QQuickCalendarModel::locale() const +{ + return mLocale; +} + +/*! + Sets the locale to \a locale. +*/ +void QQuickCalendarModel::setLocale(const QLocale &locale) +{ + if (locale != mLocale) { + Qt::DayOfWeek previousFirstDayOfWeek = mLocale.firstDayOfWeek(); + mLocale = locale; + emit localeChanged(mLocale); + if (mLocale.firstDayOfWeek() != previousFirstDayOfWeek) { + // We don't have a previousDate, so just use our current one... + // it's ignored anyway, since we're forcing the repopulation. + populateFromVisibleDate(mVisibleDate, true); + } + } +} + +QVariant QQuickCalendarModel::data(const QModelIndex &index, int role) const +{ + if (role == DateRole) + return mVisibleDates.at(index.row()); + return QVariant(); +} + +int QQuickCalendarModel::rowCount(const QModelIndex &) const +{ + return mVisibleDates.isEmpty() ? 0 : weeksOnACalendarMonth * daysInAWeek; +} + +QHash<int, QByteArray> QQuickCalendarModel::roleNames() const +{ + QHash<int, QByteArray> names; + names[DateRole] = QByteArrayLiteral("date"); + return names; +} + +/*! + Returns the date at \a index, or an invalid date if \a index is invalid. +*/ +QDate QQuickCalendarModel::dateAt(int index) const +{ + return index >= 0 && index < mVisibleDates.size() ? mVisibleDates.at(index) : QDate(); +} + +/*! + Returns the index for \a date, or -1 if \a date is outside of our range. +*/ +int QQuickCalendarModel::indexAt(const QDate &date) +{ + if (mVisibleDates.size() == 0 || date < mFirstVisibleDate || date > mLastVisibleDate) + return -1; + + // The index of the visible date will be the days from the + // previous month that we had to display before it, plus the + // day of the visible date itself. + return qMax(qint64(0), mFirstVisibleDate.daysTo(date)); +} + +/*! + Returns the week number for the first day of the week corresponding to \a row, + or -1 if \a row is outside of our range. +*/ +int QQuickCalendarModel::weekNumberAt(int row) const +{ + const int index = row * daysInAWeek; + const QDate date = dateAt(index); + if (date.isValid()) + return date.weekNumber(); + return -1; +} + +/*! + Called before visibleDateChanged() is emitted. + + This function is called even when just the day has changed, in which case + it does nothing. + + The \a force parameter is used when the locale has changed; the month + shown doesn't change, but the days displayed do. + The \a previousDate parameter is ignored when \a force is true. +*/ +void QQuickCalendarModel::populateFromVisibleDate(const QDate &previousDate, bool force) +{ + // We don't need to populate if the year and month haven't changed. + if (!force && mVisibleDate.year() == previousDate.year() && mVisibleDate.month() == previousDate.month()) + return; + + // Since our model is of a fixed size, we fill it once and assign values each time + // the month changes, instead of clearing and appending each time. + bool isEmpty = mVisibleDates.isEmpty(); + if (isEmpty) { + beginResetModel(); + mVisibleDates.fill(QDate(), daysOnACalendarMonth); + } + + // The actual first (1st) day of the month. + QDate firstDayOfMonthDate(mVisibleDate.year(), mVisibleDate.month(), 1); + int difference = ((firstDayOfMonthDate.dayOfWeek() - mLocale.firstDayOfWeek()) + 7) % 7; + // The first day to display should never be the 1st of the month, as we want some days from + // the previous month to be visible. + if (difference == 0) + difference += daysInAWeek; + QDate firstDateToDisplay = firstDayOfMonthDate.addDays(-difference); + for (int i = 0; i < daysOnACalendarMonth; ++i) + mVisibleDates[i] = firstDateToDisplay.addDays(i); + + mFirstVisibleDate = mVisibleDates.at(0); + mLastVisibleDate = mVisibleDates.at(mVisibleDates.size() - 1); + + if (!isEmpty) { + emit dataChanged(index(0, 0), index(rowCount() - 1, 0)); + } else { + endResetModel(); + emit countChanged(rowCount()); + } +} + +QT_END_NAMESPACE diff --git a/src/controls/Private/qquickcalendarmodel_p.h b/src/controls/Private/qquickcalendarmodel_p.h new file mode 100644 index 00000000..3505f3a4 --- /dev/null +++ b/src/controls/Private/qquickcalendarmodel_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKCALENDARMODEL_H +#define QQUICKCALENDARMODEL_H + +#include <QObject> +#include <QAbstractListModel> +#include <QLocale> +#include <QVariant> +#include <QDate> + +QT_BEGIN_NAMESPACE + +class QQuickCalendarModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QDate visibleDate READ visibleDate WRITE setVisibleDate NOTIFY visibleDateChanged) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + explicit QQuickCalendarModel(QObject *parent = 0); + + enum { + // If this class is made public, this will have to be changed. + DateRole = Qt::UserRole + 1 + }; + + QDate visibleDate() const; + void setVisibleDate(const QDate &visibleDate); + + QLocale locale() const; + void setLocale(const QLocale &locale); + + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + + int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + + QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; + + Q_INVOKABLE QDate dateAt(int index) const; + Q_INVOKABLE int indexAt(const QDate &visibleDate); + Q_INVOKABLE int weekNumberAt(int row) const; + +Q_SIGNALS: + void visibleDateChanged(const QDate &visibleDate); + void localeChanged(const QLocale &locale); + void countChanged(int count); + +protected: + void populateFromVisibleDate(const QDate &previousDate, bool force = false); + + QDate mVisibleDate; + QDate mFirstVisibleDate; + QDate mLastVisibleDate; + QVector<QDate> mVisibleDates; + QLocale mLocale; +}; + +QT_END_NAMESPACE + +#endif // QQUICKCALENDARMODEL_H diff --git a/src/controls/Private/qquickrangeddate.cpp b/src/controls/Private/qquickrangeddate.cpp new file mode 100644 index 00000000..e76e3cfb --- /dev/null +++ b/src/controls/Private/qquickrangeddate.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickrangeddate_p.h" + +QT_BEGIN_NAMESPACE + +// JavaScript Date > QDate conversion is not correct for large negative dates. +Q_GLOBAL_STATIC_WITH_ARGS(const QDate, jsMinimumDate, (QDate(1, 1, 1))) +Q_GLOBAL_STATIC_WITH_ARGS(const QDate, jsMaximumDate, (QDate(275759, 10, 25))) + +QQuickRangedDate::QQuickRangedDate() : + QObject(0), + mDate(QDate::currentDate()), + mMinimumDate(*jsMinimumDate), + mMaximumDate(*jsMaximumDate) +{ +} + +/*! \internal + \qmlproperty date QQuickRangedDate::date +*/ +void QQuickRangedDate::setDate(const QDate &date) +{ + if (date == mDate) + return; + + if (date < mMinimumDate) { + mDate = mMinimumDate; + } else if (date > mMaximumDate) { + mDate = mMaximumDate; + } else { + mDate = date; + } + + emit dateChanged(); +} + +/*! \internal + \qmlproperty date QQuickRangedDate::minimumDate +*/ +void QQuickRangedDate::setMinimumDate(const QDate &minimumDate) +{ + if (minimumDate == mMinimumDate) + return; + + mMinimumDate = qMax(minimumDate, *jsMinimumDate); + emit minimumDateChanged(); + + // If the new minimumDate makes date invalid, clamp date to it. + if (mDate < mMinimumDate) { + mDate = mMinimumDate; + emit dateChanged(); + } +} + +/*! \internal + \qmlproperty date QQuickRangedDate::maximumDate +*/ +void QQuickRangedDate::setMaximumDate(const QDate &maximumDate) +{ + if (maximumDate == mMaximumDate) + return; + + // If the new maximumDate is smaller than minimumDate, clamp maximumDate to it. + // If the new maximumDate is larger than jsMaximumDate, also clamp it. + mMaximumDate = maximumDate < mMinimumDate ? mMinimumDate : qMin(maximumDate, *jsMaximumDate); + emit maximumDateChanged(); + + // If the new maximumDate makes the date invalid, clamp it. + if (mDate > mMaximumDate) { + mDate = mMaximumDate; + emit dateChanged(); + } +} + +QT_END_NAMESPACE diff --git a/src/controls/Private/qquickrangeddate_p.h b/src/controls/Private/qquickrangeddate_p.h new file mode 100644 index 00000000..975bd27b --- /dev/null +++ b/src/controls/Private/qquickrangeddate_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** 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, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKRANGEDDATE_H +#define QQUICKRANGEDDATE_H + +#include <QDate> + +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QQuickRangedDate : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged RESET resetDate) + Q_PROPERTY(QDate minimumDate READ minimumDate WRITE setMinimumDate NOTIFY minimumDateChanged RESET resetMinimumDate) + Q_PROPERTY(QDate maximumDate READ maximumDate WRITE setMaximumDate NOTIFY maximumDateChanged RESET resetMaximumDate) +public: + QQuickRangedDate(); + ~QQuickRangedDate() {} + + QDate date() const { return mDate; } + void setDate(const QDate &date); + void resetDate() {} + + QDate minimumDate() const { return mMinimumDate; } + void setMinimumDate(const QDate &minimumDate); + void resetMinimumDate() {} + + QDate maximumDate() const { return mMaximumDate; } + void setMaximumDate(const QDate &maximumDate); + void resetMaximumDate() {} + +Q_SIGNALS: + void dateChanged(); + void minimumDateChanged(); + void maximumDateChanged(); + +private: + QDate mDate; + QDate mMinimumDate; + QDate mMaximumDate; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickRangedDate) + +#endif // QQUICKRANGEDDATE_H diff --git a/src/controls/Styles/Base/CalendarStyle.qml b/src/controls/Styles/Base/CalendarStyle.qml new file mode 100644 index 00000000..99330ffe --- /dev/null +++ b/src/controls/Styles/Base/CalendarStyle.qml @@ -0,0 +1,552 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Private 1.0 + +/*! + \qmltype CalendarStyle + \inqmlmodule QtQuick.Controls.Styles + \since 5.3 + \ingroup controlsstyling + \brief Provides custom styling for \l Calendar + + Example: + \qml + Calendar { + anchors.centerIn: parent + gridVisible: false + + style: CalendarStyle { + dayDelegate: Rectangle { + gradient: Gradient { + GradientStop { + position: 0.00 + color: styleData.selected ? "#111" : (styleData.visibleMonth && styleData.valid ? "#444" : "#666"); + } + GradientStop { + position: 1.00 + color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666"); + } + GradientStop { + position: 1.00 + color: styleData.selected ? "#777" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666"); + } + } + + Label { + text: styleData.date.getDate() + anchors.centerIn: parent + color: styleData.valid ? "white" : "grey" + } + + Rectangle { + width: parent.width + height: 1 + color: "#555" + anchors.bottom: parent.bottom + } + + Rectangle { + width: 1 + height: parent.height + color: "#555" + anchors.right: parent.right + } + } + } + } + \endqml +*/ + +Style { + id: calendarStyle + + /*! + The Calendar attached to this style. + */ + property Calendar control: __control + + /*! + The color of the grid lines. + */ + property color gridColor: "#f0f0f0" + + /*! + \internal + + The width of each grid line. + */ + property real __gridLineWidth: 1 + + function __cellRectAt(index) { + return CalendarUtils.cellRectAt(index, control.__panel.columns, control.__panel.rows, + control.__panel.availableWidth, control.__panel.availableHeight); + } + + function __cellIndexAt(mouseX, mouseY) { + return CalendarUtils.cellIndexAt(mouseX, mouseY, control.__panel.columns, control.__panel.rows, + control.__panel.availableWidth, control.__panel.availableHeight); + } + + function __isValidDate(date) { + return date !== undefined + && date.getTime() >= control.minimumDate.getTime() + && date.getTime() <= control.maximumDate.getTime(); + } + + /*! + The background of the calendar. + */ + property Component background: Rectangle { + color: "#fff" + } + + /*! + The navigation bar of the calendar. + + Styles the bar at the top of the calendar that contains the + next month/previous month buttons and the selected date label. + */ + property Component navigationBar: Item { + height: 50 + + Button { + id: previousMonth + width: parent.height * 0.6 + height: width + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: (parent.height - height) / 2 + iconSource: "images/arrow-left.png" + + onClicked: control.showPreviousMonth() + } + Label { + id: dateText + text: styleData.title + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: 14 + anchors.verticalCenter: parent.verticalCenter + anchors.left: previousMonth.right + anchors.leftMargin: 2 + anchors.right: nextMonth.left + anchors.rightMargin: 2 + } + Button { + id: nextMonth + width: parent.height * 0.6 + height: width + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: (parent.height - height) / 2 + iconSource: "images/arrow-right.png" + + onClicked: control.showNextMonth() + } + } + + /*! + The delegate that styles each date in the calendar. + + The properties provided to each delegate are: + \table + \row \li readonly property date \b styleData.date + \li The date this delegate represents. + \row \li readonly property bool \b styleData.selected + \li \c true if this is the selected date. + \row \li readonly property int \b styleData.index + \li The index of this delegate. + \row \li readonly property bool \b styleData.valid + \li \c true if this date is greater than or equal to than \l {Calendar::minimumDate}{minimumDate} and + less than or equal to \l {Calendar::maximumDate}{maximumDate}. + \row \li readonly property bool \b styleData.today + \li \c true if this date is equal to today's date. + \row \li readonly property bool \b styleData.visibleMonth + \li \c true if the month in this date is the visible month. + \row \li readonly property bool \b styleData.hovered + \li \c true if the mouse is over this cell. + \note This property is \c true even when the mouse is hovered over an invalid date. + \row \li readonly property bool \b styleData.pressed + \li \c true if the mouse is pressed on this cell. + \note This property is \c true even when the mouse is pressed on an invalid date. + \endtable + */ + property Component dayDelegate: Rectangle { + color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "white"/*"transparent"*/ + readonly property color sameMonthDateTextColor: "black" + readonly property color selectedDateColor: __syspal.highlight + readonly property color selectedDateTextColor: "white" + readonly property color differentMonthDateTextColor: Qt.darker("darkgrey", 1.4); + readonly property color invalidDateColor: "#dddddd" + + Label { + id: dayDelegateText + text: styleData.date.getDate() + anchors.centerIn: parent + horizontalAlignment: Text.AlignRight + color: { + var theColor = invalidDateColor; + if (styleData.valid) { + // Date is within the valid range. + theColor = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor; + if (styleData.selected) + theColor = selectedDateTextColor; + } + theColor; + } + } + } + + /*! + The delegate that styles each weekday. + */ + property Component dayOfWeekDelegate: Rectangle { + color: "white" + Label { + text: control.__locale.dayName(styleData.dayOfWeek, control.dayOfWeekFormat) + anchors.centerIn: parent + } + } + + /*! + The delegate that styles each week number. + */ + property Component weekNumberDelegate: Rectangle { + color: "white" + Label { + text: styleData.weekNumber + anchors.centerIn: parent + } + } + + /*! \internal */ + property Component panel: Item { + id: panelItem + + implicitWidth: 200 + implicitHeight: 200 + + property alias navigationBarItem: navigationBarLoader.item + + readonly property real dayOfWeekHeaderRowHeight: 40 + + readonly property int weeksToShow: 6 + readonly property int rows: weeksToShow + readonly property int columns: CalendarUtils.daysInAWeek + + // The combined available width and height to be shared amongst each cell. + readonly property real availableWidth: (viewContainer.width - (control.gridVisible ? __gridLineWidth : 0)) + readonly property real availableHeight: (viewContainer.height - (control.gridVisible ? __gridLineWidth : 0)) + + property int hoveredCellIndex: -1 + property int pressedCellIndex: -1 + + Loader { + id: backgroundLoader + anchors.fill: parent + sourceComponent: background + } + + Loader { + id: navigationBarLoader + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + sourceComponent: navigationBar + + property QtObject styleData: QtObject { + readonly property string title: control.__locale.standaloneMonthName(control.visibleMonth) + + new Date(control.visibleYear, control.visibleMonth, 1).toLocaleDateString(control.__locale, " yyyy") + } + } + + Row { + id: dayOfWeekHeaderRow + spacing: (control.gridVisible ? __gridLineWidth : 0) + anchors.top: navigationBarLoader.bottom + anchors.left: parent.left + anchors.leftMargin: (control.weekNumbersVisible ? weekNumbersItem.width : 0) + (control.gridVisible ? __gridLineWidth : 0) + anchors.right: parent.right + height: dayOfWeekHeaderRowHeight + + Repeater { + id: repeater + model: CalendarHeaderModel { + locale: control.__locale + } + Loader { + id: dayOfWeekDelegateLoader + sourceComponent: dayOfWeekDelegate + width: __cellRectAt(index).width - (control.gridVisible ? __gridLineWidth : 0) + height: dayOfWeekHeaderRow.height + + readonly property var __dayOfWeek: dayOfWeek + + property QtObject styleData: QtObject { + readonly property alias dayOfWeek: dayOfWeekDelegateLoader.__dayOfWeek + } + } + } + } + + Row { + id: gridRow + width: weekNumbersItem.width + viewContainer.width + height: viewContainer.height + anchors.top: dayOfWeekHeaderRow.bottom + + Item { + id: weekNumbersItem + visible: control.weekNumbersVisible + width: 30 + height: viewContainer.height + + Repeater { + id: weekNumberRepeater + model: panelItem.weeksToShow + + Loader { + id: weekNumberDelegateLoader + y: __cellRectAt(index * panelItem.columns).y + (control.gridVisible ? __gridLineWidth : 0) + width: weekNumbersItem.width + height: __cellRectAt(index * panelItem.columns).height - (control.gridVisible ? __gridLineWidth : 0) + sourceComponent: weekNumberDelegate + + readonly property int __index: index + property int __weekNumber: control.__model.weekNumberAt(index) + + Connections { + target: control + onVisibleMonthChanged: __weekNumber = control.__model.weekNumberAt(index) + onVisibleYearChanged: __weekNumber = control.__model.weekNumberAt(index) + } + + Connections { + target: control.__model + onCountChanged: __weekNumber = control.__model.weekNumberAt(index) + } + + property QtObject styleData: QtObject { + readonly property alias index: weekNumberDelegateLoader.__index + readonly property int weekNumber: weekNumberDelegateLoader.__weekNumber + } + } + } + } + + // Contains the grid lines and the grid itself. + Item { + id: viewContainer + width: panelItem.width - (control.weekNumbersVisible ? weekNumbersItem.width : 0) + height: panelItem.height - navigationBarLoader.height - dayOfWeekHeaderRow.height + + Repeater { + id: verticalGridLineRepeater + model: panelItem.columns + 1 + delegate: Rectangle { + // The last line will be an invalid index, so we must handle it + x: index < panelItem.columns + ? __cellRectAt(index).x + : __cellRectAt(panelItem.columns - 1).x + __cellRectAt(panelItem.columns - 1).width + y: 0 + width: __gridLineWidth + height: viewContainer.height + color: gridColor + visible: control.gridVisible + } + } + + Repeater { + id: horizontalGridLineRepeater + model: panelItem.rows + 1 + delegate: Rectangle { + x: 0 + // The last line will be an invalid index, so we must handle it + y: index < panelItem.columns - 1 + ? __cellRectAt(index * panelItem.columns).y + : __cellRectAt((panelItem.rows - 1) * panelItem.columns).y + __cellRectAt((panelItem.rows - 1) * panelItem.columns).height + width: viewContainer.width + height: __gridLineWidth + color: gridColor + visible: control.gridVisible + } + } + + Connections { + target: control + onSelectedDateChanged: view.selectedDateChanged() + } + + Repeater { + id: view + + property int currentIndex: -1 + + model: control.__model + + Component.onCompleted: selectedDateChanged() + + function selectedDateChanged() { + if (model !== undefined && model.locale !== undefined) { + currentIndex = model.indexAt(control.selectedDate); + } + } + + delegate: Loader { + id: delegateLoader + + x: __cellRectAt(index).x + (control.gridVisible ? __gridLineWidth : 0) + y: __cellRectAt(index).y + (control.gridVisible ? __gridLineWidth : 0) + width: __cellRectAt(index).width - (control.gridVisible ? __gridLineWidth : 0) + height: __cellRectAt(index).height - (control.gridVisible ? __gridLineWidth : 0) + + sourceComponent: dayDelegate + + readonly property int __index: index + readonly property date __date: date + // We rely on the fact that an invalid QDate will be converted to a Date + // whose year is -4713, which is always an invalid date since our + // earliest minimum date is the year 1. + readonly property bool valid: __isValidDate(date) + + property QtObject styleData: QtObject { + readonly property alias index: delegateLoader.__index + readonly property bool selected: control.selectedDate.getTime() === date.getTime() + readonly property alias date: delegateLoader.__date + readonly property bool valid: delegateLoader.valid + // TODO: this will not be correct if the app is running when a new day begins. + readonly property bool today: date.getTime() === new Date().setHours(0, 0, 0, 0) + readonly property bool visibleMonth: date.getMonth() === control.visibleMonth + readonly property bool hovered: panelItem.hoveredCellIndex == index + readonly property bool pressed: panelItem.pressedCellIndex == index + // todo: pressed property here, clicked and doubleClicked in the control itself + } + } + } + + MouseArea { + anchors.fill: parent + + hoverEnabled: true + + onEntered: { + var indexOfCell = __cellIndexAt(mouseX, mouseY); + hoveredCellIndex = indexOfCell; + var date = view.model.dateAt(indexOfCell); + if (__isValidDate(date)) { + control.hovered(date); + } + } + + onExited: { + hoveredCellIndex = -1; + } + + onPositionChanged: { + var indexOfCell = __cellIndexAt(mouse.x, mouse.y); + var previousHoveredCellIndex = hoveredCellIndex; + hoveredCellIndex = indexOfCell; + if (indexOfCell !== -1) { + var date = view.model.dateAt(indexOfCell); + if (__isValidDate(date)) { + if (hoveredCellIndex !== previousHoveredCellIndex) + control.hovered(date); + + if (pressed && date.getTime() !== control.selectedDate.getTime()) { + control.selectedDate = date; + pressedCellIndex = indexOfCell; + control.pressed(date); + } + } + } + } + + onPressed: { + var indexOfCell = __cellIndexAt(mouse.x, mouse.y); + if (indexOfCell !== -1) { + var date = view.model.dateAt(indexOfCell); + pressedCellIndex = indexOfCell; + if (__isValidDate(date)) { + control.selectedDate = date; + control.pressed(date); + } + } + } + + onReleased: { + var indexOfCell = __cellIndexAt(mouse.x, mouse.y); + if (indexOfCell !== -1) { + // The cell index might be valid, but the date has to be too. We could let the + // selected date validation take care of this, but then the selected date would + // change to the earliest day if a day before the minimum date is clicked, for example. + var date = view.model.dateAt(indexOfCell); + if (__isValidDate(date)) { + control.released(date); + } + } + pressedCellIndex = -1; + } + + onClicked: { + var indexOfCell = __cellIndexAt(mouse.x, mouse.y); + if (indexOfCell !== -1) { + var date = view.model.dateAt(indexOfCell); + if (__isValidDate(date)) + control.clicked(date); + } + } + + onDoubleClicked: { + var indexOfCell = __cellIndexAt(mouse.x, mouse.y); + if (indexOfCell !== -1) { + var date = view.model.dateAt(indexOfCell); + if (__isValidDate(date)) + control.doubleClicked(date); + } + } + } + } + } + } +} diff --git a/src/controls/Styles/Desktop/CalendarStyle.qml b/src/controls/Styles/Desktop/CalendarStyle.qml new file mode 100644 index 00000000..657947c8 --- /dev/null +++ b/src/controls/Styles/Desktop/CalendarStyle.qml @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick.Controls.Styles 1.1 + +CalendarStyle {} diff --git a/src/controls/Styles/qmldir b/src/controls/Styles/qmldir index 4ffb08ee..f0e6fcc1 100644 --- a/src/controls/Styles/qmldir +++ b/src/controls/Styles/qmldir @@ -1,6 +1,7 @@ module QtQuick.Controls.Styles ButtonStyle 1.0 Base/ButtonStyle.qml BusyIndicatorStyle 1.1 Base/BusyIndicatorStyle.qml +CalendarStyle 1.1 Base/CalendarStyle.qml CheckBoxStyle 1.0 Base/CheckBoxStyle.qml ComboBoxStyle 1.0 Base/ComboBoxStyle.qml MenuStyle 1.2 Base/MenuStyle.qml diff --git a/src/controls/Styles/styles.pri b/src/controls/Styles/styles.pri index d695893a..9199dfc5 100644 --- a/src/controls/Styles/styles.pri +++ b/src/controls/Styles/styles.pri @@ -3,6 +3,7 @@ STYLES_QML_FILES = \ $$PWD/Base/ButtonStyle.qml \ $$PWD/Base/BusyIndicatorStyle.qml \ + $$PWD/Base/CalendarStyle.qml \ $$PWD/Base/CheckBoxStyle.qml \ $$PWD/Base/ComboBoxStyle.qml \ $$PWD/Base/FocusFrameStyle.qml \ @@ -28,6 +29,7 @@ STYLES_QML_FILES += \ $$PWD/Desktop/qmldir \ $$PWD/Desktop/RowItemSingleton.qml \ $$PWD/Desktop/ButtonStyle.qml \ + $$PWD/Desktop/CalendarStyle.qml \ $$PWD/Desktop/BusyIndicatorStyle.qml \ $$PWD/Desktop/CheckBoxStyle.qml \ $$PWD/Desktop/ComboBoxStyle.qml \ diff --git a/src/controls/controls.pro b/src/controls/controls.pro index e31f8683..e19d5d18 100644 --- a/src/controls/controls.pro +++ b/src/controls/controls.pro @@ -10,6 +10,7 @@ CONTROLS_QML_FILES = \ ApplicationWindow.qml \ Button.qml \ BusyIndicator.qml \ + Calendar.qml \ CheckBox.qml \ ComboBox.qml \ GroupBox.qml \ diff --git a/src/controls/doc/images/qtquickcontrols-example-calendar.png b/src/controls/doc/images/qtquickcontrols-example-calendar.png Binary files differnew file mode 100644 index 00000000..57db0a12 --- /dev/null +++ b/src/controls/doc/images/qtquickcontrols-example-calendar.png diff --git a/src/controls/doc/src/qtquickcontrols-examples.qdoc b/src/controls/doc/src/qtquickcontrols-examples.qdoc index 4bb99d8e..1531e3ff 100644 --- a/src/controls/doc/src/qtquickcontrols-examples.qdoc +++ b/src/controls/doc/src/qtquickcontrols-examples.qdoc @@ -105,3 +105,13 @@ for touch input using \l{Qt Quick Controls}. */ +/*! + \example controls/calendar + \title Qt Quick Controls - Calendar Example + \ingroup qtquickcontrols_examples + \brief Demonstrates the use of Calendar to display events + \image qtquickcontrols-example-calendar.png + + This example shows how Calendar can be used to view events retrieved from + an SQL database. +*/ diff --git a/src/controls/plugin.cpp b/src/controls/plugin.cpp index aba18659..40cc38e3 100644 --- a/src/controls/plugin.cpp +++ b/src/controls/plugin.cpp @@ -49,6 +49,9 @@ #include "qquickstack_p.h" #include "qquickdesktopiconprovider_p.h" #include "qquickselectionmode_p.h" + +#include "Private/qquickcalendarmodel_p.h" +#include "Private/qquickrangeddate_p.h" #include "Private/qquickrangemodel_p.h" #include "Private/qquickwheelarea_p.h" #include "Private/qquicktooltip_p.h" @@ -75,6 +78,7 @@ static const struct { } qmldir [] = { { "ApplicationWindow", 1, 0 }, { "Button", 1, 0 }, + { "Calendar", 1, 2 }, { "CheckBox", 1, 0 }, { "ComboBox", 1, 0 }, { "GroupBox", 1, 0 }, @@ -131,7 +135,9 @@ void QtQuickControlsPlugin::initializeEngine(QQmlEngine *engine, const char *uri // Register private API const char *private_uri = "QtQuick.Controls.Private"; qmlRegisterType<QQuickAbstractStyle>(private_uri, 1, 0, "AbstractStyle"); + qmlRegisterType<QQuickCalendarModel>(private_uri, 1, 0, "CalendarModel"); qmlRegisterType<QQuickPadding>(); + qmlRegisterType<QQuickRangedDate>(private_uri, 1, 0, "RangedDate"); qmlRegisterType<QQuickRangeModel>(private_uri, 1, 0, "RangeModel"); qmlRegisterType<QQuickWheelArea>(private_uri, 1, 0, "WheelArea"); qmlRegisterType<QQuickSpinBoxValidator>(private_uri, 1, 0, "SpinBoxValidator"); diff --git a/tests/auto/controls/controls.pro b/tests/auto/controls/controls.pro index ce3857a6..fedc36c4 100644 --- a/tests/auto/controls/controls.pro +++ b/tests/auto/controls/controls.pro @@ -15,6 +15,8 @@ TESTDATA = $$PWD/data/* OTHER_FILES += \ $$PWD/data/tst_button.qml \ $$PWD/data/tst_busyindicator.qml \ + $$PWD/data/tst_calendar.qml \ + $$PWD/data/tst_rangeddate.qml \ $$PWD/data/tst_shortcuts.qml \ $$PWD/data/tst_spinbox.qml \ $$PWD/data/tst_tableview.qml \ diff --git a/tests/auto/controls/data/tst_calendar.qml b/tests/auto/controls/data/tst_calendar.qml new file mode 100644 index 00000000..2032029f --- /dev/null +++ b/tests/auto/controls/data/tst_calendar.qml @@ -0,0 +1,798 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls.Private 1.0 +import QtTest 1.0 + +Item { + id: container + width: 300 + height: 300 + + TestCase { + id: testcase + name: "Tests_Calendar" + when: windowShown + readonly property int navigationBarHeight: calendar !== undefined ? calendar.__panel.navigationBarItem.height : 0 + readonly property int dayOfWeekHeaderRowHeight: calendar !== undefined ? calendar.__panel.dayOfWeekHeaderRowHeight : 0 + readonly property int firstDateCellX: 0 + readonly property int firstDateCellY: navigationBarHeight + dayOfWeekHeaderRowHeight + readonly property int previousMonthButtonX: navigationBarHeight / 2 + readonly property int previousMonthButtonY: navigationBarHeight / 2 + readonly property int nextMonthButtonX: calendar ? calendar.width - navigationBarHeight / 2 : 0 + readonly property int nextMonthButtonY: navigationBarHeight / 2 + + property var calendar + + SignalSpy { + id: hoveredSignalSpy + } + + SignalSpy { + id: pressedSignalSpy + } + + SignalSpy { + id: clickedSignalSpy + } + + SignalSpy { + id: releasedSignalSpy + } + + function init() { + calendar = Qt.createQmlObject("import QtQuick.Controls 1.2; " + + " Calendar { }", container, ""); + calendar.width = 300; + calendar.height = 300; + } + + function cleanup() { + hoveredSignalSpy.clear(); + pressedSignalSpy.clear(); + clickedSignalSpy.clear(); + releasedSignalSpy.clear(); + } + + function toPixelsX(cellPosX) { + var cellRect = calendar.__style.__cellRectAt(cellPosX); + return firstDateCellX + cellRect.x + cellRect.width / 2; + } + + function toPixelsY(cellPosY) { + var cellRect = calendar.__style.__cellRectAt(cellPosY * calendar.__panel.columns); + return firstDateCellY + cellRect.y + cellRect.height / 2; + } + + function test_defaultConstructed() { + calendar.minimumDate = new Date(1, 0, 1); + calendar.maximumDate = new Date(4000, 0, 1); + + compare(calendar.minimumDate, new Date(1, 0, 1)); + compare(calendar.maximumDate, new Date(4000, 0, 1)); + compare(calendar.selectedDate, new Date(new Date().setHours(0, 0, 0, 0))); + compare(calendar.gridVisible, true); + compare(calendar.dayOfWeekFormat, Locale.ShortFormat); + compare(calendar.__locale, Qt.locale()); + } + + function test_setAfterConstructed() { + calendar.minimumDate = new Date(1900, 0, 1); + calendar.maximumDate = new Date(1999, 11, 31); + calendar.selectedDate = new Date(1980, 0, 1); + calendar.gridVisible = false; + calendar.dayOfWeekFormat = Locale.NarrowFormat; + calendar.__locale = Qt.locale("de_DE"); + + compare(calendar.minimumDate, new Date(1900, 0, 1)); + compare(calendar.maximumDate, new Date(1999, 11, 31)); + compare(calendar.selectedDate, new Date(1980, 0, 1)); + compare(calendar.gridVisible, false); + compare(calendar.__locale, Qt.locale("de_DE")); + } + + function test_selectedDate() { + calendar.minimumDate = new Date(2012, 0, 1); + calendar.maximumDate = new Date(2013, 0, 1); + + // Equal to minimumDate date. + calendar.selectedDate = new Date(calendar.minimumDate); + compare(calendar.selectedDate, calendar.minimumDate); + + // Outside minimum date. + calendar.selectedDate.setDate(calendar.selectedDate.getDate() - 1); + compare(calendar.selectedDate, calendar.minimumDate); + + // Equal to maximum date. + calendar.selectedDate = new Date(calendar.maximumDate); + compare(calendar.selectedDate, calendar.maximumDate); + + // Outside maximumDate date. + calendar.selectedDate.setDate(calendar.selectedDate.getDate() - 1); + compare(calendar.selectedDate, calendar.maximumDate); + + // Should not change. + calendar.selectedDate = undefined; + compare(calendar.selectedDate, calendar.maximumDate); + } + + // Should be able to use the full JS date range. + function test_minMaxJsDateRange() { + skip("QTBUG-36846"); + + calendar.minimumDate = CalendarUtils.minimumCalendarDate; + calendar.maximumDate = CalendarUtils.maximumCalendarDate; + + calendar.selectedDate = CalendarUtils.minimumCalendarDate; + compare(calendar.selectedDate.getTime(), CalendarUtils.minimumCalendarDate.getTime()); + + calendar.selectedDate = CalendarUtils.maximumCalendarDate; + compare(calendar.selectedDate.getTime(), CalendarUtils.maximumCalendarDate.getTime()); + } + + function test_minMaxUndefined() { + + var expected = new Date(calendar.minimumDate); + calendar.minimumDate = undefined; + compare(calendar.minimumDate, expected); + + expected = new Date(calendar.maximumDate); + calendar.maximumDate = undefined; + compare(calendar.maximumDate, expected); + } + + function test_keyNavigation() { + calendar.forceActiveFocus(); + calendar.selectedDate = new Date(2013, 0, 1); + // Set this to a certain locale, because days will be in different + // places depending on the system locale of the host machine. + calendar.__locale = Qt.locale("en_GB"); + + /* January 2013 December 2012 + M T W T F S S M T W T F S S + 31 [1] 2 3 4 5 6 26 27 28 29 30 1 2 + 7 8 9 10 11 12 13 3 4 5 6 7 8 9 + 14 15 16 17 18 19 20 ==> 10 11 12 13 14 15 16 + 21 22 23 24 25 26 27 17 18 19 20 21 22 23 + 28 29 30 31 1 2 3 24 25 26 27 28 29 30 + 4 5 6 7 8 9 10 [31] 1 2 3 4 5 6 */ + keyPress(Qt.Key_Left); + compare(calendar.selectedDate, new Date(2012, 11, 31), + "Selecting a day from the previous month should select that date."); + + /* December 2012 December 2012 + M T W T F S S M T W T F S S + 26 27 28 29 30 1 2 26 27 28 29 30 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 ==> 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 17 18 19 20 21 22 23 + 24 25 26 27 28 29 30 24 25 26 27 28 29 [30] + [31] 1 2 3 4 5 6 31 1 2 3 4 5 6 */ + keyPress(Qt.Key_Left); + compare(calendar.selectedDate, new Date(2012, 11, 30), + "Pressing the left key on the left edge should select the date " + + "1 row above on the right edge."); + + /* December 2012 December 2012 + M T W T F S S M T W T F S S + 26 27 28 29 30 1 2 26 27 28 29 30 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 ==> 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 17 18 19 20 21 22 23 + 24 25 26 27 28 29 [30] 24 25 26 27 28 29 30 + 31 1 2 3 4 5 6 [31] 1 2 3 4 5 6 */ + keyPress(Qt.Key_Right); + compare(calendar.selectedDate, new Date(2012, 11, 31), + "Pressing the right key on the right edge should select the date " + + "1 row below on the left edge."); + + /* April 2013 March 2013 + M T W T F S S M T W T F S S + [1] 2 3 4 5 6 7 25 26 27 28 1 2 3 + 8 9 10 11 12 13 14 4 5 6 7 8 9 10 + 15 16 17 18 19 20 21 ==> 11 12 13 14 15 16 17 + 22 23 24 25 26 27 28 18 19 20 21 22 23 24 + 29 30 31 1 2 3 4 25 26 27 28 29 30 [31] + 5 6 7 8 9 10 11 1 2 3 4 5 6 7 */ + calendar.selectedDate = new Date(2013, 3, 1); + keyPress(Qt.Key_Left); + compare(calendar.selectedDate, new Date(2013, 2, 31), + "Pressing the left key on the left edge of the first row should " + + "select the last date of the previous month."); + + /* January 2014 January 2014 + M T W T F S S M T W T F S S + 30 31 [1] 2 3 4 5 30 31 1 2 3 4 5 + 6 7 8 9 10 11 12 6 7 8 9 10 11 12 + 13 14 15 16 17 18 19 ==> 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 20 21 22 23 24 25 26 + 27 28 29 30 31 1 2 27 28 29 30 [31] 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 */ + calendar.selectedDate = new Date(2014, 0, 1); + keyPress(Qt.Key_End); + compare(calendar.selectedDate, new Date(2014, 0, 31), + "Pressing the end key should select the last date in the same month."); + + /* January 2014 January 2014 + M T W T F S S M T W T F S S + 30 31 1 2 3 4 5 30 31 [1] 2 3 4 5 + 6 7 8 9 10 11 12 6 7 8 9 10 11 12 + 13 14 15 16 17 18 19 ==> 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 20 21 22 23 24 25 26 + 27 28 29 30 [31] 1 2 27 28 29 30 31 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 */ + calendar.selectedDate = new Date(2014, 0, 31); + keyPress(Qt.Key_Home); + compare(calendar.selectedDate, new Date(2014, 0, 1), + "Pressing the home key should select the first date in the same month."); + + /* December 2013 November 2013 + M T W T F S S M T W T F S S + 25 26 27 28 29 30 1 28 29 30 31 [1] 2 3 + 2 3 4 5 6 7 8 4 5 6 7 8 9 10 + 9 10 11 12 13 14 15 ==> 11 12 13 14 15 16 17 + 16 17 18 19 20 21 22 18 19 20 21 22 23 24 + 23 24 25 26 27 28 29 25 26 27 28 29 [30] 1 + 30 [31] 1 2 3 4 5 2 3 4 5 6 7 8 */ + calendar.selectedDate = new Date(2013, 11, 31); + keyPress(Qt.Key_PageUp); + compare(calendar.selectedDate, new Date(2013, 10, 30), + "Pressing the page up key should select the equivalent date in the previous month."); + + /* November 2013 December 2013 + M T W T F S S M T W T F S S + 28 29 30 31 [1] 2 3 25 26 27 28 29 30 1 + 4 5 6 7 8 9 10 2 3 4 5 6 7 8 + 11 12 13 14 15 16 17 ==> 9 10 11 12 13 14 15 + 18 19 20 21 22 23 24 16 17 18 19 20 21 22 + 25 26 27 28 29 [30] 1 23 24 25 26 27 28 29 + 2 3 4 5 6 7 8 [30] 31 1 2 3 4 5 */ + calendar.selectedDate = new Date(2013, 10, 30); + keyPress(Qt.Key_PageDown); + compare(calendar.selectedDate, new Date(2013, 11, 30), + "Pressing the page down key should select the equivalent date in the next month."); + } + + function test_selectPreviousMonth() { + calendar.selectedDate = new Date(2013, 0, 1); + compare(calendar.selectedDate, new Date(2013, 0, 1)); + + calendar.__selectPreviousMonth(); + compare(calendar.selectedDate, new Date(2012, 11, 1)); + } + + function test_showPreviousMonthWithMouse() { + calendar.selectedDate = new Date(2013, 1, 28); + compare(calendar.visibleMonth, 1); + compare(calendar.visibleYear, 2013); + + mouseClick(calendar, previousMonthButtonX, previousMonthButtonY, Qt.LeftButton); + compare(calendar.visibleMonth, 0); + compare(calendar.visibleYear, 2013); + + mouseClick(calendar, previousMonthButtonX, previousMonthButtonY, Qt.LeftButton); + compare(calendar.visibleMonth, 11); + compare(calendar.visibleYear, 2012); + } + + function test_selectNextMonth() { + calendar.selectedDate = new Date(2013, 0, 31); + compare(calendar.selectedDate, new Date(2013, 0, 31)); + + calendar.__selectNextMonth(); + compare(calendar.selectedDate, new Date(2013, 1, 28)); + + calendar.__selectNextMonth(); + compare(calendar.selectedDate, new Date(2013, 2, 28)); + } + + function test_showNextMonthWithMouse() { + calendar.selectedDate = new Date(2013, 0, 31); + compare(calendar.visibleMonth, 0); + compare(calendar.visibleYear, 2013); + + mouseClick(calendar, nextMonthButtonX, nextMonthButtonY, Qt.LeftButton); + compare(calendar.visibleMonth, 1); + compare(calendar.visibleYear, 2013); + + mouseClick(calendar, nextMonthButtonX, nextMonthButtonY, Qt.LeftButton); + compare(calendar.visibleMonth, 2); + compare(calendar.visibleYear, 2013); + } + + function test_selectDateWithMouse() { + /* January 2013 + S M T W T F S + 30 31 [1] 2 3 4 5 + 6 7 8 9 10 11 12 + 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 + 27 28 29 30 31 1 2 + 3 4 5 6 7 8 9 */ + + var startDate = new Date(2013, 0, 1); + calendar.selectedDate = startDate; + calendar.__locale = Qt.locale("en_US"); + compare(calendar.selectedDate, startDate); + + pressedSignalSpy.target = calendar; + pressedSignalSpy.signalName = "pressed"; + + clickedSignalSpy.target = calendar; + clickedSignalSpy.signalName = "clicked"; + + releasedSignalSpy.target = calendar; + releasedSignalSpy.signalName = "released"; + + // Clicking on header items should do nothing. + mousePress(calendar, 0, navigationBarHeight, Qt.LeftButton); + compare(calendar.selectedDate, startDate); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + mouseRelease(calendar, 0, navigationBarHeight, Qt.LeftButton); + compare(calendar.selectedDate, startDate); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + var firstVisibleDate = new Date(2012, 11, 30); + for (var week = 0; week < CalendarUtils.weeksOnACalendarMonth; ++week) { + for (var day = 0; day < CalendarUtils.daysInAWeek; ++day) { + var expectedDate = new Date(firstVisibleDate); + var cellIndex = (week * CalendarUtils.daysInAWeek + day); + expectedDate.setDate(expectedDate.getDate() + cellIndex); + + mousePress(calendar, toPixelsX(day), toPixelsY(week), Qt.LeftButton); + compare(calendar.selectedDate, expectedDate); + compare(calendar.__panel.pressedCellIndex, cellIndex); + compare(pressedSignalSpy.count, 1); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + mouseRelease(calendar, toPixelsX(day), toPixelsY(week), Qt.LeftButton); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 1); + compare(releasedSignalSpy.count, 1); + compare(clickedSignalSpy.count, 1); + + pressedSignalSpy.clear(); + releasedSignalSpy.clear(); + clickedSignalSpy.clear(); + + if (expectedDate.getMonth() != startDate.getMonth()) { + // A different month is being displayed as a result of the click; + // change back to the original month so our results are correct. + calendar.selectedDate = startDate; + compare(calendar.selectedDate, startDate); + } + } + } + + // Ensure released event does not trigger date selection. + calendar.selectedDate = startDate; + mousePress(calendar, toPixelsX(1), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, new Date(2012, 11, 31)); + compare(calendar.__panel.pressedCellIndex, 1); + compare(pressedSignalSpy.count, 1); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + pressedSignalSpy.clear(); + releasedSignalSpy.clear(); + clickedSignalSpy.clear(); + + mouseRelease(calendar, toPixelsX(1), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, new Date(2012, 11, 31)); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 1); + compare(clickedSignalSpy.count, 1); + } + + function test_selectInvalidDateWithMouse() { + var startDate = new Date(2013, 0, 1); + calendar.minimumDate = new Date(2013, 0, 1); + calendar.selectedDate = new Date(startDate); + calendar.maximumDate = new Date(2013, 1, 5); + calendar.__locale = Qt.locale("no_NO"); + + pressedSignalSpy.target = calendar; + pressedSignalSpy.signalName = "pressed"; + + clickedSignalSpy.target = calendar; + clickedSignalSpy.signalName = "clicked"; + + releasedSignalSpy.target = calendar; + releasedSignalSpy.signalName = "released"; + + /* January 2013 + M T W T F S S + [31] 1 2 3 4 5 6 + 7 8 9 10 11 12 13 + 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 + 28 29 30 31 1 2 3 + 4 5 6 7 8 9 10 */ + mousePress(calendar, toPixelsX(0), toPixelsY(0), Qt.LeftButton); + compare(calendar.__panel.pressedCellIndex, 0); + compare(calendar.selectedDate, startDate); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + mouseRelease(calendar, toPixelsX(0), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, startDate); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + /* January 2013 December 2012 + M T W T F S S M T W T F S S + 31 1 2 3 4 5 6 31 1 2 3 4 5 6 + 7 8 9 10 11 12 13 7 8 9 10 11 12 13 + 14 15 16 17 18 19 20 through 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 to 21 22 23 24 25 26 27 + 28 29 30 31 1 2 3 28 29 30 31 1 2 3 + 4 5 [6] 7 8 9 10 4 5 6 7 8 9 [10] */ + for (var x = 2; x < 7; ++x) { + mousePress(calendar, toPixelsX(x), toPixelsY(5), Qt.LeftButton); + compare(calendar.selectedDate, startDate); + compare(calendar.__panel.pressedCellIndex, 35 + x); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + mouseRelease(calendar, toPixelsX(x), toPixelsY(5), Qt.LeftButton); + compare(calendar.selectedDate, startDate); + compare(calendar.__panel.pressedCellIndex, -1); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + } + } + + function test_daysCenteredVertically() { + /* + When the first day of the visible month is the first cell in + the calendar, we want to push it onto the next row to ensure + there is some balance between the days shown in the previous + and next months. + + December 2014 + M T W T F S S + 24 25 26 27 28 29 30 + 1 2 3 4 5 6 7 + 8 9 10 11 12 13 14 + 15 16 17 18 19 20 21 + 22 23 24 25 26 27 28 + 29 30 31 1 2 3 4 */ + + calendar.__locale = Qt.locale("en_GB"); + calendar.selectedDate = new Date(2014, 11, 1); + mousePress(calendar, toPixelsX(0), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, new Date(2014, 10, 24)); + mouseRelease(calendar, toPixelsX(0), toPixelsY(0), Qt.LeftButton); + } + + function test_hovered() { + // Moving the mouse within the calendar once seems to be necessary for it + // to receive subsequent move events. + mouseMove(calendar, calendar.width, calendar.height, 100); + + hoveredSignalSpy.target = calendar; + hoveredSignalSpy.signalName = "hovered"; + + var selectedDate = calendar.selectedDate; + var visibleMonth = calendar.visibleMonth; + var visibleYear = calendar.visibleYear; + for (var row = 0; row < CalendarUtils.weeksOnACalendarMonth; ++row) { + for (var col = 0; col < CalendarUtils.daysInAWeek; ++col) { + mouseMove(calendar, toPixelsX(col), toPixelsY(row)); + + compare(hoveredSignalSpy.count, 1); + compare(calendar.__panel.hoveredCellIndex, row * CalendarUtils.daysInAWeek + col); + compare(calendar.selectedDate, selectedDate); + compare(calendar.visibleMonth, visibleMonth); + compare(calendar.visibleYear, visibleYear); + + hoveredSignalSpy.clear(); + } + } + + // Moving the mouse around over the same cell should only emit one hovered signal. + mouseMove(calendar, toPixelsX(0), toPixelsY(0)); + compare(hoveredSignalSpy.count, 1); + + mouseMove(calendar, toPixelsX(0) + 1, toPixelsY(0) + 1); + compare(hoveredSignalSpy.count, 1); + + hoveredSignalSpy.clear(); + + // Moving the mouse outside the control should unset any hovered data. + mouseMove(calendar, -1, -1); + compare(calendar.__panel.hoveredCellIndex, -1); + compare(hoveredSignalSpy.count, 0); + + // Moving it back in should set the hovered data. + mouseMove(calendar, toPixelsX(0), toPixelsY(0)); + compare(hoveredSignalSpy.count, 1); + compare(calendar.__panel.hoveredCellIndex, 0); + } + + function dragTo(cellX, cellY, expectedCellIndex, expectedDate) { + mouseMove(calendar, toPixelsX(cellX), toPixelsY(cellY), Qt.LeftButton); + compare(calendar.selectedDate, expectedDate); + compare(calendar.__panel.pressedCellIndex, expectedCellIndex); + compare(hoveredSignalSpy.count, 1); + compare(pressedSignalSpy.count, 1); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + hoveredSignalSpy.clear(); + pressedSignalSpy.clear(); + releasedSignalSpy.clear(); + clickedSignalSpy.clear(); + } + + function test_dragWhileMousePressed() { + calendar.minimumDate = new Date(2014, 1, 1); + calendar.selectedDate = new Date(2014, 1, 28); + calendar.maximumDate = new Date(2014, 2, 31); + calendar.__locale = Qt.locale("en_GB"); + + hoveredSignalSpy.target = calendar; + hoveredSignalSpy.signalName = "hovered"; + + pressedSignalSpy.target = calendar; + pressedSignalSpy.signalName = "pressed"; + + clickedSignalSpy.target = calendar; + clickedSignalSpy.signalName = "clicked"; + + releasedSignalSpy.target = calendar; + releasedSignalSpy.signalName = "released"; + + /* + This test drags across each row, alternating from left to right + when an edge is reached. + + February 2014 February 2014 + M T W T F S S M T W T F S S + 27 28 29 30 31 [1] 2 27 28 29 30 31 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 through 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 to 17 18 19 20 21 22 23 + 24 25 26 27 28 1 2 24 25 26 27 [28] 1 2 + 3 4 5 6 7 8 9 3 4 5 6 7 8 9 */ + + mousePress(calendar, toPixelsX(5), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, new Date(2014, 1, 1)); + compare(calendar.__panel.pressedCellIndex, 5); + compare(hoveredSignalSpy.count, 1); + compare(pressedSignalSpy.count, 1); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + + hoveredSignalSpy.clear(); + pressedSignalSpy.clear(); + releasedSignalSpy.clear(); + clickedSignalSpy.clear(); + + // The first row just has one drag. + dragTo(6, 0, 6, new Date(2014, 1, 2)); + + // Second row, right to left. + for (var x = 6, index = 13; index >= 7; --x, --index) + dragTo(x, Math.floor(index / CalendarUtils.daysInAWeek), index, new Date(2014, 1, 9 - (6 - x))); + + // Third row, left to right. + for (x = 0, index = 14; index <= 20; ++x, ++index) + dragTo(x, Math.floor(index / CalendarUtils.daysInAWeek), index, new Date(2014, 1, 10 + x)); + + // Fourth row, right to left. + for (x = 6, index = 27; index >= 21; --x, --index) { + dragTo(x, Math.floor(index / CalendarUtils.daysInAWeek), index, new Date(2014, 1, 23 - (6 - x))); + } + + // Fifth row, left to right. Stop at the last day of the month. + for (x = 0, index = 28; index <= 32; ++x, ++index) { + dragTo(x, Math.floor(index / CalendarUtils.daysInAWeek), index, new Date(2014, 1, 24 + x)); + } + + // Dragging into the next month should work. + var firstDateInNextMonth = new Date(2014, 2, 1); + dragTo(5, 4, 33, firstDateInNextMonth); + + // Finish the drag. + mouseRelease(calendar, toPixelsX(5), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, firstDateInNextMonth); + compare(calendar.__panel.pressedCellIndex, -1); + compare(hoveredSignalSpy.count, 0); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 1); + compare(clickedSignalSpy.count, 1); + + hoveredSignalSpy.clear(); + pressedSignalSpy.clear(); + releasedSignalSpy.clear(); + clickedSignalSpy.clear(); + + // Now try dragging into an invalid date. + calendar.selectedDate = calendar.minimumDate; + compare(calendar.visibleMonth, calendar.minimumDate.getMonth()); + + mouseMove(calendar, toPixelsX(4), toPixelsY(0), Qt.LeftButton); + compare(calendar.selectedDate, calendar.minimumDate); + compare(calendar.__panel.pressedCellIndex, -1); + compare(hoveredSignalSpy.count, 0); + compare(pressedSignalSpy.count, 0); + compare(releasedSignalSpy.count, 0); + compare(clickedSignalSpy.count, 0); + } + + function ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight) { + for (var row = 1; row < rows; ++row) { + for (var col = 1; col < columns; ++col) { + var lastHorizontalRect = CalendarUtils.cellRectAt((row - 1) * columns + col - 1, columns, rows, availableWidth, availableHeight); + var thisHorizontalRect = CalendarUtils.cellRectAt((row - 1) * columns + col, columns, rows, availableWidth, availableHeight); + compare (lastHorizontalRect.x + lastHorizontalRect.width, thisHorizontalRect.x, + "Gaps between column " + (col - 1) + " and " + col + " in a grid of " + columns + " columns and " + rows + " rows, " + + "with an availableWidth of " + availableWidth + " and availableHeight of " + availableHeight); + + var lastVerticalRect = CalendarUtils.cellRectAt((row - 1) * columns + col - 1, columns, rows, availableWidth, availableHeight); + var thisVerticalRect = CalendarUtils.cellRectAt(row * columns + col - 1, columns, rows, availableWidth, availableHeight); + compare (lastVerticalRect.y + lastVerticalRect.height, thisVerticalRect.y, + "Gaps between row " + (row - 1) + " and " + row + " in a grid of " + columns + " columns and " + rows + " rows, " + + "with an availableWidth of " + availableWidth + " and availableHeight of " + availableHeight); + } + } + } + + function test_cellRectCalculation() { + var columns = CalendarUtils.daysInAWeek; + var rows = CalendarUtils.weeksOnACalendarMonth; + + // No extra space available. + var availableWidth = 10 * columns; + var availableHeight = 10 * rows; + var rect = CalendarUtils.cellRectAt(0, columns, rows, availableWidth, availableHeight); + compare(rect.x, 0); + compare(rect.y, 0); + compare(rect.width, 10); + compare(rect.height, 10); + + rect = CalendarUtils.cellRectAt(columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10); + compare(rect.y, 0); + compare(rect.width, 10); + compare(rect.height, 10); + + rect = CalendarUtils.cellRectAt(rows * columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10); + compare(rect.y, (rows - 1) * 10); + compare(rect.width, 10); + compare(rect.height, 10); + + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + + // 1 extra pixel of space in both width and height. + availableWidth = 10 * columns + 1; + availableHeight = 10 * rows + 1; + rect = CalendarUtils.cellRectAt(0, columns, rows, availableWidth, availableHeight); + compare(rect.x, 0); + compare(rect.y, 0); + compare(rect.width, 10 + 1); + compare(rect.height, 10 + 1); + + rect = CalendarUtils.cellRectAt(columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10 + 1); + compare(rect.y, 0); + compare(rect.width, 10); + compare(rect.height, 10 + 1); + + rect = CalendarUtils.cellRectAt(rows * columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10 + 1); + compare(rect.y, (rows - 1) * 10 + 1); + compare(rect.width, 10); + compare(rect.height, 10); + + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + + // 6 extra pixels in width, 5 in height. + availableWidth = 10 * columns + 6; + availableHeight = 10 * rows + 5; + rect = CalendarUtils.cellRectAt(0, columns, rows, availableWidth, availableHeight); + compare(rect.x, 0); + compare(rect.y, 0); + compare(rect.width, 10 + 1); + compare(rect.height, 10 + 1); + + rect = CalendarUtils.cellRectAt(columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10 + 6); + compare(rect.y, 0); + compare(rect.width, 10); + compare(rect.height, 10 + 1); + + rect = CalendarUtils.cellRectAt(rows * columns - 1, columns, rows, availableWidth, availableHeight); + compare(rect.x, (columns - 1) * 10 + 6); + compare(rect.y, (rows - 1) * 10 + 5); + compare(rect.width, 10); + compare(rect.height, 10); + + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + + availableWidth = 280; + availableHeight = 190; + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + + for (var i = 0; i < columns; ++i) { + ++availableWidth; + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + } + + for (i = 0; i < columns; ++i) { + ++availableHeight; + ensureNoGapsBetweenCells(columns, rows, availableWidth, availableHeight); + } + } + + function test_cellIndexCalculation() { + var columns = CalendarUtils.daysInAWeek; + var rows = CalendarUtils.weeksOnACalendarMonth; + + var availableWidth = 10 * columns; + var availableHeight = 10 * rows; + var rect = CalendarUtils.cellRectAt(0, columns, rows, availableWidth, availableHeight); + + for (var row = 0; row < rows; ++row) { + for (var col = 0; col < columns; ++col) { + // Test against the center of each cell. + compare(CalendarUtils.cellIndexAt(col * 10 + 5, row * 10 + 5, columns, rows, availableWidth, availableHeight), row * columns + col); + } + } + } + } +} diff --git a/tests/auto/controls/data/tst_rangeddate.qml b/tests/auto/controls/data/tst_rangeddate.qml new file mode 100644 index 00000000..ed58a685 --- /dev/null +++ b/tests/auto/controls/data/tst_rangeddate.qml @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls.Private 1.0 +import QtTest 1.0 + +Item { + id: container + + TestCase { + id: testcase + name: "Tests_RangedDate" + when: true + + property var rangedDate + readonly property string importsStr: "import QtQuick.Controls 1.1; import QtQuick.Controls.Private 1.0; " + + function init() { + rangedDate = Qt.createQmlObject(importsStr + " RangedDate {}", container, ""); + } + + function test_defaultConstruction() { + // rangedDate.date should be the current date. + compare(rangedDate.date.getTime(), new Date(Date.now()).setHours(0, 0, 0, 0)); + } + + function test_minMax() { + skip("QTBUG-36846"); + + rangedDate.minimumDate = CalendarUtils.minimumCalendarDate; + rangedDate.maximumDate = CalendarUtils.maximumCalendarDate; + + compare(rangedDate.minimumDate.getTime(), CalendarUtils.minimumCalendarDate.getTime()); + compare(rangedDate.maximumDate.getTime(), CalendarUtils.maximumCalendarDate.getTime()); + } + + function test_constructionPropertyOrder() { + // All values are valid; fine. + rangedDate = Qt.createQmlObject(importsStr + " RangedDate { " + + "date: new Date(1900, 0, 2); " + + "minimumDate: new Date(1900, 0, 1); " + + "maximumDate: new Date(1900, 0, 3); " + + " }", container, ""); + compare(rangedDate.date.getTime(), new Date(1900, 0, 2).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.maximumDate.getTime(), new Date(1900, 0, 3).getTime()); + + // All values are the same; doesn't make sense, but is fine [1]. + rangedDate = Qt.createQmlObject(importsStr + " RangedDate { " + + "date: new Date(1900, 0, 1);" + + "minimumDate: new Date(1900, 0, 1);" + + "maximumDate: new Date(1900, 0, 1);" + + " }", container, ""); + compare(rangedDate.date.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.maximumDate.getTime(), new Date(1900, 0, 1).getTime()); + + // date is lower than min - should be clamped to min. + rangedDate = Qt.createQmlObject(importsStr + " RangedDate { " + + "date: new Date(1899, 0, 1);" + + "minimumDate: new Date(1900, 0, 1);" + + "maximumDate: new Date(1900, 0, 1);" + + " }", container, ""); + compare(rangedDate.date.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.maximumDate.getTime(), new Date(1900, 0, 1).getTime()); + + // date is higher than max - should be clamped to max. + rangedDate = Qt.createQmlObject(importsStr + " RangedDate { " + + "date: new Date(1900, 0, 2);" + + "minimumDate: new Date(1900, 0, 1);" + + "maximumDate: new Date(1900, 0, 1);" + + " }", container, ""); + compare(rangedDate.date.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1900, 0, 1).getTime()); + compare(rangedDate.maximumDate.getTime(), new Date(1900, 0, 1).getTime()); + + // If the order of property construction is undefined (as it should be if it's declarative), + // then is min considered higher than max or max lower than min? Which should be changed? + + // For now, max will always be the one that's changed. It will be set to min, + // as min may already be the largest possible date (See [1]). + rangedDate = Qt.createQmlObject(importsStr + " RangedDate { " + + "date: new Date(1900, 0, 1);" + + "minimumDate: new Date(1900, 0, 2);" + + "maximumDate: new Date(1900, 0, 1);" + + " }", container, ""); + compare(rangedDate.date.getTime(), new Date(1900, 0, 2).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1900, 0, 2).getTime()); + compare(rangedDate.maximumDate.getTime(), new Date(1900, 0, 2).getTime()); + + // [1] Do we want to enforce min and max being different? E.g. if min + // is (1900, 0, 1) and max is (1900, 0, 1), max should be set (1900, 0, 2). + // Another e.g.: if min is the largest possible date and max is the same, + // min should be set to the previous day, and vice versa. + // Currently, I'm assuming that it's fine for them to be the same, + // even if it means date can only ever be one value. + } + + // "Flickering" is the effect that changing the min/max has on the date; + // setting the minimum to be higher than the date will force the date to + // be changed, and the same applies to the maximum. + function test_flickering() { + // MIN DATE MAX + // [ 1990 | 1995 | 1999 ] + rangedDate.date = new Date(1995, 0, 1); + compare(rangedDate.date.getTime(), new Date(1995, 0, 1).getTime()); + rangedDate.minimumDate = new Date(1990, 0, 1); + compare(rangedDate.minimumDate.getTime(), new Date(1990, 0, 1).getTime()); + rangedDate.maximumDate = new Date(1999, 0, 1); + compare(rangedDate.maximumDate.getTime(), new Date(1999, 0, 1).getTime()); + + // MIN DATE MAX + // [ 1996 | 1996 | 1999 ] + rangedDate.minimumDate = new Date(1996, 0, 1); + compare(rangedDate.date.getTime(), new Date(1996, 0, 1).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1996, 0, 1).getTime()); + + // MIN DATE MAX + // [ 1990 | 1996 | 1999 ] + rangedDate.minimumDate = new Date(1990, 0, 1); + compare(rangedDate.date.getTime(), new Date(1996, 0, 1).getTime()); + compare(rangedDate.minimumDate.getTime(), new Date(1990, 0, 1).getTime()); + } + + function test_nullValues() { + var expected = new Date(rangedDate.date); + rangedDate.date = undefined; + compare(rangedDate.date.getTime(), expected.getTime()); + + expected = new Date(rangedDate.minimumDate); + rangedDate.minimumDate = undefined; + compare(rangedDate.minimumDate.getTime(), expected.getTime()); + + expected = new Date(rangedDate.maximumDate); + rangedDate.maximumDate = undefined; + compare(rangedDate.maximumDate.getTime(), expected.getTime()); + } + } +} |