summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@digia.com>2013-07-15 22:57:52 +0200
committerMitch Curtis <mitch.curtis@digia.com>2013-12-06 13:07:41 +0100
commit8cfe8d2104baa9224586b5f087cb2f12ec74eea3 (patch)
tree733066323d2368e02847ca924afc069b53669e5c
parent4746541dcfebb73ea8398e60e046e394ea9fa441 (diff)
downloadqtquickcontrols-8cfe8d2104baa9224586b5f087cb2f12ec74eea3.tar.gz
Add Calendar and supporting components.
Adds: Calendar, CalendarModel, CalendarHeaderModel, CalendarStyle, RangedDate, DateUtils and relevant tests. Task-number: QTBUG-29948 Change-Id: Ibdd3923c514908cf8707f8572748e43a6eadbdfc Reviewed-by: Mitch Curtis <mitch.curtis@digia.com>
-rw-r--r--src/controls/Calendar.qml369
-rw-r--r--src/controls/CalendarHeaderModel.qml92
-rw-r--r--src/controls/CalendarModel.qml117
-rw-r--r--src/controls/Private/DateUtils.js153
-rw-r--r--src/controls/Private/private.pri3
-rw-r--r--src/controls/Private/qmldir1
-rw-r--r--src/controls/Private/qquickrangeddate.cpp115
-rw-r--r--src/controls/Private/qquickrangeddate_p.h96
-rw-r--r--src/controls/Styles/Base/CalendarStyle.qml283
-rw-r--r--src/controls/Styles/qmldir1
-rw-r--r--src/controls/Styles/styles.pri1
-rw-r--r--src/controls/controls.pro3
-rw-r--r--src/controls/plugin.cpp6
-rw-r--r--tests/auto/controls/controls.pro2
-rw-r--r--tests/auto/controls/data/tst_calendar.qml364
-rw-r--r--tests/auto/controls/data/tst_rangeddate.qml176
16 files changed, 1782 insertions, 0 deletions
diff --git a/src/controls/Calendar.qml b/src/controls/Calendar.qml
new file mode 100644
index 00000000..779b8b8e
--- /dev/null
+++ b/src/controls/Calendar.qml
@@ -0,0 +1,369 @@
+/****************************************************************************
+**
+** 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: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.0
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Controls.Private 1.0
+
+//import "Private/DateUtils.js" as DateUtils
+
+/*!
+ \qmltype Calendar
+ \inqmlmodule QtQuick.Controls
+ \since QtQuick.Controls 1.1
+ \ingroup controls
+ \brief Provides a way to select dates from a calendar
+
+ Calendar allows selection of dates from a grid of days, similar to a typical
+ calendar. The selected date can be set through \l selectedDate, or with the
+ mouse and directional arrow keys. The current month displayed can be changed
+ by clicking the previous and next month buttons, or pressing the left/right
+ arrow keys when the selected date is the first or last visible date,
+ respectively.
+
+ 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.
+
+ Localization is supported through the \l locale property. The selected date
+ is displayed according to \l locale, and it can be accessed through the
+ \l selectedDateText property.
+
+ \sa CalendarModel, CalendarHeaderModel
+*/
+
+Control {
+ id: calendar
+
+ /*!
+ \qmlproperty date Calendar::date
+
+ The date that can be set 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 If selectedDate is equal to \c undefined or some other invalid
+ value, it will not be changed.
+
+ \li If there are hours, minutes, seconds or milliseconds set, they
+ will be removed.
+ \endlist
+
+ \sa isValidDate()
+ */
+ 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
+
+ RangedDate {
+ id: rangedDate
+ date: new Date()
+ minimumDate: DateUtils.minimumCalendarDate
+ maximumDate: DateUtils.maximumCalendarDate
+ }
+
+ /*!
+ This property determines the visibility of the navigation bar.
+
+ The navigation bar contains the previous and next month buttons, as well
+ as the displayed date.
+
+ The default value is \c true.
+ */
+ property bool navigationBarVisible: true
+
+ /*!
+ \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
+ qml-qtquick2-locale.html#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 localised, as well as which
+ day is considered the first in a week.
+
+ The default locale is \c Qt.locale().
+ */
+ property var locale: Qt.locale()
+
+ /*!
+ \qmlproperty enum Calendar::selectedDateFormat
+ \qmlproperty string Calendar::selectedDateFormat
+
+ The format to display \l selectedDateText in.
+
+ This can be either one of the following enums, or a string:
+
+ \list
+ \li Locale.ShortFormat
+ \li Locale.LongFormat
+ \li Locale.NarrowFormat
+ \endlist
+
+ See QLocale::toString()/QLocale::toDateTime() for the full list of
+ formatting options.
+
+ The default value is \c "MMMM yyyy".
+ */
+ property var selectedDateFormat: "MMMM yyyy"
+
+ /*!
+ The selected date converted to a string using \l locale.
+ */
+ readonly property string selectedDateText: selectedDate.toLocaleDateString(locale, selectedDateFormat)
+
+ /*!
+ This property holds the CalendarModel that will be used by the Calendar
+ to populate the dates available to the user.
+ */
+ property CalendarModel model: CalendarModel { locale: calendar.locale }
+
+ /*!
+ \qmlsignal Calendar::doubleClicked(date selectedDate)
+
+ This signal is emitted when a date within the current month displayed
+ by the calendar is double clicked. For example, dates outside the valid
+ range do not emit this signal when clicked. Dates belonging to the
+ previous or next month can not be double clicked.
+
+ The argument is the \a date that was double clicked.
+ */
+ signal doubleClicked(date selectedDate)
+
+ /*!
+ \qmlsignal Calendar::escapePressed()
+
+ This signal is emitted when escape is pressed while the view has focus.
+ When Calendar is used as a popup, this signal can be handled to close
+ the calendar.
+ */
+ signal escapePressed
+
+ style: Qt.createComponent(/*Settings.style*/"Styles/Base" + "/CalendarStyle.qml", calendar)
+
+ Keys.forwardTo: [view]
+
+ /*!
+ Returns true if \a date is not \c undefined and not less than
+ \l minimumDate nor greater than \l maximumDate.
+ */
+ function isValidDate(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.
+ return date !== undefined && date.getTime() >= calendar.minimumDate.getTime()
+ && date.getTime() <= calendar.maximumDate.getTime();
+ }
+
+ /*!
+ Selects the month before the current month in \l selectedDate.
+ */
+ function previousMonth() {
+ calendar.selectedDate = DateUtils.setMonth(calendar.selectedDate, calendar.selectedDate.getMonth() - 1);
+ }
+
+ /*!
+ Selects the month after the current month in \l selectedDate.
+ */
+ function nextMonth() {
+ calendar.selectedDate = DateUtils.setMonth(calendar.selectedDate, calendar.selectedDate.getMonth() + 1);
+ }
+
+ GridView {
+ id: view
+ cellWidth: __style.cellWidth
+ cellHeight: __style.cellHeight
+ currentIndex: -1
+ anchors.left: parent.left
+ anchors.right: parent.right
+ y: __panel.navigationBarItem.y + __panel.navigationBarItem.height
+ width: cellWidth * DateUtils.daysInAWeek
+ // TODO: fix the reason behind + 1 stopping the flickableness..
+ // might have something to do with the header
+ height: cellHeight * (__style.weeksToShow + 1)
+ model: calendar.model
+ boundsBehavior: Flickable.StopAtBounds
+ KeyNavigation.tab: __panel.navigationBarItem
+
+ property var previousDate
+
+ Keys.onLeftPressed: {
+ if (currentIndex != 0) {
+ // Be lazy and let the view determine which index we're moving
+ // to, then we can calculate the date from that.
+ moveCurrentIndexLeft();
+ // This will cause the index to be set again (to the same value).
+ calendar.selectedDate = model.get(currentIndex).date;
+ } else {
+ // We're at the left edge of the calendar on the first row;
+ // this day is the first of the week and the month, so
+ // moving left should go to the last day of the previous month,
+ // rather than do nothing (which is what GridView does when
+ // keyNavigationWraps is false).
+ var newDate = new Date(calendar.selectedDate);
+ newDate.setDate(newDate.getDate() - 1);
+ calendar.selectedDate = newDate;
+ }
+ }
+
+ Keys.onUpPressed: {
+ moveCurrentIndexUp();
+ calendar.selectedDate = model.get(currentIndex).date;
+ }
+
+ Keys.onDownPressed: {
+ moveCurrentIndexDown();
+ calendar.selectedDate = model.get(currentIndex).date;
+ }
+
+ Keys.onRightPressed: {
+ moveCurrentIndexRight();
+ calendar.selectedDate = model.get(currentIndex).date;
+ }
+
+ Keys.onEscapePressed: {
+ calendar.escapePressed();
+ }
+
+ Component.onCompleted: {
+ repopulate();
+ dateChanged();
+
+ if (visible) {
+ forceActiveFocus();
+ }
+ }
+
+ Connections {
+ target: calendar
+ onSelectedDateChanged: view.dateChanged()
+ }
+
+ function dateChanged() {
+ if (model !== undefined && model.locale !== undefined) {
+ if (previousDate === undefined ||
+ previousDate !== undefined &&
+ previousDate.getMonth() != calendar.selectedDate.getMonth()) {
+ repopulate();
+ }
+ previousDate = new Date(calendar.selectedDate);
+ currentIndex = model.indexFromDate(calendar.selectedDate);
+ }
+ }
+
+ function repopulate() {
+ model.populateFromDate(calendar.selectedDate);
+ }
+
+ delegate: Loader {
+ sourceComponent: __style.dateDelegate
+
+ property date cellDate: date
+ readonly property bool isCurrentItem: GridView.isCurrentItem
+
+ width: view.cellWidth
+ height: view.cellHeight
+
+ MouseArea {
+ anchors.fill: parent
+
+ function setDateIfValid(date) {
+ if (calendar.isValidDate(date)) {
+ calendar.selectedDate = date;
+ }
+ }
+
+ onClicked: {
+ setDateIfValid(cellDate)
+ }
+
+ onDoubleClicked: {
+ if (cellDate.getTime() === calendar.selectedDate.getTime()) {
+ // Only accept double clicks if the first click does not
+ // change the month displayed. This is because double-
+ // clicking on a date in the next month will first cause
+ // a single click which will change the month and the
+ // the release will be triggered on the same index but a
+ // different date (the date in the next month).
+ calendar.doubleClicked(cellDate);
+ }
+ }
+ }
+ }
+
+ header: Loader {
+ width: view.width
+ height: view.cellHeight
+
+ sourceComponent: __style.headerDelegate
+ }
+ }
+}
diff --git a/src/controls/CalendarHeaderModel.qml b/src/controls/CalendarHeaderModel.qml
new file mode 100644
index 00000000..db8ae53d
--- /dev/null
+++ b/src/controls/CalendarHeaderModel.qml
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** 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: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.0
+
+/*!
+ \qmltype CalendarHeaderModel
+ \inqmlmodule QtQuick.Controls
+ \since QtQuick.Controls 1.1
+ \ingroup controls
+ \brief A model for days of the week
+
+ The CalendarHeaderModel model 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
+
+ \sa Calendar, CalendarModel
+ */
+
+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: Qt.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/CalendarModel.qml b/src/controls/CalendarModel.qml
new file mode 100644
index 00000000..a7fca579
--- /dev/null
+++ b/src/controls/CalendarModel.qml
@@ -0,0 +1,117 @@
+/****************************************************************************
+**
+** 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: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.0
+import QtQuick.Controls.Private 1.0
+
+/*!
+ \qmltype CalendarModel
+ \inqmlmodule QtQuick.Controls
+ \since QtQuick.Controls 1.1
+ \ingroup controls
+ \brief A model that represents days in and surrounding a certain month
+
+ The CalendarModel model provides a list of the days in and surrounding the
+ month in which a given date lies.
+
+ The only role provided by the model is \a date, a JavaScript \l Date object.
+
+ \sa Calendar, CalendarHeaderModel
+ */
+
+ListModel {
+ id: root
+
+ /*!
+ The month that the calendar is currently displaying -
+ regardless of whether the selected date is in the previous or
+ next month.
+
+ The month is zero-based, as it refers to the calendar's
+ \l {Calendar::selectedDate}{selectedDate}, which is a JavaScript
+ \l Date object.
+ */
+ property int month: 0
+
+ /*!
+ The first date that is visible on the calendar.
+ */
+ property date firstVisibleDate
+ /*!
+ The last date that is visible on the calendar.
+ */
+ property date lastVisibleDate
+
+ /*!
+ The locale that this model should be based on.
+ This affects, for example, the first day of the week, and consequently
+ the order in which dates are populated in the model.
+ */
+ property var locale: Qt.locale()
+
+ /*!
+ Populates the calendar with days from the month in \a date.
+ */
+ function populateFromDate(date) {
+ month = date.getMonth();
+
+ DateUtils.populateDatesOnACalendarMonth(root, date);
+
+ firstVisibleDate = root.get(0).date;
+ lastVisibleDate = root.get(root.count - 1).date;
+ }
+
+ /*!
+ Returns the index of \a date into the view, or \c -1 if \a date is
+ outside the range of visible dates.
+ */
+ function indexFromDate(date) {
+ if (count == 0 || date.getTime() < firstVisibleDate.getTime()
+ || date.getTime() > lastVisibleDate.getTime()) {
+ return -1;
+ }
+
+ // The index of the selected date will be the days from the
+ // previous month that we had to display before it, plus the
+ // day of the selected date itself.
+ var daysDifference = (date.getTime() - firstVisibleDate.getTime()) / DateUtils.msPerDay;
+ return Math.abs(daysDifference);
+ }
+}
diff --git a/src/controls/Private/DateUtils.js b/src/controls/Private/DateUtils.js
new file mode 100644
index 00000000..d482cea6
--- /dev/null
+++ b/src/controls/Private/DateUtils.js
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** 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: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;
+
+// Not the number of weeks per month, but the number of weeks that are
+// shown on a typical calendar.
+var weeksOnACalendarMonth = 6;
+
+/*!
+ The amount of days to populate the calendar with.
+*/
+var daysOnACalendarMonth = daysInAWeek * weeksOnACalendarMonth
+
+var msPerDay = 86400000;
+
+// 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();
+}
+
+function dayNameFromDayOfWeek(locale, dayOfWeekFormat, dayOfWeek) {
+ return locale.dayName(dayOfWeek, dayOfWeekFormat);
+}
+
+/*!
+ Clears \a model, then populates it with dates (role: "date")
+ that you'd see on a calendar for the given \a date.
+
+ \a model - A ListModel. Should have the following property:
+ locale - A JavaScript Locale object.
+ \a date - The date that the calendar month is based on (only the year
+ and month are relevant)
+*/
+function populateDatesOnACalendarMonth(model, date) {
+ if (model.count < daysOnACalendarMonth) {
+ var dummyDate = new Date(1970, 0, 1, 0, 0, 0, 0);
+ while (model.count < daysOnACalendarMonth) {
+ // We only want to fill the model once, to avoid having to actually
+ // repopulate it. Instead, we just set properties.
+ model.append({ date: dummyDate });
+ }
+ }
+
+ // Ideally we'd display the 1st of the month as the first
+ // day in the calendar, but typically it's not the first day of
+ // the week, so we need to display some days before it.
+
+ // The actual first (1st) day of the month.
+ // Avoid issue where years like "1" can't be created using (years, months, days)
+ // constructor by just using the milliseconds constructor.
+ var firstDayOfMonthDate = new Date(date);
+ firstDayOfMonthDate.setDate(1);
+ // The first day to display, if not the 1st of the month, will be
+ // before the first day of the month.
+ var difference = Math.abs(firstDayOfMonthDate.getDay() - model.locale.firstDayOfWeek);
+ // The first day before the 1st that is equal to this locale's firstDayOfWeek.
+ var firstDateToDisplay = new Date(firstDayOfMonthDate);
+
+ if (difference != 0) {
+ firstDateToDisplay.setDate(firstDateToDisplay.getDate() - difference);
+
+ for (var i = 1; i <= difference; ++i) {
+ var earlierDate = new Date(firstDayOfMonthDate);
+ earlierDate.setDate(earlierDate.getDate() - i);
+
+ // Reverse through it since we're iterating back through time.
+ model.set(difference - i, { date: earlierDate });
+ }
+ }
+ // Else, the first day of the month is also the first day of the week;
+ // it can be the first day in the calendar.
+
+ i = difference;
+ for (var d = firstDayOfMonthDate.getDate(); d <= daysInMonth(date); ++d, ++i) {
+ var tmpDate = new Date(date);
+ tmpDate.setDate(d);
+ model.set(i, { date: tmpDate });
+ }
+
+ // Fill up the calendar with days from the next month.
+ var firstDayOfNextMonth = new Date(firstDayOfMonthDate);
+ firstDayOfNextMonth.setMonth(firstDayOfNextMonth.getMonth() + 1);
+ var daysToFill = daysOnACalendarMonth - i;
+ for (var offset = 0; offset < daysToFill; ++offset) {
+ var nextMonthDate = new Date(firstDayOfNextMonth);
+ nextMonthDate.setDate(nextMonthDate.getDate() + offset);
+
+ model.set(i + offset, { date: nextMonthDate });
+ }
+}
+
+/*!
+ 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;
+}
diff --git a/src/controls/Private/private.pri b/src/controls/Private/private.pri
index 30464974..c717febf 100644
--- a/src/controls/Private/private.pri
+++ b/src/controls/Private/private.pri
@@ -3,6 +3,7 @@ HEADERS += \
$$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 \
@@ -13,6 +14,7 @@ SOURCES += \
$$PWD/qquicktooltip.cpp \
$$PWD/qquickspinboxvalidator.cpp \
$$PWD/qquickrangemodel.cpp \
+ $$PWD/qquickrangeddate.cpp \
$$PWD/qquickcontrolsettings.cpp \
$$PWD/qquickwheelarea.cpp \
$$PWD/qquickabstractstyle.cpp
@@ -30,6 +32,7 @@ PRIVATE_QML_FILES += \
$$PWD/TabBar.qml \
$$PWD/BasicButton.qml \
$$PWD/Control.qml \
+ $$PWD/DateUtils.js \
$$PWD/FastGlow.qml \
$$PWD/SourceProxy.qml\
$$PWD/Style.qml \
diff --git a/src/controls/Private/qmldir b/src/controls/Private/qmldir
index 35138780..d2cf5f48 100644
--- a/src/controls/Private/qmldir
+++ b/src/controls/Private/qmldir
@@ -1,6 +1,7 @@
module QtQuick.Controls.Private
AbstractCheckable 1.0 AbstractCheckable.qml
Control 1.0 Control.qml
+DateUtils 1.0 DateUtils.js
FocusFrame 1.0 FocusFrame.qml
Margins 1.0 Margins.qml
BasicButton 1.0 BasicButton.qml
diff --git a/src/controls/Private/qquickrangeddate.cpp b/src/controls/Private/qquickrangeddate.cpp
new file mode 100644
index 00000000..18f5bd45
--- /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.
+const QDate QQuickRangedDate::jsMinimumDate = QDate(1, 1, 1);
+const QDate QQuickRangedDate::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..53771539
--- /dev/null
+++ b/src/controls/Private/qquickrangeddate_p.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+**
+** 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();
+
+protected:
+
+private:
+ QDate mDate;
+ QDate mMinimumDate;
+ QDate mMaximumDate;
+
+ static const QDate jsMinimumDate;
+ static const QDate jsMaximumDate;
+};
+
+QML_DECLARE_TYPE(QQuickRangedDate)
+
+QT_END_NAMESPACE
+
+#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..dae6c6ca
--- /dev/null
+++ b/src/controls/Styles/Base/CalendarStyle.qml
@@ -0,0 +1,283 @@
+/****************************************************************************
+**
+** 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: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.1
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Private 1.0
+
+/*!
+ \qmltype CalendarStyle
+ \inqmlmodule QtQuick.Controls.Styles
+ \since QtQuick.Controls.Styles 1.1
+ \ingroup controlsstyling
+ \brief Provides custom styling for \l Calendar
+
+ Example:
+ \qml
+ Calendar {
+ anchors.centerIn: parent
+
+ style: CalendarStyle {
+ dateDelegate: Rectangle {
+ readonly property bool thisMonth: cellDate.getMonth() === control.selectedDate.getMonth()
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0.00
+ color: isCurrentItem ? "#111" : (thisMonth ? "#444" : "#666");
+ }
+ GradientStop {
+ position: 1.00
+ color: isCurrentItem ? "#444" : (thisMonth ? "#111" : "#666");
+ }
+ GradientStop {
+ position: 1.00
+ color: isCurrentItem ? "#777" : (thisMonth ? "#111" : "#666");
+ }
+ }
+
+ Text {
+ text: cellDate.getDate()
+ anchors.centerIn: parent
+ color: "white"
+ }
+
+ 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 number of weeks to be shown.
+ */
+ readonly property int weeksToShow: 6
+
+ /*!
+ The height of the navigation bar.
+ */
+ readonly property real navigationBarHeight: 40
+
+ /*!
+ The width of each cell in the view.
+ */
+ readonly property real cellWidth: control.width % 2 == 0
+ ? control.width / DateUtils.daysInAWeek
+ : Math.floor(control.width / DateUtils.daysInAWeek)
+
+ /*!
+ The height of each cell in the view.
+ */
+ readonly property real cellHeight: {control.height - navigationBarHeight % 2 == 0
+ ? (parent.height - navigationBarHeight) / (weeksToShow + 1)
+ : Math.floor((control.height - navigationBarHeight) / (weeksToShow + 1))
+ }
+
+ /*!
+ The Calendar attached to this style.
+ */
+ property Calendar control: __control
+
+ /*!
+ The background of the calendar.
+
+ This component is typically not visible (that is, it is not able to be
+ seen; the \l {Item::visible}{visible} property is still \c true) if the
+ other components are fully opaque and consume as much space as possible.
+ */
+ 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 {
+ visible: control.navigationBarVisible
+ anchors.fill: parent
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#464646"
+ }
+
+ KeyNavigation.tab: previousMonth
+
+ 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.previousMonth()
+ }
+ Text {
+ id: dateText
+ text: control.selectedDateText
+ color: "#fff"
+
+ font.pixelSize: 12
+ anchors.centerIn: parent
+ }
+ 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"
+
+// KeyNavigation.tab: control.view
+
+ onClicked: control.nextMonth()
+ }
+ }
+
+ /*!
+ The delegate that styles each date in the calendar.
+
+ The properties provided by the view to each delegate are:
+ \list
+ \li property date cellDate
+ \li readonly property bool isCurrentItem
+ \endlist
+ */
+ property Component dateDelegate: Rectangle {
+ id: dayDelegate
+ color: cellDate !== undefined && isCurrentItem ? selectedDateColor : "white"
+// radius: 1
+ readonly property color sameMonthDateTextColor: "black"
+ readonly property color selectedDateColor: "steelblue"
+ readonly property color selectedDateTextColor: "white"
+ readonly property color differentMonthDateTextColor: Qt.darker("darkgrey", 1.4);
+ readonly property color invalidDatecolor: "#dddddd"
+
+ Text {
+ id: dayDelegateText
+ text: cellDate.getDate()
+ font.pixelSize: 14
+ anchors.centerIn: parent
+ color: {
+ var color = invalidDatecolor;
+ if (control.isValidDate(cellDate)) {
+ // Date is within the valid range.
+ color = cellDate.getMonth() === control.selectedDate.getMonth()
+ ? sameMonthDateTextColor : differentMonthDateTextColor;
+
+ if (GridView.isCurrentItem) {
+ color = selectedDateTextColor
+ }
+ }
+ color;
+ }
+ }
+ }
+
+ /*!
+ The delegate that styles the header of the calendar.
+ */
+ property Component headerDelegate: Row {
+ id: headerRow
+ Repeater {
+ id: repeater
+ model: CalendarHeaderModel { locale: control.locale }
+ Item {
+ width: calendarStyle.cellWidth
+ height: calendarStyle.cellHeight
+ Rectangle {
+ color: "white"
+ anchors.fill: parent
+ Text {
+ text: DateUtils.dayNameFromDayOfWeek(control.locale,
+ control.dayOfWeekFormat, dayOfWeek)
+ anchors.centerIn: parent
+ }
+ }
+ }
+ }
+ }
+
+ /*! \internal */
+ property Component panel: Item {
+ anchors.fill: parent
+ implicitWidth: 250
+ implicitHeight: 250
+
+ property alias navigationBarItem: navigationBarLoader.item
+
+ Loader {
+ id: backgroundLoader
+ anchors.fill: parent
+ sourceComponent: background
+ }
+
+ Loader {
+ id: navigationBarLoader
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ height: calendarStyle.navigationBarHeight
+ sourceComponent: navigationBar
+ }
+ }
+}
diff --git a/src/controls/Styles/qmldir b/src/controls/Styles/qmldir
index 5cd368ac..5d5cc0a3 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
ProgressBarStyle 1.0 Base/ProgressBarStyle.qml
diff --git a/src/controls/Styles/styles.pri b/src/controls/Styles/styles.pri
index d695893a..a062edc4 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 \
diff --git a/src/controls/controls.pro b/src/controls/controls.pro
index e31f8683..f772c321 100644
--- a/src/controls/controls.pro
+++ b/src/controls/controls.pro
@@ -10,6 +10,9 @@ CONTROLS_QML_FILES = \
ApplicationWindow.qml \
Button.qml \
BusyIndicator.qml \
+ Calendar.qml \
+ CalendarModel.qml \
+ CalendarHeaderModel.qml \
CheckBox.qml \
ComboBox.qml \
GroupBox.qml \
diff --git a/src/controls/plugin.cpp b/src/controls/plugin.cpp
index 2a676e48..2ca21220 100644
--- a/src/controls/plugin.cpp
+++ b/src/controls/plugin.cpp
@@ -48,6 +48,8 @@
#include "qquickstack_p.h"
#include "qquickdesktopiconprovider_p.h"
#include "qquickselectionmode_p.h"
+
+#include "Private/qquickrangeddate_p.h"
#include "Private/qquickrangemodel_p.h"
#include "Private/qquickwheelarea_p.h"
#include "Private/qquicktooltip_p.h"
@@ -69,6 +71,9 @@ static const struct {
} qmldir [] = {
{ "ApplicationWindow", 1, 0 },
{ "Button", 1, 0 },
+ { "Calendar", 1, 1 },
+ { "CalendarModel", 1, 1 },
+ { "CalendarHeaderModel", 1, 1 },
{ "CheckBox", 1, 0 },
{ "ComboBox", 1, 0 },
{ "GroupBox", 1, 0 },
@@ -127,6 +132,7 @@ void QtQuickControlsPlugin::initializeEngine(QQmlEngine *engine, const char *uri
const char *private_uri = "QtQuick.Controls.Private";
qmlRegisterType<QQuickAbstractStyle>(private_uri, 1, 0, "AbstractStyle");
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..6fab0038
--- /dev/null
+++ b/tests/auto/controls/data/tst_calendar.qml
@@ -0,0 +1,364 @@
+/****************************************************************************
+**
+** 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: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.0
+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 cellWidth: calendar !== undefined ? calendar.width / DateUtils.daysInAWeek : 0
+ readonly property int cellHeight: calendar !== undefined ? calendar.height / 7 - navigationBarHeight : 0
+ readonly property int navigationBarHeight: 40
+ readonly property int firstDateCellX: cellWidth / 2
+ readonly property int firstDateCellY: cellHeight / 2 + navigationBarHeight + cellHeight
+ 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: signalSpy
+ }
+
+ function init() {
+ calendar = Qt.createQmlObject("import QtQuick.Controls 1.1; " +
+ " Calendar { }", container, "");
+ calendar.width = 200;
+ calendar.height = 200;
+ }
+
+ function cleanup() {
+ signalSpy.clear();
+ }
+
+ function toPixelsX(cellPosX) {
+ return firstDateCellX + cellPosX * cellWidth;
+ }
+
+ function toPixelsY(cellPosY) {
+ return firstDateCellY + cellPosY * cellHeight;
+ }
+
+ 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.navigationBarVisible, true);
+ compare(calendar.dayOfWeekFormat, Locale.ShortFormat);
+ compare(calendar.locale, Qt.locale());
+ compare(calendar.selectedDateText,
+ calendar.selectedDate.toLocaleDateString(calendar.locale, calendar.selectedDateFormat));
+ }
+
+ 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.navigationBarVisible = 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.navigationBarVisible, false);
+ compare(calendar.locale, Qt.locale("de_DE"));
+ compare(calendar.selectedDateText,
+ calendar.selectedDate.toLocaleDateString(calendar.locale, calendar.selectedDateFormat))
+ }
+
+ 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() {
+ calendar.minimumDate = DateUtils.minimumCalendarDate;
+ calendar.maximumDate = DateUtils.maximumCalendarDate;
+
+ calendar.selectedDate = DateUtils.minimumCalendarDate;
+ compare(calendar.selectedDate, DateUtils.minimumCalendarDate);
+
+ calendar.selectedDate = DateUtils.maximumCalendarDate;
+ compare(calendar.selectedDate, DateUtils.maximumCalendarDate);
+ }
+
+ 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_localisation() {
+ calendar.selectedDate = new Date(2013, 0, 1);
+
+ var locales = [Qt.locale().name, "en_AU", "en_GB", "en_US", "de_DE", "nb_NO",
+ "pl_PL", "zh_CN", "fr_FR", "it_IT", "pt_BR", "ru_RU", "es_ES"];
+ var stringFormats = ["dd-MM-yyyy", "yyyy", "MM", "dd"];
+ var enumFormats = [Locale.ShortFormat, Locale.NarrowFormat, Locale.LongFormat];
+
+ for (var i = 0; i < locales.length; ++i) {
+ calendar.locale = Qt.locale(locales[i]);
+ for (var formatIndex = 0; formatIndex < stringFormats.length; ++formatIndex) {
+ calendar.selectedDateFormat = stringFormats[formatIndex];
+ compare(calendar.selectedDateText,
+ calendar.selectedDate.toLocaleDateString(calendar.locale, calendar.selectedDateFormat));
+ }
+
+ for (formatIndex = 0; formatIndex < enumFormats.length; ++formatIndex) {
+ calendar.selectedDateFormat = enumFormats[formatIndex];
+ compare(calendar.selectedDateText,
+ calendar.selectedDate.toLocaleDateString(calendar.locale, calendar.selectedDateFormat));
+ }
+ }
+ }
+
+ 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.");
+ }
+
+ function test_previousMonth() {
+ calendar.selectedDate = new Date(2013, 0, 1);
+ compare(calendar.selectedDate, new Date(2013, 0, 1));
+
+ calendar.previousMonth();
+ compare(calendar.selectedDate, new Date(2012, 11, 1));
+ }
+
+ function test_previousMonthWithMouse() {
+ calendar.selectedDate = new Date(2013, 1, 28);
+ compare(calendar.selectedDate, new Date(2013, 1, 28));
+
+ mouseClick(calendar, previousMonthButtonX, previousMonthButtonY, Qt.LeftButton);
+ compare(calendar.selectedDate, new Date(2013, 0, 28));
+
+ mouseClick(calendar, previousMonthButtonX, previousMonthButtonY, Qt.LeftButton);
+ compare(calendar.selectedDate, new Date(2012, 11, 28));
+ }
+
+ function test_nextMonth() {
+ calendar.selectedDate = new Date(2013, 0, 31);
+ compare(calendar.selectedDate, new Date(2013, 0, 31));
+
+ calendar.nextMonth();
+ compare(calendar.selectedDate, new Date(2013, 1, 28));
+
+ calendar.nextMonth();
+ compare(calendar.selectedDate, new Date(2013, 2, 28));
+ }
+
+ function test_nextMonthWithMouse() {
+ calendar.selectedDate = new Date(2013, 0, 31);
+ compare(calendar.selectedDate, new Date(2013, 0, 31));
+
+ waitForRendering(calendar)
+
+ mouseClick(calendar, nextMonthButtonX, nextMonthButtonY, Qt.LeftButton);
+ compare(calendar.selectedDate, new Date(2013, 1, 28));
+
+ mouseClick(calendar, nextMonthButtonX, nextMonthButtonY, Qt.LeftButton);
+ compare(calendar.selectedDate, new Date(2013, 2, 28));
+ }
+
+ function test_selectDateWithMouse() {
+ var startDate = new Date(2013, 0, 1);
+ calendar.selectedDate = startDate;
+ calendar.locale = Qt.locale("en_US");
+ compare(calendar.selectedDate, startDate);
+
+ // Clicking on header items should do nothing.
+ mouseClick(calendar, 0, navigationBarHeight, Qt.LeftButton);
+ compare(calendar.selectedDate, startDate);
+
+ var firstVisibleDate = new Date(2012, 11, 30);
+ for (var week = 0; week < DateUtils.weeksOnACalendarMonth; ++week) {
+ for (var day = 0; day < DateUtils.daysInAWeek; ++day) {
+ var expectedDate = new Date(firstVisibleDate);
+ expectedDate.setDate(expectedDate.getDate() + (week * DateUtils.daysInAWeek + day));
+
+ mouseClick(calendar, toPixelsX(day), toPixelsY(week), Qt.LeftButton);
+ expectFail("", "TODO: Mouse click seems to go to cell above (header). Works manually.");
+ compare(calendar.selectedDate, expectedDate);
+
+ 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);
+ }
+ }
+ }
+ }
+
+ 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");
+
+ /* 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 */
+ mouseClick(calendar, toPixelsX(0), toPixelsY(0), Qt.LeftButton);
+ compare(calendar.selectedDate, startDate);
+
+ /* 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) {
+ mouseClick(calendar, toPixelsX(x), toPixelsY(5), Qt.LeftButton);
+ compare(calendar.selectedDate, startDate);
+ }
+ }
+
+ function test_escapePressed() {
+ signalSpy.signalName = "escapePressed";
+ signalSpy.target = calendar;
+ keyPress(Qt.Key_Escape);
+ compare(signalSpy.count, 1);
+ }
+ }
+}
diff --git a/tests/auto/controls/data/tst_rangeddate.qml b/tests/auto/controls/data/tst_rangeddate.qml
new file mode 100644
index 00000000..b648bb28
--- /dev/null
+++ b/tests/auto/controls/data/tst_rangeddate.qml
@@ -0,0 +1,176 @@
+/****************************************************************************
+**
+** 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: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.0
+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() {
+ rangedDate.minimumDate = DateUtils.minimumCalendarDate;
+ rangedDate.maximumDate = DateUtils.maximumCalendarDate;
+
+ compare(rangedDate.minimumDate.getTime(), DateUtils.minimumCalendarDate.getTime());
+ compare(rangedDate.maximumDate.getTime(), DateUtils.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());
+ }
+ }
+}