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
This commit is contained in:
Kai Uwe Broulik 2015-07-27 11:41:35 +02:00
parent 80940951e0
commit 11e5ff10ae
8 changed files with 433 additions and 252 deletions

View File

@ -48,10 +48,22 @@ void Calendar::setDisplayedDate(const QDate &dateTime)
if (m_displayedDate == dateTime) { if (m_displayedDate == dateTime) {
return; return;
} }
const int oldMonth = m_displayedDate.month();
const int oldYear = m_displayedDate.year();
m_displayedDate = dateTime; m_displayedDate = dateTime;
// m_dayHelper->setDate(m_displayedDate.year(), m_displayedDate.month()); // m_dayHelper->setDate(m_displayedDate.year(), m_displayedDate.month());
updateData(); updateData();
emit displayedDateChanged(); emit displayedDateChanged();
if (oldMonth != m_displayedDate.month()) {
emit monthNameChanged();
}
if (oldYear != m_displayedDate.year()) {
emit yearChanged();
}
} }
QDate Calendar::today() const QDate Calendar::today() const
@ -222,9 +234,7 @@ void Calendar::updateData()
//QDate previousMonth(m_displayedDate.year(), m_displayedDate.month() - 1, 1); //QDate previousMonth(m_displayedDate.year(), m_displayedDate.month() - 1, 1);
for (int i = 0; i < daysBeforeCurrentMonth; i++) { for (int i = 0; i < daysBeforeCurrentMonth; i++) {
DayData day; DayData day;
day.isCurrentMonth = false; day.isCurrent = false;
day.isNextMonth = false;
day.isPreviousMonth = true;
day.dayNumber = previousMonth.daysInMonth() - (daysBeforeCurrentMonth - (i + 1)); day.dayNumber = previousMonth.daysInMonth() - (daysBeforeCurrentMonth - (i + 1));
day.monthNumber = previousMonth.month(); day.monthNumber = previousMonth.month();
day.yearNumber = previousMonth.year(); day.yearNumber = previousMonth.year();
@ -235,9 +245,7 @@ void Calendar::updateData()
for (int i = 0; i < m_displayedDate.daysInMonth(); i++) { for (int i = 0; i < m_displayedDate.daysInMonth(); i++) {
DayData day; DayData day;
day.isCurrentMonth = true; day.isCurrent = true;
day.isNextMonth = false;
day.isPreviousMonth = false;
day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates
// day.containsEventItems = m_dayHelper->containsEventItems(i + 1); // day.containsEventItems = m_dayHelper->containsEventItems(i + 1);
day.monthNumber = m_displayedDate.month(); day.monthNumber = m_displayedDate.month();
@ -249,9 +257,7 @@ void Calendar::updateData()
if (daysAfterCurrentMonth > 0) { if (daysAfterCurrentMonth > 0) {
for (int i = 0; i < daysAfterCurrentMonth; i++) { for (int i = 0; i < daysAfterCurrentMonth; i++) {
DayData day; DayData day;
day.isCurrentMonth = false; day.isCurrent = false;
day.isNextMonth = true;
day.isPreviousMonth = false;
day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates day.dayNumber = i + 1; // +1 to go form 0 based index to 1 based calendar dates
// day.containsEventItems = false; // day.containsEventItems = false;
day.monthNumber = m_displayedDate.addMonths(1).month(); day.monthNumber = m_displayedDate.addMonths(1).month();
@ -292,36 +298,43 @@ void Calendar::updateData()
// qDebug() << "m_dayList size: " << m_dayList.count(); // qDebug() << "m_dayList size: " << m_dayList.count();
// qDebug() << "---------------------------------------------------------------"; // qDebug() << "---------------------------------------------------------------";
} }
void Calendar::nextDecade()
{
setDisplayedDate(m_displayedDate.addYears(10));
}
void Calendar::previousDecade()
{
setDisplayedDate(m_displayedDate.addYears(-10));
}
void Calendar::nextYear() void Calendar::nextYear()
{ {
m_displayedDate = m_displayedDate.addYears(1); setDisplayedDate(m_displayedDate.addYears(1));
updateData();
emit displayedDateChanged();
emit yearChanged();
} }
void Calendar::previousYear() void Calendar::previousYear()
{ {
m_displayedDate = m_displayedDate.addYears(-1); setDisplayedDate(m_displayedDate.addYears(-1));
updateData();
emit displayedDateChanged();
emit yearChanged();
} }
void Calendar::nextMonth() void Calendar::nextMonth()
{ {
m_displayedDate = m_displayedDate.addMonths(1); setDisplayedDate(m_displayedDate.addMonths(1));
updateData();
emit displayedDateChanged();
emit monthNameChanged();
emit yearChanged();
} }
void Calendar::previousMonth() void Calendar::previousMonth()
{ {
m_displayedDate = m_displayedDate.addMonths(-1); setDisplayedDate(m_displayedDate.addMonths(-1));
updateData(); }
emit displayedDateChanged();
emit monthNameChanged(); void Calendar::goToMonth(int month)
emit yearChanged(); {
setDisplayedDate(QDate(m_displayedDate.year(), month, m_displayedDate.day()));
}
void Calendar::goToYear(int year)
{
setDisplayedDate(QDate(year, m_displayedDate.month(), m_displayedDate.day()));
} }

View File

@ -118,7 +118,7 @@ class Calendar : public QObject
*/ */
Q_PROPERTY(QAbstractListModel *daysModel READ daysModel CONSTANT) Q_PROPERTY(QAbstractListModel *daysModel READ daysModel CONSTANT)
Q_ENUMS(Type) Q_ENUMS(Type DateMatchingPrecision)
public: public:
enum Type { enum Type {
@ -129,6 +129,12 @@ public:
}; };
Q_DECLARE_FLAGS(Types, Type) Q_DECLARE_FLAGS(Types, Type)
enum DateMatchingPrecision {
MatchYear,
MatchYearAndMonth,
MatchYearMonthAndDay
};
explicit Calendar(QObject *parent = 0); explicit Calendar(QObject *parent = 0);
// Displayed date // Displayed date
@ -171,9 +177,13 @@ public:
Q_INVOKABLE void previousMonth(); Q_INVOKABLE void previousMonth();
Q_INVOKABLE void nextYear(); Q_INVOKABLE void nextYear();
Q_INVOKABLE void previousYear(); Q_INVOKABLE void previousYear();
Q_INVOKABLE void nextDecade();
Q_INVOKABLE void previousDecade();
Q_INVOKABLE QString dayName(int weekday) const; Q_INVOKABLE QString dayName(int weekday) const;
Q_INVOKABLE int currentWeek() const; Q_INVOKABLE int currentWeek() const;
Q_INVOKABLE void resetToToday(); Q_INVOKABLE void resetToToday();
Q_INVOKABLE void goToMonth(int month);
Q_INVOKABLE void goToYear(int year);
Q_SIGNALS: Q_SIGNALS:
void displayedDateChanged(); void displayedDateChanged();

View File

@ -4,9 +4,7 @@
class DayData class DayData
{ {
public: public:
bool isPreviousMonth; bool isCurrent;
bool isCurrentMonth;
bool isNextMonth;
// bool containsHolidayItems; // bool containsHolidayItems;
// bool containsEventItems; // bool containsEventItems;
// bool containsTodoItems; // bool containsTodoItems;

View File

@ -26,9 +26,7 @@ DaysModel::DaysModel(QObject *parent) :
{ {
QHash<int, QByteArray> roleNames; QHash<int, QByteArray> roleNames;
roleNames.insert(isPreviousMonth, "isPreviousMonth"); roleNames.insert(isCurrent, "isCurrent");
roleNames.insert(isCurrentMonth, "isCurrentMonth");
roleNames.insert(isNextMonth, "isNextMonth");
//roleNames.insert(containsHolidayItems, "containsHolidayItems"); //roleNames.insert(containsHolidayItems, "containsHolidayItems");
//roleNames.insert(containsEventItems, "containsEventItems"); //roleNames.insert(containsEventItems, "containsEventItems");
// roleNames.insert(containsTodoItems, "containsTodoItems"); // roleNames.insert(containsTodoItems, "containsTodoItems");
@ -63,13 +61,11 @@ QVariant DaysModel::data(const QModelIndex &index, int role) const
{ {
if (index.isValid()) { if (index.isValid()) {
DayData currentData = m_data->at(index.row()); const DayData &currentData = m_data->at(index.row());
switch (role) { switch (role) {
case isPreviousMonth: case isCurrent:
return currentData.isPreviousMonth; return currentData.isCurrent;
case isNextMonth:
return currentData.isNextMonth;
// case containsHolidayItems: // case containsHolidayItems:
// return currentData.containsHolidayItems; // return currentData.containsHolidayItems;
/* case containsEventItems: /* case containsEventItems:

View File

@ -28,9 +28,7 @@ class DaysModel : public QAbstractListModel
Q_OBJECT Q_OBJECT
public: public:
enum Roles { enum Roles {
isPreviousMonth = Qt::UserRole + 1, isCurrent = Qt::UserRole + 1,
isCurrentMonth,
isNextMonth,
//containsHolidayItems, //containsHolidayItems,
//containsEventItems, //containsEventItems,
//containsTodoItems, //containsTodoItems,

View File

@ -1,6 +1,7 @@
/* /*
* Copyright 2013 Heena Mahour <heena393@gmail.com> * Copyright 2013 Heena Mahour <heena393@gmail.com>
* Copyright 2013 Sebastian Kügler <sebas@kde.org> * Copyright 2013 Sebastian Kügler <sebas@kde.org>
* Copyright 2015 Kai Uwe Broulik <kde@privat.broulik.de>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * 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.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as Components import org.kde.plasma.components 2.0 as Components
import org.kde.plasma.calendar 2.0
Item { Item {
id: dayStyle 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: { onHeightChanged: {
// this is needed here as the text is first rendered, counting with the default root.cellHeight // 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 // 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 // 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 { Rectangle {
id: todayRect id: todayRect
anchors.fill: parent anchors.fill: parent
opacity: { opacity: {
if (calendarGrid.selectedItem == dayStyle && today) { if (selected && today) {
0.6 0.6
} else if (today) { } else if (today) {
0.4 0.4
@ -55,7 +85,7 @@ Item {
id: highlightDate id: highlightDate
anchors.fill: todayRect anchors.fill: todayRect
opacity: { opacity: {
if (calendarGrid.selectedItem == dayStyle) { if (selected) {
0.6 0.6
} else if (dateMouse.containsMouse) { } else if (dateMouse.containsMouse) {
0.4 0.4
@ -71,10 +101,17 @@ Item {
Components.Label { Components.Label {
id: label id: label
anchors.centerIn: parent anchors.fill: parent
text: dayNumber horizontalAlignment: Text.AlignHCenter
opacity: (isPreviousMonth || isNextMonth) ? 0.5: 1.0 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 color: today ? theme.backgroundColor : theme.textColor
Behavior on color {
ColorAnimation { duration: units.shortDuration * 2 }
}
} }
MouseArea { MouseArea {
@ -82,17 +119,12 @@ Item {
anchors.fill: parent anchors.fill: parent
//z: label.z + 1 //z: label.z + 1
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: dayStyle.activated()
var rowNumber = Math.floor(index / 7);
week = 1+calendarBackend.weeksModel[rowNumber];
root.date = model;
calendarGrid.selectedItem = dayStyle;
}
} }
Component.onCompleted: { Component.onCompleted: {
if (today) { if (stack.depth === 1 && today) {
root.date = model; root.date = model
} }
} }
} }

View File

@ -1,6 +1,7 @@
/* /*
* Copyright 2013 Heena Mahour <heena393@gmail.com> * Copyright 2013 Heena Mahour <heena393@gmail.com>
* Copyright 2013 Sebastian Kügler <sebas@kde.org> * Copyright 2013 Sebastian Kügler <sebas@kde.org>
* Copyright 2015 Kai Uwe Broulik <kde@privat.broulik.de>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@ -16,22 +17,146 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.0
import org.kde.plasma.calendar 2.0 import org.kde.plasma.calendar 2.0
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as Components import org.kde.plasma.components 2.0 as Components
import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.extras 2.0 as PlasmaExtras
Item { Item {
id: daysCalendar 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 // Paints the inner grid and the outer frame
Canvas { Canvas {
id: canvas id: canvas
width: (root.cellWidth + root.borderWidth) * gridColumns + root.borderWidth anchors {
height: (root.cellHeight + root.borderWidth) * calendarGrid.rows + root.borderWidth horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom bottom: parent.bottom
}
width: (daysCalendar.cellWidth + root.borderWidth) * gridColumns + root.borderWidth
height: (daysCalendar.cellHeight + root.borderWidth) * calendarGrid.rows + root.borderWidth
opacity: root.borderOpacity opacity: root.borderOpacity
antialiasing: false antialiasing: false
@ -58,26 +183,26 @@ Item {
// horizontal lines // horizontal lines
for (var i = 0; i < calendarGrid.rows + 1; i++) { 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) { if (i == 0 || i == calendarGrid.rows) {
ctx.moveTo(0, lineY); ctx.moveTo(0, lineY);
} else { } 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); ctx.lineTo(width, lineY);
} }
// vertical lines // vertical lines
for (var i = 0; i < gridColumns + 1; i++) { 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 // Draw the outer vertical lines in full height so that it closes
// the outer rectangle // the outer rectangle
if (i == 0 || i == gridColumns) { if (i == 0 || i == gridColumns) {
ctx.moveTo(lineX, 0); ctx.moveTo(lineX, 0);
} else { } else {
ctx.moveTo(lineX, root.borderWidth + root.cellHeight); ctx.moveTo(lineX, root.borderWidth + daysCalendar.cellHeight);
} }
ctx.lineTo(lineX, height); ctx.lineTo(lineX, height);
} }
@ -88,13 +213,6 @@ Item {
} }
} }
Connections {
target: root
onShowWeekNumbersChanged: {
canvas.requestPaint();
}
}
Connections { Connections {
target: theme target: theme
onTextColorChanged: { onTextColorChanged: {
@ -104,7 +222,7 @@ Item {
Column { Column {
id: weeksColumn id: weeksColumn
visible: root.showWeekNumbers visible: showWeekNumbers
anchors { anchors {
top: canvas.top top: canvas.top
left: parent.left left: parent.left
@ -112,40 +230,38 @@ Item {
// The borderWidth needs to be counted twice here because it goes // The borderWidth needs to be counted twice here because it goes
// in fact through two lines - the topmost one (the outer edge) // in fact through two lines - the topmost one (the outer edge)
// and then the one below weekday strings // and then the one below weekday strings
topMargin: root.cellHeight + root.borderWidth + root.borderWidth topMargin: daysCalendar.cellHeight + root.borderWidth + root.borderWidth
} }
spacing: root.borderWidth spacing: root.borderWidth
Repeater { Repeater {
model: root.showWeekNumbers ? calendarBackend.weeksModel : [] model: showWeekNumbers ? calendarBackend.weeksModel : []
Components.Label { Components.Label {
height: root.cellHeight height: daysCalendar.cellHeight
width: root.cellWidth width: daysCalendar.cellWidth
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
opacity: 0.4 opacity: 0.4
text: modelData text: modelData
font.pixelSize: Math.max(theme.smallestFont.pixelSize, root.cellHeight / 6) font.pixelSize: Math.max(theme.smallestFont.pixelSize, daysCalendar.cellHeight / 6)
} }
} }
} }
Grid { Grid {
id: calendarGrid 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 { anchors {
right: canvas.right
rightMargin: root.borderWidth
bottom: parent.bottom bottom: parent.bottom
bottomMargin: root.borderWidth bottomMargin: root.borderWidth
} }
columns: calendarBackend.days columns: daysCalendar.columns
rows: calendarBackend.weeks + 1 rows: daysCalendar.rows + 1
spacing: root.borderWidth spacing: root.borderWidth
property Item selectedItem property Item selectedItem
property bool containsEventItems: false // FIXME property bool containsEventItems: false // FIXME
@ -159,17 +275,16 @@ Item {
} }
} }
Repeater { Repeater {
id: days id: days
model: calendarBackend.days
Item { Item {
width: root.cellWidth width: daysCalendar.cellWidth
height: root.cellHeight height: daysCalendar.cellHeight
Components.Label { Components.Label {
text: Qt.locale().dayName(calendarBackend.firstDayOfWeek + index, Locale.ShortFormat) 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 horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignBottom verticalAlignment: Text.AlignBottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -181,9 +296,14 @@ Item {
Repeater { Repeater {
id: repeater id: repeater
model: calendarBackend.daysModel
DayDelegate {} DayDelegate {
id: delegate
width: daysCalendar.cellWidth
height: daysCalendar.cellHeight
onActivated: daysCalendar.activated(index, model, delegate)
}
} }
} }
} }

View File

@ -1,6 +1,7 @@
/* /*
* Copyright 2013 Heena Mahour <heena393@gmail.com> * Copyright 2013 Heena Mahour <heena393@gmail.com>
* Copyright 2013 Sebastian Kügler <sebas@kde.org> * Copyright 2013 Sebastian Kügler <sebas@kde.org>
* Copyright 2015 Kai Uwe Broulik <kde@privat.broulik.de>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@ -16,7 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import org.kde.plasma.calendar 2.0 import org.kde.plasma.calendar 2.0
import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.components 2.0 as PlasmaComponents
@ -27,31 +30,26 @@ Item {
anchors.fill: parent anchors.fill: parent
property QtObject date
property date showDate: new Date()
property alias selectedMonth: calendarBackend.monthName property alias selectedMonth: calendarBackend.monthName
property alias selectedYear: calendarBackend.year property alias selectedYear: calendarBackend.year
property QtObject date
property date currentDate
property date showDate: new Date()
property int borderWidth: 1 property int borderWidth: 1
property real borderOpacity: 0.4 property real borderOpacity: 0.4
property int columns: calendarBackend.days property int columns: calendarBackend.days
property int rows: calendarBackend.weeks 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 Item selectedItem
property int week; property int week;
property int firstDay: new Date(showDate.getFullYear(), showDate.getMonth(), 1).getDay() property int firstDay: new Date(showDate.getFullYear(), showDate.getMonth(), 1).getDay()
property date today property date today
property bool showWeekNumbers: false property bool showWeekNumbers: false
function isToday(date) { function isToday(date) {
if (date.toDateString() == new Date().toDateString()) { if (date.toDateString() == new Date().toDateString()) {
return true; return true;
@ -67,6 +65,31 @@ Item {
function resetToToday() { function resetToToday() {
calendarBackend.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 { Calendar {
@ -76,176 +99,167 @@ Item {
weeks: 6 weeks: 6
firstDayOfWeek: Qt.locale().firstDayOfWeek firstDayOfWeek: Qt.locale().firstDayOfWeek
today: root.today today: root.today
onYearChanged: {
updateYearOverview()
updateDecadeOverview()
}
} }
ColumnLayout { ListModel {
// This is to ensure that the inner grid.width is always aligned to be divisible by 7, id: monthModel
// 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
}
PlasmaExtras.Heading { Component.onCompleted: {
id: monthHeading for (var i = 0; i < 12; ++i) {
append({
level: 1 label: Qt.locale().standaloneMonthName(i, Locale.LongFormat),
text: calendarBackend.displayedDate.getFullYear() == new Date().getFullYear() ? root.selectedMonth : root.selectedMonth + ", " + root.selectedYear monthNumber: i + 1,
elide: Text.ElideRight isCurrent: true
font.capitalization: Font.Capitalize })
Loader {
id: menuLoader
property QtObject calendarBackend: calendarBackend
} }
MouseArea { updateYearOverview()
id: monthMouse }
property int previousPixelDelta }
width: monthHeading.paintedWidth ListModel {
anchors { id: yearModel
left: parent.left
top: parent.top Component.onCompleted: {
bottom: parent.bottom 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: { NumberAnimation {
if (menuLoader.source == "") { target: enterItem
menuLoader.source = "MonthMenu.qml" duration: units.longDuration
} property: "opacity"
menuLoader.item.year = selectedYear from: 0
menuLoader.item.open(0, height); to: 1
} }
onExited: previousPixelDelta = 0 NumberAnimation {
onWheel: { target: enterItem
var delta = wheel.angleDelta.y || wheel.angleDelta.x duration: units.longDuration
var pixelDelta = wheel.pixelDelta.y || wheel.pixelDelta.x property: "scale"
from: 1.5
// For high-precision touchpad scrolling, we get a wheel event for basically every slightest to: 1
// 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. popTransition: StackViewTransition {
if (pixelDelta) { NumberAnimation {
if (Math.abs(previousPixelDelta) < monthMouse.height) { target: exitItem
previousPixelDelta += pixelDelta duration: units.longDuration
return property: "opacity"
} from: 1
} to: 0
}
if (delta >= 15) { NumberAnimation {
calendarBackend.previousMonth() target: exitItem
} else if (delta <= -15) { duration: units.longDuration
calendarBackend.nextMonth() property: "scale"
} from: 1
previousPixelDelta = 0 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 { DaysCalendar {
id: calendar title: calendarBackend.displayedDate.getFullYear()
columns: 3
rows: 4
Layout.fillWidth: true dateMatchingPrecision: Calendar.MatchYearAndMonth
Layout.fillHeight: true
PlasmaComponents.Label { gridModel: monthModel
text: "◀"
opacity: leftmouse.containsMouse ? 1 : 0.4 onPrevious: calendarBackend.previousYear()
Behavior on opacity { NumberAnimation {} } onNext: calendarBackend.nextYear()
font.pixelSize: Math.max(theme.smallestFont.pixelSize, Math.floor(root.cellHeight / 3)) onHeaderClicked: stack.push(decadeOverview)
anchors { onActivated: {
top: parent.top calendarBackend.goToMonth(date.monthNumber)
left: parent.left stack.pop()
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()
}
}
} }
} }
} }
Component {
id: decadeOverview
/* DaysCalendar {
Item { readonly property int decade: {
id: calendarToolbar var year = calendarBackend.displayedDate.getFullYear()
visible: false return year - year % 10
anchors { }
left: parent.left
right: parent.right
bottomMargin: 20
bottom: parent.bottom
}
PlasmaComponents.ToolButton { title: decade + " " + (decade + 9)
id: currentDate columns: 3
iconSource: "view-pim-calendar" rows: 4
width: height
onClicked: {
calendarBackend.startDate = today();
}
PlasmaCore.ToolTipArea {
id: tool
anchors.fill: currentDate
mainText: "Select Today"
}
anchors {
left: parent.left
}
}
PlasmaComponents.TextField { dateMatchingPrecision: Calendar.MatchYear
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
}
}
PlasmaComponents.TextField { gridModel: yearModel
id: weekField
text: week == 0 ? calendarBackend.currentWeek(): week onPrevious: calendarBackend.previousDecade()
width: calendarOperations.width/10 onNext: calendarBackend.nextDecade()
anchors { onActivated: {
right: parent.right calendarBackend.goToYear(date.yearNumber)
stack.pop()
} }
} }
} }
*/
} }