6import QtQuick.Controls 2.15 as QQC2
7import QtQuick.Layouts 1.15
8import org.kde.kirigami 2.15 as Kirigami
9import org.kde.kirigamiaddons.dateandtime 1.0
10import org.kde.kirigamiaddons.components 1.0 as Components
11import org.kde.kirigamiaddons.delegates 1.0 as Delegates
16 signal datePicked(date pickedDate)
18 property date selectedDate: new Date()
19 readonly property int year: selectedDate.getFullYear()
20 readonly property int month: selectedDate.getMonth()
21 readonly property int day: selectedDate.getDate()
22 property bool showDays: true
23 property bool showControlHeader: true
30 property date minimumDate
37 property date maximumDate
39 topPadding: Kirigami.Units.largeSpacing
40 rightPadding: Kirigami.Units.largeSpacing
41 bottomPadding: Kirigami.Units.largeSpacing
42 leftPadding: Kirigami.Units.largeSpacing
44 onActiveFocusChanged: if (activeFocus) {
45 dateSegmentedButton.forceActiveFocus();
48 property bool _completed:
false
49 property bool _runSetDate:
false
51 onSelectedDateChanged:
if (selectedDate !== null && _completed) {
52 setToDate(selectedDate)
55 Component.onCompleted: {
58 setToDate(selectedDate);
61 onShowDaysChanged:
if (!showDays) pickerView.currentIndex = 1;
63 function setToDate(date) {
69 if (root.minimumDate.valueOf() && date.valueOf() < minimumDate.valueOf()) {
73 if (root.maximumDate.valueOf() && date.valueOf() > maximumDate.valueOf()) {
77 const yearDiff = date.getFullYear() - yearPathView.currentItem.startDate.getFullYear();
80 const decadeDiff = Math.floor((date.getFullYear() + 1 - decadePathView.currentItem.startDate.getFullYear()) / 12);
82 let newYearIndex = yearPathView.currentIndex +
yearDiff;
83 let newDecadeIndex = decadePathView.currentIndex + decadeDiff;
85 let firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole);
86 let lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 2,0), InfiniteCalendarViewModel.StartDateRole);
87 let firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole);
88 let lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole);
91 const monthDiff = date.getMonth() - monthPathView.currentItem.firstDayOfMonth.getMonth() + (12 * (date.getFullYear() - monthPathView.currentItem.firstDayOfMonth.getFullYear()));
92 let newMonthIndex = monthPathView.currentIndex + monthDiff;
93 let firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole);
94 let lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole);
96 while(firstMonthItemDate >= date) {
97 monthPathView.model.addDates(
false)
98 firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole);
101 if(firstMonthItemDate < date && newMonthIndex === 0) {
102 newMonthIndex = date.getMonth() - firstMonthItemDate.getMonth() + (12 * (date.getFullYear() - firstMonthItemDate.getFullYear())) + 1;
105 while(lastMonthItemDate <= date) {
106 monthPathView.model.addDates(
true)
107 lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole);
110 monthPathView.currentIndex = newMonthIndex;
114 while(firstYearItemDate >= date) {
115 yearPathView.model.addDates(
false)
116 firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole);
119 if(firstYearItemDate < date && newYearIndex === 0) {
120 newYearIndex = date.getFullYear() - firstYearItemDate.getFullYear() + 1;
123 while(lastYearItemDate <= date) {
124 yearPathView.model.addDates(
true)
125 lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole);
129 while(firstDecadeItemDate >= date) {
130 decadePathView.model.addDates(
false)
131 firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole);
134 if(firstDecadeItemDate < date && newDecadeIndex === 0) {
135 newDecadeIndex = date.getFullYear() - firstDecadeItemDate.getFullYear() + 1;
138 while(lastDecadeItemDate.getFullYear() <= date.getFullYear()) {
139 decadePathView.model.addDates(
true)
140 lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole);
143 yearPathView.currentIndex = newYearIndex;
144 decadePathView.currentIndex = newDecadeIndex;
150 selectedDate =
new Date()
153 function prevMonth() {
154 const newDate =
new Date(selectedDate.getFullYear(), selectedDate.getMonth() - 1, selectedDate.getDate());
155 if (root.minimumDate.valueOf() && newDate.valueOf() < minimumDate.valueOf()) {
156 if (selectedDate == minimumDate) {
159 selectedDate = minimumDate;
161 selectedDate = newDate;
165 function nextMonth() {
166 const newDate =
new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, selectedDate.getDate());
167 if (root.maximumDate.valueOf() && newDate.valueOf() > maximumDate.valueOf()) {
168 if (selectedDate == maximumDate) {
171 selectedDate = maximumDate;
174 selectedDate = newDate;
178 function prevYear() {
179 const newDate =
new Date(selectedDate.getFullYear() - 1, selectedDate.getMonth(), selectedDate.getDate())
180 if (root.minimumDate.valueOf() && newDate.valueOf() < minimumDate.valueOf()) {
181 if (selectedDate == minimumDate) {
184 selectedDate = minimumDate;
186 selectedDate = newDate;
190 function nextYear() {
191 const newDate =
new Date(selectedDate.getFullYear() + 1, selectedDate.getMonth(), selectedDate.getDate());
192 if (root.maximumDate && newDate.valueOf() > maximumDate.valueOf()) {
193 if (selectedDate == maximumDate) {
196 selectedDate = maximumDate;
198 selectedDate = newDate;
202 function prevDecade() {
203 const newDate =
new Date(selectedDate.getFullYear() - 10, selectedDate.getMonth(), selectedDate.getDate());
204 if (root.minimumDate.valueOf() && newDate.valueOf() < minimumDate.valueOf()) {
205 if (selectedDate == minimumDate) {
208 selectedDate = minimumDate;
210 selectedDate = newDate;
214 function nextDecade() {
215 const newDate =
new Date(selectedDate.getFullYear() + 10, selectedDate.getMonth(), selectedDate.getDate())
216 if (root.maximumDate && newDate.valueOf() > maximumDate.valueOf()) {
217 if (selectedDate == maximumDate) {
220 selectedDate = maximumDate;
222 selectedDate = newDate;
226 contentItem: ColumnLayout {
231 Layout.fillWidth:
true
232 Layout.bottomMargin:
Kirigami.Units.smallSpacing
234 Components.SegmentedButton {
235 id: dateSegmentedButton
239 text: root.selectedDate.getDate()
240 onTriggered: pickerView.currentIndex = 0
241 checked: pickerView.currentIndex === 0
244 text: root.selectedDate.toLocaleDateString(
Qt.locale(),
"MMMM")
245 onTriggered: pickerView.currentIndex = 1
246 checked: pickerView.currentIndex === 1
250 text: root.selectedDate.getFullYear()
251 onTriggered: pickerView.currentIndex = 2
252 checked: pickerView.currentIndex === 2
258 Layout.fillWidth:
true
261 Components.SegmentedButton {
265 icon.name:
'go-previous-view'
266 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Go Previous")
267 displayHint:
Kirigami.DisplayHint.IconOnly
269 if (pickerView.currentIndex === 1) {
271 }
else if (pickerView.currentIndex === 2) {
279 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Jump to today")
280 displayHint:
Kirigami.DisplayHint.IconOnly
281 icon.name: 'go-jump-today'
282 onTriggered: goToday()
286 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Go Next")
287 icon.name: 'go-next-view'
288 displayHint:
Kirigami.DisplayHint.IconOnly
290 if (pickerView.currentIndex === 1) {
292 }
else if (pickerView.currentIndex === 2) {
310 Layout.fillWidth:
true
311 Layout.fillHeight:
true
318 model: InfiniteCalendarViewModel {
319 scale: InfiniteCalendarViewModel.MonthScale
320 currentDate: root.selectedDate
321 minimumDate: root.minimumDate
322 maximumDate: root.maximumDate
328 property date firstDayOfMonth: model.firstDay
329 property bool isNextOrCurrentItem: index >= monthPathView.currentIndex -1 && index <= monthPathView.currentIndex + 1
331 active: isNextOrCurrentItem && root.showDays
333 sourceComponent: GridLayout {
337 width: monthPathView.
width
338 height: monthPathView.height
339 Layout.topMargin:
Kirigami.Units.smallSpacing
341 property var modelLoader: Loader {
344 year: firstDay.getFullYear()
345 month: firstDay.getMonth() + 1
354 model: dayGrid.modelLoader.item.weekDays
355 delegate: QQC2.Label {
356 Layout.fillWidth:
true
357 Layout.fillHeight:
true
358 horizontalAlignment: Text.AlignHCenter
359 rightPadding:
Kirigami.Units.mediumSpacing
360 leftPadding:
Kirigami.Units.mediumSpacing
369 model: dayGrid.modelLoader.item
371 delegate: DatePickerDelegate {
374 required
property bool isToday
375 required
property bool sameMonth
376 required
property int dayNumber
378 repeater: dayRepeater
379 minimumDate: root.minimumDate
380 maximumDate: root.maximumDate
381 previousAction: goPreviousAction
382 nextAction: goNextAction
386 Accessible.name:
if (dayNumber === 1 || index === 0) {
387 date.toLocaleDateString(locale, Locale.ShortFormat)
398 checked: date.getDate() === selectedDate.getDate() &&
399 date.getMonth() === selectedDate.getMonth() &&
400 date.getFullYear() === selectedDate.getFullYear()
401 opacity: sameMonth && inScope ? 1 : 0.6
413 onCurrentIndexChanged: {
414 if (pickerView.currentIndex === 0) {
415 root.selectedDate =
new Date(currentItem.firstDayOfMonth.getFullYear(), currentItem.firstDayOfMonth.getMonth(), root.selectedDate.getDate());
418 if (currentIndex >= count - 2) {
419 model.addDates(
true);
420 }
else if (currentIndex <= 1) {
421 model.addDates(
false);
422 startIndex += model.datesToAdd;
432 model: InfiniteCalendarViewModel {
433 scale: InfiniteCalendarViewModel.YearScale
434 currentDate: root.selectedDate
440 required
property int index
441 required
property date startDate
443 property bool isNextOrCurrentItem: index >= yearPathView.currentIndex -1 && index <= yearPathView.currentIndex + 1
446 height: parent.height
448 active: isNextOrCurrentItem
450 sourceComponent: GridLayout {
456 buttons: yearGrid.children
462 model: yearGrid.columns * yearGrid.rows
464 delegate: DatePickerDelegate {
467 date:
new Date(yearViewLoader.startDate.getFullYear(), index)
469 minimumDate: root.minimumDate.valueOf() ?
new Date(root.minimumDate).setDate(0) :
new Date(
"invalid")
470 maximumDate: root.maximumDate.valueOf() ? new Date(root.maximumDate.getFullYear(), root.maximumDate.getMonth() + 1, 0) : new Date("invalid")
471 repeater: monthRepeater
472 previousAction: goPreviousAction
473 nextAction: goNextAction
475 horizontalPadding: padding * 2
476 rightPadding: undefined
477 leftPadding: undefined
478 highlighted: date.getMonth() === new Date().getMonth() &&
479 date.getFullYear() === new Date().getFullYear()
481 checked: date.getMonth() === selectedDate.getMonth() &&
482 date.getFullYear() === selectedDate.getFullYear()
483 text:
Qt.locale().standaloneMonthName(date.getMonth())
485 selectedDate =
new Date(date);
486 root.datePicked(date);
487 if(root.showDays) pickerView.currentIndex = 0;
494 onCurrentIndexChanged: {
495 if (pickerView.currentIndex === 1) {
496 root.selectedDate =
new Date(currentItem.startDate.getFullYear(), root.selectedDate.getMonth(), root.selectedDate.getDate());
499 if (currentIndex >= count - 2) {
500 model.addDates(
true);
501 }
else if (currentIndex <= 1) {
502 model.addDates(
false);
503 startIndex += model.datesToAdd;
514 model: InfiniteCalendarViewModel {
515 scale: InfiniteCalendarViewModel.DecadeScale
516 currentDate: root.selectedDate
522 required
property int index
523 required
property date startDate
525 property bool isNextOrCurrentItem: index >= decadePathView.currentIndex -1 && index <= decadePathView.currentIndex + 1
528 height: parent.height
530 active: isNextOrCurrentItem
532 sourceComponent: GridLayout {
539 buttons: decadeGrid.children
545 model: decadeGrid.columns * decadeGrid.rows
547 delegate: DatePickerDelegate {
550 readonly
property bool sameDecade: Math.floor(date.getFullYear() / 10) == Math.floor(year / 10)
552 date:
new Date(startDate.getFullYear() + index, 0)
553 minimumDate: root.minimumDate.valueOf() ? new Date(root.minimumDate.getFullYear(), 0, 0) : new Date("invalid")
554 maximumDate: root.maximumDate.valueOf() ? new Date(root.maximumDate.getFullYear(), 12, 0) : new Date("invalid")
555 repeater: decadeRepeater
556 previousAction: goPreviousAction
557 nextAction: goNextAction
559 highlighted: date.getFullYear() === new Date().getFullYear()
561 horizontalPadding: padding * 2
562 rightPadding: undefined
563 leftPadding: undefined
565 checked: date.getFullYear() === selectedDate.getFullYear()
566 opacity: sameDecade ? 1 : 0.7
567 text: date.getFullYear()
569 selectedDate =
new Date(date);
570 root.datePicked(date);
571 pickerView.currentIndex = 1;
578 onCurrentIndexChanged: {
579 if (pickerView.currentIndex === 2) {
581 root.selectedDate =
new Date(currentItem.startDate.getFullYear() + 1, root.selectedDate.getMonth(), root.selectedDate.getDate());
584 if (currentIndex >= count - 2) {
585 model.addDates(
true);
586 }
else if (currentIndex <= 1) {
587 model.addDates(
false);
588 startIndex += model.datesToAdd;
Month model exposing month days and events to a QML view.
QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
int yearDiff(QDate start, QDate end)
const QObjectList & children() const const