diff options
author | Mitch Curtis <mitch.curtis@digia.com> | 2013-07-15 22:57:52 +0200 |
---|---|---|
committer | Mitch Curtis <mitch.curtis@digia.com> | 2013-12-06 13:07:41 +0100 |
commit | 8cfe8d2104baa9224586b5f087cb2f12ec74eea3 (patch) | |
tree | 733066323d2368e02847ca924afc069b53669e5c | |
parent | 4746541dcfebb73ea8398e60e046e394ea9fa441 (diff) |
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.qml | 369 | ||||
-rw-r--r-- | src/controls/CalendarHeaderModel.qml | 92 | ||||
-rw-r--r-- | src/controls/CalendarModel.qml | 117 | ||||
-rw-r--r-- | src/controls/Private/DateUtils.js | 153 | ||||
-rw-r--r-- | src/controls/Private/private.pri | 3 | ||||
-rw-r--r-- | src/controls/Private/qmldir | 1 | ||||
-rw-r--r-- | src/controls/Private/qquickrangeddate.cpp | 115 | ||||
-rw-r--r-- | src/controls/Private/qquickrangeddate_p.h | 96 | ||||
-rw-r--r-- | src/controls/Styles/Base/CalendarStyle.qml | 283 | ||||
-rw-r--r-- | src/controls/Styles/qmldir | 1 | ||||
-rw-r--r-- | src/controls/Styles/styles.pri | 1 | ||||
-rw-r--r-- | src/controls/controls.pro | 3 | ||||
-rw-r--r-- | src/controls/plugin.cpp | 6 | ||||
-rw-r--r-- | tests/auto/controls/controls.pro | 2 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_calendar.qml | 364 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_rangeddate.qml | 176 |
16 files changed, 1782 insertions, 0 deletions
diff --git a/src/controls/Calendar.qml b/src/controls/Calendar.qml new file mode 100644 index 000000000..779b8b8e7 --- /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 000000000..db8ae53d1 --- /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 000000000..a7fca5792 --- /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 000000000..d482cea62 --- /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 304649745..c717febf3 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 351387806..d2cf5f487 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 000000000..18f5bd45b --- /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 000000000..537715399 --- /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 000000000..dae6c6caf --- /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 5cd368ac8..5d5cc0a3b 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 d695893a0..a062edc46 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 e31f86838..f772c3210 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 2a676e48d..2ca212206 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 ce3857a6b..fedc36c48 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 000000000..6fab00385 --- /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 000000000..b648bb286 --- /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()); + } + } +} |