From 11e5ff10ae993dd2078c6019623e60d7e12f8ff1 Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Mon, 27 Jul 2015 11:41:35 +0200 Subject: [PATCH] Improve calendar navigation This adds a "Year overview" showing all 12 months in a grid, and a "Decade overview" showing the current decade. CHANGELOG: Calendar navigation has been significantly improved, providing a year and decade overview REVIEW: 122488 --- src/declarativeimports/calendar/calendar.cpp | 67 ++-- src/declarativeimports/calendar/calendar.h | 12 +- src/declarativeimports/calendar/daydata.h | 4 +- src/declarativeimports/calendar/daysmodel.cpp | 12 +- src/declarativeimports/calendar/daysmodel.h | 4 +- .../calendar/qml/DayDelegate.qml | 66 +++- .../calendar/qml/DaysCalendar.qml | 192 ++++++++-- .../calendar/qml/MonthView.qml | 328 +++++++++--------- 8 files changed, 433 insertions(+), 252 deletions(-) diff --git a/src/declarativeimports/calendar/calendar.cpp b/src/declarativeimports/calendar/calendar.cpp index c462dbd9a..67a08e5c4 100644 --- a/src/declarativeimports/calendar/calendar.cpp +++ b/src/declarativeimports/calendar/calendar.cpp @@ -48,10 +48,22 @@ void Calendar::setDisplayedDate(const QDate &dateTime) if (m_displayedDate == dateTime) { return; } + + const int oldMonth = m_displayedDate.month(); + const int oldYear = m_displayedDate.year(); + m_displayedDate = dateTime; + // m_dayHelper->setDate(m_displayedDate.year(), m_displayedDate.month()); + updateData(); emit displayedDateChanged(); + if (oldMonth != m_displayedDate.month()) { + emit monthNameChanged(); + } + if (oldYear != m_displayedDate.year()) { + emit yearChanged(); + } } QDate Calendar::today() const @@ -222,9 +234,7 @@ void Calendar::updateData() //QDate previousMonth(m_displayedDate.year(), m_displayedDate.month() - 1, 1); for (int i = 0; i < daysBeforeCurrentMonth; i++) { DayData day; - day.isCurrentMonth = false; - day.isNextMonth = false; - day.isPreviousMonth = true; + day.isCurrent = false; day.dayNumber = previousMonth.daysInMonth() - (daysBeforeCurrentMonth - (i + 1)); day.monthNumber = previousMonth.month(); day.yearNumber = previousMonth.year(); @@ -235,9 +245,7 @@ void Calendar::updateData() for (int i = 0; i < m_displayedDate.daysInMonth(); i++) { DayData day; - day.isCurrentMonth = true; - day.isNextMonth = false; - day.isPreviousMonth = false; + day.isCurrent = true; day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates // day.containsEventItems = m_dayHelper->containsEventItems(i + 1); day.monthNumber = m_displayedDate.month(); @@ -249,9 +257,7 @@ void Calendar::updateData() if (daysAfterCurrentMonth > 0) { for (int i = 0; i < daysAfterCurrentMonth; i++) { DayData day; - day.isCurrentMonth = false; - day.isNextMonth = true; - day.isPreviousMonth = false; + day.isCurrent = false; day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates // day.containsEventItems = false; day.monthNumber = m_displayedDate.addMonths(1).month(); @@ -292,36 +298,43 @@ void Calendar::updateData() // qDebug() << "m_dayList size: " << m_dayList.count(); // qDebug() << "---------------------------------------------------------------"; } + +void Calendar::nextDecade() +{ + setDisplayedDate(m_displayedDate.addYears(10)); +} + +void Calendar::previousDecade() +{ + setDisplayedDate(m_displayedDate.addYears(-10)); +} + void Calendar::nextYear() { - m_displayedDate = m_displayedDate.addYears(1); - updateData(); - emit displayedDateChanged(); - emit yearChanged(); + setDisplayedDate(m_displayedDate.addYears(1)); } void Calendar::previousYear() { - m_displayedDate = m_displayedDate.addYears(-1); - updateData(); - emit displayedDateChanged(); - emit yearChanged(); + setDisplayedDate(m_displayedDate.addYears(-1)); } void Calendar::nextMonth() { - m_displayedDate = m_displayedDate.addMonths(1); - updateData(); - emit displayedDateChanged(); - emit monthNameChanged(); - emit yearChanged(); + setDisplayedDate(m_displayedDate.addMonths(1)); } void Calendar::previousMonth() { - m_displayedDate = m_displayedDate.addMonths(-1); - updateData(); - emit displayedDateChanged(); - emit monthNameChanged(); - emit yearChanged(); + setDisplayedDate(m_displayedDate.addMonths(-1)); +} + +void Calendar::goToMonth(int month) +{ + setDisplayedDate(QDate(m_displayedDate.year(), month, m_displayedDate.day())); +} + +void Calendar::goToYear(int year) +{ + setDisplayedDate(QDate(year, m_displayedDate.month(), m_displayedDate.day())); } diff --git a/src/declarativeimports/calendar/calendar.h b/src/declarativeimports/calendar/calendar.h index 5dc3081a4..1849ada32 100644 --- a/src/declarativeimports/calendar/calendar.h +++ b/src/declarativeimports/calendar/calendar.h @@ -118,7 +118,7 @@ class Calendar : public QObject */ Q_PROPERTY(QAbstractListModel *daysModel READ daysModel CONSTANT) - Q_ENUMS(Type) + Q_ENUMS(Type DateMatchingPrecision) public: enum Type { @@ -129,6 +129,12 @@ public: }; Q_DECLARE_FLAGS(Types, Type) + enum DateMatchingPrecision { + MatchYear, + MatchYearAndMonth, + MatchYearMonthAndDay + }; + explicit Calendar(QObject *parent = 0); // Displayed date @@ -171,9 +177,13 @@ public: Q_INVOKABLE void previousMonth(); Q_INVOKABLE void nextYear(); Q_INVOKABLE void previousYear(); + Q_INVOKABLE void nextDecade(); + Q_INVOKABLE void previousDecade(); Q_INVOKABLE QString dayName(int weekday) const; Q_INVOKABLE int currentWeek() const; Q_INVOKABLE void resetToToday(); + Q_INVOKABLE void goToMonth(int month); + Q_INVOKABLE void goToYear(int year); Q_SIGNALS: void displayedDateChanged(); diff --git a/src/declarativeimports/calendar/daydata.h b/src/declarativeimports/calendar/daydata.h index 39ac086e0..26616f0ba 100644 --- a/src/declarativeimports/calendar/daydata.h +++ b/src/declarativeimports/calendar/daydata.h @@ -4,9 +4,7 @@ class DayData { public: - bool isPreviousMonth; - bool isCurrentMonth; - bool isNextMonth; + bool isCurrent; // bool containsHolidayItems; // bool containsEventItems; // bool containsTodoItems; diff --git a/src/declarativeimports/calendar/daysmodel.cpp b/src/declarativeimports/calendar/daysmodel.cpp index 1a6f4546c..2d059a8e8 100644 --- a/src/declarativeimports/calendar/daysmodel.cpp +++ b/src/declarativeimports/calendar/daysmodel.cpp @@ -26,9 +26,7 @@ DaysModel::DaysModel(QObject *parent) : { QHash roleNames; - roleNames.insert(isPreviousMonth, "isPreviousMonth"); - roleNames.insert(isCurrentMonth, "isCurrentMonth"); - roleNames.insert(isNextMonth, "isNextMonth"); + roleNames.insert(isCurrent, "isCurrent"); //roleNames.insert(containsHolidayItems, "containsHolidayItems"); //roleNames.insert(containsEventItems, "containsEventItems"); // roleNames.insert(containsTodoItems, "containsTodoItems"); @@ -63,13 +61,11 @@ QVariant DaysModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { - DayData currentData = m_data->at(index.row()); + const DayData ¤tData = m_data->at(index.row()); switch (role) { - case isPreviousMonth: - return currentData.isPreviousMonth; - case isNextMonth: - return currentData.isNextMonth; + case isCurrent: + return currentData.isCurrent; // case containsHolidayItems: // return currentData.containsHolidayItems; /* case containsEventItems: diff --git a/src/declarativeimports/calendar/daysmodel.h b/src/declarativeimports/calendar/daysmodel.h index e1285f614..a5bdac986 100644 --- a/src/declarativeimports/calendar/daysmodel.h +++ b/src/declarativeimports/calendar/daysmodel.h @@ -28,9 +28,7 @@ class DaysModel : public QAbstractListModel Q_OBJECT public: enum Roles { - isPreviousMonth = Qt::UserRole + 1, - isCurrentMonth, - isNextMonth, + isCurrent = Qt::UserRole + 1, //containsHolidayItems, //containsEventItems, //containsTodoItems, diff --git a/src/declarativeimports/calendar/qml/DayDelegate.qml b/src/declarativeimports/calendar/qml/DayDelegate.qml index 6a3747e12..b2c79af29 100644 --- a/src/declarativeimports/calendar/qml/DayDelegate.qml +++ b/src/declarativeimports/calendar/qml/DayDelegate.qml @@ -1,6 +1,7 @@ /* * Copyright 2013 Heena Mahour * Copyright 2013 Sebastian Kügler + * Copyright 2015 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -20,26 +21,55 @@ import org.kde.plasma.calendar 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as Components +import org.kde.plasma.calendar 2.0 Item { id: dayStyle - width: root.cellWidth - height: root.cellHeight - property bool today: root.today.toDateString() == new Date(yearNumber, monthNumber - 1, dayNumber).toDateString() // for some reason the comparison doesn't work without toDateString() + signal activated + + readonly property date thisDate: new Date(yearNumber, typeof monthNumber !== "undefined" ? monthNumber - 1 : 0, typeof dayNumber !== "undefined" ? dayNumber : 1) + readonly property bool today: { + var today = root.today; + var result = true; + if (dateMatchingPrecision >= Calendar.MatchYear) { + result = result && today.getFullYear() === thisDate.getFullYear() + } + if (dateMatchingPrecision >= Calendar.MatchYearAndMonth) { + result = result && today.getMonth() === thisDate.getMonth() + } + if (dateMatchingPrecision >= Calendar.MatchYearMonthAndDay) { + result = result && today.getDate() === thisDate.getDate() + } + return result + } + readonly property bool selected: { + var current = root.currentDate + var result = true + if (dateMatchingPrecision >= Calendar.MatchYear) { + result = result && current.getFullYear() === thisDate.getFullYear() + } + if (dateMatchingPrecision >= Calendar.MatchYearAndMonth) { + result = result && current.getMonth() === thisDate.getMonth() + } + if (dateMatchingPrecision >= Calendar.MatchYearMonthAndDay) { + result = result && current.getDate() === thisDate.getDate() + } + return result + } onHeightChanged: { // this is needed here as the text is first rendered, counting with the default root.cellHeight // then root.cellHeight actually changes to whatever it should be, but the Label does not pick // it up after that, so we need to change it explicitly after the cell size changes - label.font.pixelSize = Math.max(theme.smallestFont.pixelSize, Math.floor(root.cellHeight / 3)) + label.font.pixelSize = Math.max(theme.smallestFont.pixelSize, Math.floor(daysCalendar.cellHeight / 3)) } Rectangle { id: todayRect anchors.fill: parent opacity: { - if (calendarGrid.selectedItem == dayStyle && today) { + if (selected && today) { 0.6 } else if (today) { 0.4 @@ -55,7 +85,7 @@ Item { id: highlightDate anchors.fill: todayRect opacity: { - if (calendarGrid.selectedItem == dayStyle) { + if (selected) { 0.6 } else if (dateMouse.containsMouse) { 0.4 @@ -71,10 +101,17 @@ Item { Components.Label { id: label - anchors.centerIn: parent - text: dayNumber - opacity: (isPreviousMonth || isNextMonth) ? 0.5: 1.0 + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: model.label || dayNumber + opacity: isCurrent ? 1.0 : 0.5 + wrapMode: Text.NoWrap + elide: Text.ElideRight color: today ? theme.backgroundColor : theme.textColor + Behavior on color { + ColorAnimation { duration: units.shortDuration * 2 } + } } MouseArea { @@ -82,17 +119,12 @@ Item { anchors.fill: parent //z: label.z + 1 hoverEnabled: true - onClicked: { - var rowNumber = Math.floor(index / 7); - week = 1+calendarBackend.weeksModel[rowNumber]; - root.date = model; - calendarGrid.selectedItem = dayStyle; - } + onClicked: dayStyle.activated() } Component.onCompleted: { - if (today) { - root.date = model; + if (stack.depth === 1 && today) { + root.date = model } } } diff --git a/src/declarativeimports/calendar/qml/DaysCalendar.qml b/src/declarativeimports/calendar/qml/DaysCalendar.qml index ab3e750ed..35a1e202c 100644 --- a/src/declarativeimports/calendar/qml/DaysCalendar.qml +++ b/src/declarativeimports/calendar/qml/DaysCalendar.qml @@ -1,6 +1,7 @@ /* * Copyright 2013 Heena Mahour * Copyright 2013 Sebastian Kügler + * Copyright 2015 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -16,22 +17,146 @@ * along with this program. If not, see . */ import QtQuick 2.0 + import org.kde.plasma.calendar 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as Components import org.kde.plasma.extras 2.0 as PlasmaExtras + Item { id: daysCalendar - readonly property int gridColumns: root.showWeekNumbers ? calendarGrid.columns + 1 : calendarGrid.columns + signal headerClicked + + signal previous + signal next + + signal activated(int index, var date, var item) + + readonly property int gridColumns: showWeekNumbers ? calendarGrid.columns + 1 : calendarGrid.columns + + property int rows + property int columns + + property bool showWeekNumbers + + onShowWeekNumbersChanged: canvas.requestPaint() + + // how precise date matching should be, 3 = day+month+year, 2 = month+year, 1 = just year + property int dateMatchingPrecision + + property alias headerModel: days.model + property alias gridModel: repeater.model + + property alias title: heading.text + + // Take the calendar width, subtract the inner and outer spacings and divide by number of columns (==days in week) + readonly property int cellWidth: Math.floor((stack.width - (daysCalendar.columns + 1) * root.borderWidth) / (daysCalendar.columns + (showWeekNumbers ? 1 : 0))) + // Take the calendar height, subtract the inner spacings and divide by number of rows (root.weeks + one row for day names) + readonly property int cellHeight: Math.floor((stack.height - heading.height - (daysCalendar.rows + 1) * root.borderWidth) / (daysCalendar.rows + 1)) + + PlasmaExtras.Heading { + id: heading + + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + level: 1 + elide: Text.ElideRight + font.capitalization: Font.Capitalize + + MouseArea { + id: monthMouse + property int previousPixelDelta + + width: heading.paintedWidth + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + onClicked: { + if (!stack.busy) { + daysCalendar.headerClicked() + } + } + onExited: previousPixelDelta = 0 + onWheel: { + var delta = wheel.angleDelta.y || wheel.angleDelta.x + var pixelDelta = wheel.pixelDelta.y || wheel.pixelDelta.x + + // For high-precision touchpad scrolling, we get a wheel event for basically every slightest + // finger movement. To prevent the view from suddenly ending up in the next century, we + // cumulate all the pixel deltas until they're larger than the label and then only change + // the month. Standard mouse wheel scrolling is unaffected since it's fine. + if (pixelDelta) { + if (Math.abs(previousPixelDelta) < monthMouse.height) { + previousPixelDelta += pixelDelta + return + } + } + + if (delta >= 15) { + daysCalendar.previous() + } else if (delta <= -15) { + daysCalendar.next() + } + previousPixelDelta = 0 + } + } + } + + Components.Label { + anchors { + top: heading.bottom + left: parent.left + leftMargin: Math.floor(units.largeSpacing / 2) + } + text: "◀" + opacity: leftmouse.containsMouse ? 1 : 0.4 + Behavior on opacity { NumberAnimation {} } + + MouseArea { + id: leftmouse + anchors.fill: parent + anchors.margins: -units.largeSpacing / 3 + hoverEnabled: true + onClicked: daysCalendar.previous() + } + } + + Components.Label { + anchors { + top: heading.bottom + right: parent.right + rightMargin: Math.floor(units.largeSpacing / 2) + } + text: "▶" + opacity: rightmouse.containsMouse ? 1 : 0.4 + Behavior on opacity { NumberAnimation {} } + + MouseArea { + id: rightmouse + anchors.fill: parent + anchors.margins: -units.largeSpacing / 3 + hoverEnabled: true + onClicked: daysCalendar.next() + } + } // Paints the inner grid and the outer frame Canvas { id: canvas - width: (root.cellWidth + root.borderWidth) * gridColumns + root.borderWidth - height: (root.cellHeight + root.borderWidth) * calendarGrid.rows + root.borderWidth - anchors.bottom: parent.bottom + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + } + width: (daysCalendar.cellWidth + root.borderWidth) * gridColumns + root.borderWidth + height: (daysCalendar.cellHeight + root.borderWidth) * calendarGrid.rows + root.borderWidth opacity: root.borderOpacity antialiasing: false @@ -58,26 +183,26 @@ Item { // horizontal lines for (var i = 0; i < calendarGrid.rows + 1; i++) { - var lineY = lineBasePoint + (root.cellHeight + root.borderWidth) * (i); + var lineY = lineBasePoint + (daysCalendar.cellHeight + root.borderWidth) * (i); if (i == 0 || i == calendarGrid.rows) { ctx.moveTo(0, lineY); } else { - ctx.moveTo(root.showWeekNumbers ? root.cellWidth + root.borderWidth : root.borderWidth, lineY); + ctx.moveTo(showWeekNumbers ? daysCalendar.cellWidth + root.borderWidth : root.borderWidth, lineY); } ctx.lineTo(width, lineY); } // vertical lines for (var i = 0; i < gridColumns + 1; i++) { - var lineX = lineBasePoint + (root.cellWidth + root.borderWidth) * (i); + var lineX = lineBasePoint + (daysCalendar.cellWidth + root.borderWidth) * (i); // Draw the outer vertical lines in full height so that it closes // the outer rectangle if (i == 0 || i == gridColumns) { ctx.moveTo(lineX, 0); } else { - ctx.moveTo(lineX, root.borderWidth + root.cellHeight); + ctx.moveTo(lineX, root.borderWidth + daysCalendar.cellHeight); } ctx.lineTo(lineX, height); } @@ -88,13 +213,6 @@ Item { } } - Connections { - target: root - onShowWeekNumbersChanged: { - canvas.requestPaint(); - } - } - Connections { target: theme onTextColorChanged: { @@ -104,7 +222,7 @@ Item { Column { id: weeksColumn - visible: root.showWeekNumbers + visible: showWeekNumbers anchors { top: canvas.top left: parent.left @@ -112,40 +230,38 @@ Item { // The borderWidth needs to be counted twice here because it goes // in fact through two lines - the topmost one (the outer edge) // and then the one below weekday strings - topMargin: root.cellHeight + root.borderWidth + root.borderWidth + topMargin: daysCalendar.cellHeight + root.borderWidth + root.borderWidth } spacing: root.borderWidth Repeater { - model: root.showWeekNumbers ? calendarBackend.weeksModel : [] + model: showWeekNumbers ? calendarBackend.weeksModel : [] Components.Label { - height: root.cellHeight - width: root.cellWidth + height: daysCalendar.cellHeight + width: daysCalendar.cellWidth horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter opacity: 0.4 text: modelData - font.pixelSize: Math.max(theme.smallestFont.pixelSize, root.cellHeight / 6) + font.pixelSize: Math.max(theme.smallestFont.pixelSize, daysCalendar.cellHeight / 6) } } } Grid { id: calendarGrid - // Pad the grid to not overlap with the top and left frame - // When week numbers are shown, the border needs to be counted twice - // because there's one more cell to count with and therefore also - // another border to add - x: root.showWeekNumbers ? 2 * root.borderWidth + root.cellWidth: root.borderWidth anchors { + right: canvas.right + rightMargin: root.borderWidth bottom: parent.bottom bottomMargin: root.borderWidth } - columns: calendarBackend.days - rows: calendarBackend.weeks + 1 + columns: daysCalendar.columns + rows: daysCalendar.rows + 1 + spacing: root.borderWidth property Item selectedItem property bool containsEventItems: false // FIXME @@ -159,17 +275,16 @@ Item { } } - - Repeater { id: days - model: calendarBackend.days + Item { - width: root.cellWidth - height: root.cellHeight + width: daysCalendar.cellWidth + height: daysCalendar.cellHeight + Components.Label { text: Qt.locale().dayName(calendarBackend.firstDayOfWeek + index, Locale.ShortFormat) - font.pixelSize: Math.max(theme.smallestFont.pixelSize, root.cellHeight / 6) + font.pixelSize: Math.max(theme.smallestFont.pixelSize, daysCalendar.cellHeight / 6) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignBottom anchors.horizontalCenter: parent.horizontalCenter @@ -181,9 +296,14 @@ Item { Repeater { id: repeater - model: calendarBackend.daysModel - DayDelegate {} + DayDelegate { + id: delegate + width: daysCalendar.cellWidth + height: daysCalendar.cellHeight + + onActivated: daysCalendar.activated(index, model, delegate) + } } } } diff --git a/src/declarativeimports/calendar/qml/MonthView.qml b/src/declarativeimports/calendar/qml/MonthView.qml index 601755f4e..57ac59a3f 100644 --- a/src/declarativeimports/calendar/qml/MonthView.qml +++ b/src/declarativeimports/calendar/qml/MonthView.qml @@ -1,6 +1,7 @@ /* * Copyright 2013 Heena Mahour * Copyright 2013 Sebastian Kügler + * Copyright 2015 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -16,7 +17,9 @@ * along with this program. If not, see . */ import QtQuick 2.0 +import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 + import org.kde.plasma.calendar 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents @@ -27,31 +30,26 @@ Item { anchors.fill: parent - property QtObject date - property date showDate: new Date() - property alias selectedMonth: calendarBackend.monthName property alias selectedYear: calendarBackend.year + property QtObject date + property date currentDate + + property date showDate: new Date() + property int borderWidth: 1 property real borderOpacity: 0.4 property int columns: calendarBackend.days property int rows: calendarBackend.weeks - // Take the calendar width, subtract the inner and outer spacings and divide by number of columns (==days in week) - property int cellWidth: Math.floor((calendar.width - (root.columns + 1) * borderWidth) / (root.columns + (root.showWeekNumbers ? 1 : 0))) //prefCellWidth() - - // Take the calendar height, subtract the inner spacings and divide by number of rows (root.weeks + one row for day names) - property int cellHeight: Math.floor((calendar.height - (root.rows + 1) * borderWidth) / (root.rows + 1)) //prefCellHeight() - property Item selectedItem property int week; property int firstDay: new Date(showDate.getFullYear(), showDate.getMonth(), 1).getDay() property date today property bool showWeekNumbers: false - function isToday(date) { if (date.toDateString() == new Date().toDateString()) { return true; @@ -67,6 +65,31 @@ Item { function resetToToday() { calendarBackend.resetToToday(); + stack.pop(null); + } + + function updateYearOverview() { + var date = calendarBackend.displayedDate; + var day = date.getDate(); + var year = date.getFullYear(); + + for (var i = 0, j = monthModel.count; i < j; ++i) { + monthModel.setProperty(i, "yearNumber", year); + } + } + + function updateDecadeOverview() { + var date = calendarBackend.displayedDate; + var day = date.getDate(); + var month = date.getMonth() + 1; + var year = date.getFullYear(); + var decade = year - year % 10; + + for (var i = 0, j = yearModel.count; i < j; ++i) { + var label = decade - 1 + i; + yearModel.setProperty(i, "yearNumber", label); + yearModel.setProperty(i, "label", label); + } } Calendar { @@ -76,176 +99,167 @@ Item { weeks: 6 firstDayOfWeek: Qt.locale().firstDayOfWeek today: root.today + + onYearChanged: { + updateYearOverview() + updateDecadeOverview() + } } - ColumnLayout { - // This is to ensure that the inner grid.width is always aligned to be divisible by 7, - // fixes wrong side margins because of the rounding of cell size - // (consider the parent.width to be 404, the cell width would be 56, - // but 56*7 + 6 (the inner spacing) is 398, so we split the remaining 6 to avoid - // wrong alignment) - anchors { - fill: parent - leftMargin: Math.floor(((parent.width - (calendar.gridColumns + 1) * borderWidth) % calendar.gridColumns) / 2) - rightMargin: anchors.leftMargin - bottomMargin: anchors.leftMargin - } + ListModel { + id: monthModel - PlasmaExtras.Heading { - id: monthHeading - - level: 1 - text: calendarBackend.displayedDate.getFullYear() == new Date().getFullYear() ? root.selectedMonth : root.selectedMonth + ", " + root.selectedYear - elide: Text.ElideRight - font.capitalization: Font.Capitalize - - Loader { - id: menuLoader - property QtObject calendarBackend: calendarBackend + Component.onCompleted: { + for (var i = 0; i < 12; ++i) { + append({ + label: Qt.locale().standaloneMonthName(i, Locale.LongFormat), + monthNumber: i + 1, + isCurrent: true + }) } - MouseArea { - id: monthMouse - property int previousPixelDelta + updateYearOverview() + } + } - width: monthHeading.paintedWidth - anchors { - left: parent.left - top: parent.top - bottom: parent.bottom + ListModel { + id: yearModel + + Component.onCompleted: { + for (var i = 0; i < 12; ++i) { + append({ + isCurrent: (i > 0 && i < 11) // first and last year are outside the decade + }) + } + updateDecadeOverview() + } + } + + StackView { + id: stack + + anchors.fill: parent + + delegate: StackViewDelegate { + pushTransition: StackViewTransition { + NumberAnimation { + target: exitItem + duration: units.longDuration + property: "opacity" + from: 1 + to: 0 } - onClicked: { - if (menuLoader.source == "") { - menuLoader.source = "MonthMenu.qml" - } - menuLoader.item.year = selectedYear - menuLoader.item.open(0, height); + NumberAnimation { + target: enterItem + duration: units.longDuration + property: "opacity" + from: 0 + to: 1 } - onExited: previousPixelDelta = 0 - onWheel: { - var delta = wheel.angleDelta.y || wheel.angleDelta.x - var pixelDelta = wheel.pixelDelta.y || wheel.pixelDelta.x - - // For high-precision touchpad scrolling, we get a wheel event for basically every slightest - // finger movement. To prevent the view from suddenly ending up in the next century, we - // cumulate all the pixel deltas until they're larger than the label and then only change - // the month. Standard mouse wheel scrolling is unaffected since it's fine. - if (pixelDelta) { - if (Math.abs(previousPixelDelta) < monthMouse.height) { - previousPixelDelta += pixelDelta - return - } - } - - if (delta >= 15) { - calendarBackend.previousMonth() - } else if (delta <= -15) { - calendarBackend.nextMonth() - } - previousPixelDelta = 0 + NumberAnimation { + target: enterItem + duration: units.longDuration + property: "scale" + from: 1.5 + to: 1 + } + } + popTransition: StackViewTransition { + NumberAnimation { + target: exitItem + duration: units.longDuration + property: "opacity" + from: 1 + to: 0 + } + NumberAnimation { + target: exitItem + duration: units.longDuration + property: "scale" + from: 1 + to: 1.5 + } + NumberAnimation { + target: enterItem + duration: units.longDuration + property: "opacity" + from: 0 + to: 1 } } } + initialItem: DaysCalendar { + title: calendarBackend.displayedDate.getFullYear() == new Date().getFullYear() ? root.selectedMonth : root.selectedMonth + ", " + root.selectedYear + + columns: calendarBackend.days + rows: calendarBackend.weeks + + showWeekNumbers: root.showWeekNumbers + + headerModel: calendarBackend.days + gridModel: calendarBackend.daysModel + + dateMatchingPrecision: Calendar.MatchYearMonthAndDay + + onPrevious: calendarBackend.previousMonth() + onNext: calendarBackend.nextMonth() + onHeaderClicked: { + stack.push(yearOverview) + } + onActivated: { + var rowNumber = Math.floor(index / 7); + week = 1 + calendarBackend.weeksModel[rowNumber]; + root.date = date + root.currentDate = new Date(date.yearNumber, date.monthNumber - 1, date.dayNumber) + } + } + } + + Component { + id: yearOverview + DaysCalendar { - id: calendar + title: calendarBackend.displayedDate.getFullYear() + columns: 3 + rows: 4 - Layout.fillWidth: true - Layout.fillHeight: true + dateMatchingPrecision: Calendar.MatchYearAndMonth - PlasmaComponents.Label { - text: "◀" - opacity: leftmouse.containsMouse ? 1 : 0.4 - Behavior on opacity { NumberAnimation {} } - font.pixelSize: Math.max(theme.smallestFont.pixelSize, Math.floor(root.cellHeight / 3)) - anchors { - top: parent.top - left: parent.left - leftMargin: Math.floor(units.largeSpacing / 2) + root.borderWidth - topMargin: anchors.leftMargin - } - MouseArea { - id: leftmouse - anchors.fill: parent - anchors.margins: -units.largeSpacing / 3 - hoverEnabled: true - onClicked: { - calendarBackend.previousMonth() - } - } - } - PlasmaComponents.Label { - text: "▶" - opacity: rightmouse.containsMouse ? 1 : 0.4 - Behavior on opacity { NumberAnimation {} } - font.pixelSize: Math.max(theme.smallestFont.pixelSize, Math.floor(root.cellHeight / 3)) - anchors { - top: parent.top - right: parent.right - rightMargin: Math.floor(units.largeSpacing / 2) + root.borderWidth - topMargin: anchors.rightMargin - } - MouseArea { - id: rightmouse - anchors.fill: parent - anchors.margins: -units.largeSpacing / 3 - hoverEnabled: true - onClicked: { - calendarBackend.nextMonth() - } - } + gridModel: monthModel + + onPrevious: calendarBackend.previousYear() + onNext: calendarBackend.nextYear() + onHeaderClicked: stack.push(decadeOverview) + onActivated: { + calendarBackend.goToMonth(date.monthNumber) + stack.pop() } } } + Component { + id: decadeOverview -/* - Item { - id: calendarToolbar - visible: false - anchors { - left: parent.left - right: parent.right - bottomMargin: 20 - bottom: parent.bottom - } + DaysCalendar { + readonly property int decade: { + var year = calendarBackend.displayedDate.getFullYear() + return year - year % 10 + } - PlasmaComponents.ToolButton { - id: currentDate - iconSource: "view-pim-calendar" - width: height - onClicked: { - calendarBackend.startDate = today(); - } - PlasmaCore.ToolTipArea { - id: tool - anchors.fill: currentDate - mainText: "Select Today" - } - anchors { - left: parent.left - } - } + title: decade + " – " + (decade + 9) + columns: 3 + rows: 4 - PlasmaComponents.TextField { - id: dateField - text: date == "" ? Qt.formatDateTime ( new Date(), "d/M/yyyy" ): date - width: calendarOperations.width/3 - anchors { - leftMargin: 20 - rightMargin: 30 - left: currentDate.right - right: weekField.left - } - } + dateMatchingPrecision: Calendar.MatchYear - PlasmaComponents.TextField { - id: weekField - text: week == 0 ? calendarBackend.currentWeek(): week - width: calendarOperations.width/10 - anchors { - right: parent.right + gridModel: yearModel + + onPrevious: calendarBackend.previousDecade() + onNext: calendarBackend.nextDecade() + onActivated: { + calendarBackend.goToYear(date.yearNumber) + stack.pop() } } } - */ }